Encore运行时嵌入Redis服务器:本地开发与生产环境行为一致的秘诀

Encore运行时嵌入Redis服务器:本地开发与生产环境行为一致的秘诀

运行时嵌入Redis服务器:本地与生产环境一致性的探索

2026年6月25日,这篇阅读时长6分钟的文章将介绍如何在运行时中为本地开发和测试运行内存版Redis,以及如何确保其行为与生产环境中的Redis一致。

Encore:跨环境运行后端代码的利器

Encore能在本地开发、测试和生产环境中运行相同的后端代码。应用程序所依赖的基础设施会在代码中声明,Encore会为每个环境配置相应的基础设施。为了让本地开发更具实用性,这些基础设施不仅要在本地机器上可用,其行为还需与生产环境保持一致。

缓存搭建难题与创新解决方案

大多数基础设施在本地搭建并不复杂,如数据库可在Docker中运行,发布/订阅可借助本地的NSQ守护进程。然而,缓存的搭建比较棘手,现实选择要么在Docker中运行真正的Redis,需安装并维护另一个容器;要么用模拟对象替代,但代码依赖模拟对象未实现的功能时,行为会与Redis不一致。我们采用不同方法,在运行时中内置一个内存版Redis服务器,它会在本地开发和测试时自动启动,且与运行时处于同一进程。本文将介绍该方法的工作原理、为何将Go实现移植到Rust,以及如何确保内存版服务器的行为与应用程序在生产环境中使用的Redis一致。

同一进程中的内存版Redis

Encore的Go运行时长期采用这种方式。在Go端,本地开发使用alicebob/miniredis,这是用Go编写的内存版Redis服务器,本地运行应用程序无需外部Redis。构建为TypeScript应用程序提供支持的Rust运行时时,我们也需实现相同功能。一种方案是保留Go实现,将其作为独立进程由运行时启动和停止,但这需额外发布二进制文件,并在运行时之外管理另一个进程,包括其启动、关闭和错误处理。我们希望内存版服务器能像其他基础设施层一样,直接集成在运行时内部。于是,我们将miniredis移植到了Rust(#2300),使其作为库在运行时中运行。这个移植版本约有25000行Rust代码,实现了应用程序实际使用的数据类型,包括字符串、哈希、列表、集合、有序集合、流、发布/订阅、事务和Lua脚本。它是真正的Redis服务器,通过TCP套接字监听并使用Redis有线协议(RESP)进行通信,而非仅模拟部分命令的存根。移植过程中,我们还保留了Go版本的操作行为。miniredis有自己的模拟时钟,因此嵌入式服务器会运行一个小的后台任务,每秒推进一次时钟,以确保在长时间会话中基于时间的过期机制正常工作,并将键的数量限制在一定范围内,避免本地缓存无限增长。

运行时如何选择连接地址

Encore应用程序中的缓存和其他资源一样,是在代码中声明的。运行时只需要这个声明就足够了。在部署环境中,Encore会配置一个真正的Redis,运行时会连接到该Redis;而在本地开发和测试环境中,运行时会在本地地址启动内置服务器并连接到它。这个决策基于运行时配置,每个Redis集群都有一个 “in_memory” 标志,当该标志设置为 “true” 时,运行时会启动嵌入式服务器,而不是连接到配置的服务器(#2322)。在运行时中,这个决策逻辑很简单。当需要嵌入式服务器时,运行时会启动它,并将一个指向本地服务器的 “redis://” 地址传递给原本用于连接托管集群的Redis客户端。无论在本地还是生产环境,应用程序代码都是相同的。它持有一个键空间(keyspace),并对Redis客户端调用 “get”、“set”、“increment” 等方法。唯一的区别在于客户端连接的地址。由于嵌入式服务器通过相同类型的套接字使用相同的协议进行通信,客户端连接它的方式与连接托管Redis完全相同。

确保与真实Redis行为一致

只有当嵌入式服务器的行为与它所替代的Redis一致时,它才具有实用价值。一个命令在处理边缘情况时的细微差异,可能会导致代码在本地通过测试,但在生产环境中失败,而这正是我们追求本地与生产环境一致性所需要避免的情况。为了防止这种情况发生,我们将Rust服务器与移植来源的实现进行对比测试。miniredis附带了一个Go集成测试套件,该套件会向实时服务器发送命令并检查响应。我们对Rust服务器运行相同的测试套件,并逐字节比较原始的RESP响应。当字节匹配时,说明我们的服务器的响应方式与参考实现一致。以这种方式运行参考测试套件,揭示了一些很容易被忽略的差异。其中一个差异在于过期机制的测试,测试套件会推进模拟时钟以检查键是否按计划过期,因此Rust服务器需要提供一个命令,让测试可以调用该命令来快进自己的时钟,以达到相同的效果。另一个差异是TLS,测试套件使用的证书链能被Go的TLS实现接受,但会被Rust的TLS实现拒绝,因此为了能够建立连接,我们需要为测试构建一个合适的证书层次结构。这些差异是手写模拟对象无法重现的,如果不与参考实现进行对比,它们很可能会被忽视。

本地开发的新体验

在本地开发和测试中,你只需声明并使用缓存,而无需在应用程序之外安装或运行任何额外的东西。测试使用的是真正的Redis服务器,而不是模拟对象,因此测试所依赖的命令行为与生产环境中的行为一致。需要注意的是,嵌入式服务器仅用于本地开发和测试。在生产环境中,Encore会配置一个真正的、托管的Redis,因为嵌入式服务器是为开发设计的,不具备扩展性。将其集成到运行时中,使得本地开发和测试能够与生产环境保持一致,而无需手动搭建缓存。如果你想深入了解,文档中涵盖了Encore如何根据代码配置基础设施以及缓存原语本身的相关内容。

目录

  • 同一进程中的内存版Redis
  • 运行时如何选择连接地址
  • 确保与真实Redis行为一致
  • 本地开发的新体验

Encore

面向人类和智能体的后端平台。使用Go或TypeScript构建后端,并在自己的AWS或GCP上运行,同时具备团队和AI智能体安全发布所需的保障措施。

相关文章

  • 基础设施 06/10/26 / 阅读时长7分钟:为什么基础设施变更仍需一周时间:应用程序代码可以在数小时内发布,但所需的基础设施却要等待数天进行审核。本文将探讨排队问题的根源、AI编码工具对其的影响,以及团队如何解决这一问题。
  • 人工智能 06/03/26 / 阅读时长7分钟:AI智能体钟爱类型错误:智能体只会修复它能看到的问题,而编译错误是始终能及时发现的信号。
  • 人工智能 05/20/26 / 阅读时长14分钟:TypeScript后端框架是否已为AI智能体做好准备?我们对五个TypeScript后端框架进行了一次基准测试。分析测试结果后,我们又进行了两次测试,每次结果都有所不同。