Why RESP3 will be the only protocol supported by Redis 6

Salvatore Sanfilippo

为什么 Redis 6 将只支持 RESP3 协议

[编辑!Stack Overflow 的 Marc Gravell(马克·格拉维尔)建议我们可以按连接切换协议,以实现向后兼容:发送一条命令来启用 RESP3。因此,我们不再需要通过全局配置来切换服务器行为。这样说来,这对我而言就更容易接受了,我正在重新思考这篇博文的核心内容。]

Redis 5 发布几周后,我开始着手实现 RESP3。经过几天的工作,看到这件事终于成为现实,感觉非常好。RESP3 是 Redis 从 Redis 6 开始将使用的新客户端—服务器协议。https://github.com/antirez/resp3上的规范应该能清楚说明,这一从旧协议 RESP2 演进而来的协议将如何改善 Redis 生态系统。不过可以说,最重要的一点是,RESP3 比 RESP2 更具“语义性”。例如,它引入了 map、set(元素无序的列表)、返回数据的 attributes(属性)等概念;attributes 可以用辅助信息来补充响应,等等。最终目标是让新的 Redis 客户端少替我们做一些工作,也就是说,只需确定一组固定规则,就能把 RESP3 的每种响应类型转换为客户端库编程语言中相应的合适类型。

在我设想的 Redis 未来中,客户端在底层会更加智能,会尽力处理连接、pipelining(流水线处理)和状态,而在面向用户的一侧则会明显简单得多,理想的 Redis 客户端应该像这样:

result = redis.call(“GET”,keyname);

当然,你可以在此之上构建更高级的抽象,但底层应该就是这样;返回的响应也不应该要求针对特定命令进行临时性的过滤:RESP3 的返回类型应包含足够的信息,以便返回合适的数据类型。因此,HGETALL 将返回 RESP3 的“map”,LRANGE 将返回“array”,而 EXISTS 将返回 RESP3 的“boolean”。

这样一来,即使客户端库没有为某个新命令进行*专门*的设计,新命令也能按预期工作。而在 RESP2 中,实际情况往往是:命令可以通过“method missing”或类似机制运行,但后来当该命令在客户端库中被*真正*实现时,返回类型却发生了变化,从而引入了隐蔽的不兼容问题。

不过,尽管新协议是对旧协议的渐进式改进,但它当然会在客户端库一侧引入破坏性不兼容,也会在*应用层( application layer)*引入不兼容。例如,ZSCORE 现在将返回 double,而不是 string,因此应用代码应该更新;或者,客户端库也可以实现一个兼容选项,将 RESP3 响应转换回原来的 RESP2 类型。

如果 Lua 脚本不针对新协议进行修改,也将无法继续工作,因为 Lua 同样会看到 redis.call() 命令返回的更具语义性的类型。类似地,Lua 也将能够返回 RESP3 中实现的所有新数据类型。

正因为如此,人们对我的决定感到担忧:我打算让 Redis 6 只支持 RESP3。不会提供兼容模式来将 Redis 6 服务器切换到 RESP2,因此你要么升级客户端库并升级应用程序(或者使用客户端库的向后兼容模式),要么就无法切换到 Redis 6。

我这样做有充分的理由,下面想解释我为何作出这一决定,以及我将如何缓解用户和客户端库作者所面临的问题。先从缓解措施说起:

  • Redis 5 将在 Redis 6 发布后继续获得完整支持两年。所有关键修复都会回移植到 Redis 5,并且会持续提供补丁级版本。
  • 预计 Redis 6 将在大约 1 年或 1 年半后发布。不过,Redis 6 将在大约 1 个月后切换到 RESP3。因此,人们将有很长时间使用、试验并应对采用新协议的 Redis unstable(不稳定版)。与许多其他软件不同,Redis unstable 有很多普通用户,原因既在于它是 Github 上的默认分支,也在于按照传统,Redis unstable 从来没有真正那么不稳定;这将带来大量的提前接触机会。
  • 对此我还没有百分之百确定,但 Lua 脚本引擎可能会提供兼容模式,以返回与 Redis 5 相同的类型。不过,该兼容模式默认不会启用,而是需要针对每个执行的脚本选择启用:在调用 Redis 命令前,先调用特殊的 redis.resp2_compat() 函数。因此,每个 Redis 6 服务器的行为都会相同,不受其配置影响,就像 Redis 在过去 10 年中一直以来那样。

以上就是缓解措施。下面则是我不会让 Redis 6 同时支持两个版本的原因:

  1. 这或多或少完全没有用。如果人们把 Redis 6 切换到 RESP2 模式,他们仍然停留在过去,只是在等待 Redis 7 发布、取消 RESP2 支持并让一切再次崩溃。与此同时,当你处理一个 Redis 6 安装时,你永远不知道它会返回什么,具体取决于它的配置方式。因此,同一个客户端库对同一个命令可能返回 Hash,也可能返回 Array。
  2. 这会在没有充分理由的情况下增加工作量和复杂度(见“1”)。许多命令都需要检查旧协议,以确定应以何种格式返回响应。
  3. 将 Redis 6 的新功能与协议变更绑定在一起,我们就给了用户充分理由进行切换,并移植他们的客户端和应用程序。到某个时候,一切都会完成,我们就可以专注于新事物。否则,我们会拥有一批切换到新服务器以使用新功能、却仍在使用旧协议的 Redis 6 用户,而到了 Redis 7,这场闹剧又会重演。
  4. 如果有人告诉你,适配客户端库是一项可怕的工作,那我不敢苟同。是的,确实需要做一些改动,但现在既然我正在实现服务器端,就能看出这并没有那么可怕。真正可怕的是,大多数客户端工作根本没有报酬,完全是出于热情和与他人分享的意愿才进行的。我敢打赌,我们很快就会看到许多 RESP3 的实现。
  5. RESP3 的设计允许客户端自动检测当前是 RESP2 还是 RESP3,并进行切换,因此新的客户端将同时适用于 Redis <= 5 和 Redis 6。

好了,就这些。希望这能说明我的观点及其背后的原因;同时,协议切换期间将启用的缓解措施,也能让用户相信,这不会造成非常“艰难”的破坏性变更。