Why RESP3 will be the only protocol supported by Redis 6

Salvatore Sanfilippo

为什么 RESP3 将成为 Redis 6 唯一支持的协议

原文由 Salvatore Sanfilippo 发布,订阅该博客

[编辑!我正在重新考虑以上所有内容,因为来自 Stack Overflow 的 Marc Gravell 建议,我们可以按连接来实现向后兼容的协议切换,只需发送一条命令来启用 RESP3。这意味着不再需要通过全局配置来切换服务器的行为。这样一来,对我来说就可接受得多了,我也在重新思考这篇博文的核心观点]

Redis 5 发布几周后,我开始着手实现 RESP3,做了几天工作后,看到这一切终于成为现实,感觉非常好。RESP3 是 Redis 从 6 开始将要使用的全新客户端-服务器协议。https://github.com/antirez/resp3 上的规范应该已经清楚地说明了,这一对旧协议 RESP2 的演进将如何改善 Redis 生态。但最重要的一点是,RESP3 比 RESP2 更具“语义”。例如,它引入了映射(map)、集合(无序元素列表)、返回数据的属性(可以用附加信息来增强回复)等概念。最终目标是让新的 Redis 客户端需要做的工作更少,也就是只需确定一套固定的规则,就能把 RESP3 的每一种回复类型转换为客户端编程语言中对应的恰当类型。

在 Redis 的未来,我希望看到客户端在底层变得更智能,竭尽全力处理好连接、流水线和状态,而在面向用户的层面则显得简单得多,以至于理想的 Redis 客户端看起来就像这样:

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

当然,在此之上你还可以构建更高级的抽象,但最底层就应该是这个样子,而且返回的回复不应再需要针对特定命令做专门的过滤:RESP3 的返回类型本身就应包含足够的信息来返回恰当的数据类型。比如,HGETALL 将返回 RESP3 的“map”,而 LRANGE 将返回“array”,EXISTS 则会返回 RESP3 的“boolean”。

这也意味着,即使客户端库并未*专门*为某个新命令做适配,新命令也能按预期工作。而在使用 RESP2 时,情况往往是命令通过类似 "method missing" 这样的机制勉强可用,但等到客户端库*真正*为该命令实现支持时,返回类型却发生了变化,从而引入了微妙的不兼容。

然而,虽然新协议是在旧协议基础上的增量改进,它仍会在客户端库层面(当然)以及*应用层*带来破坏性的不兼容。例如,ZSCORE 现在将返回双精度浮点数,而不是字符串,因此需要更新应用代码,或者,客户端库也可以提供一个兼容选项,将 RESP3 的回复转换回原先的 RESP2 类型。

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

正因为如此,大家对我的决定感到担忧:我打算让 Redis 6 *只*支持 RESP3。Redis 6 将不会提供切换回 RESP2 的兼容模式,所以你要么升级客户端库并升级应用(或使用客户端库的向后兼容模式),要么就无法升级到 Redis 6。

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

  • Redis 5 将在 Redis 6 发布后继续获得 2 年的全面支持。所有关键的修复都会向后移植到 Redis 5,并持续提供补丁版本。
  • Redis 6 预计在大约 1 到 1.5 年后发布。但 Redis 6 大约在 1 个月后就会切换到 RESP3。因此,人们将有很长一段时间去使用、体验和适应这个使用新协议的不稳定版本。考虑到与许多其他软件不同,Redis 的不稳定版拥有大量日常用户,既因为它是 GitHub 上的默认分支,也因为传统上 Redis 的不稳定版其实并没有那么不稳定,这将带来大量的前期曝光。
  • 这一点我还不能 100% 确定,但 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。

就这些了。希望这能阐明我的观点和背后的原因,同时,协议切换期间将采取的这些缓解措施,也能让用户相信,这不会是一次非常“剧烈”的断裂。

本文章由 muse-spark-1.2-contributor 进行翻译

评论