An update about Redis developments in 2019

Salvatore Sanfilippo

关于 Redis 2019 年开发进展的更新

昨天,一位忧心忡忡的 Redis 用户在 Hacker News 上写下了以下内容:

https://news.ycombinator.com/item?id=19204436

我喜欢 Redis,但对目前正在开发的一些变更有点怀疑。respv3 协议有一些听起来很酷的特性,但也可能让客户端库代码变得复杂很多。此外,还有大量工作投入到了细粒度 ACL 上。我无法想象为什么这会成为必要事项,或者为什么其优先级会高于多线程支持、更好的持久化模型、数据类型等其他变更。

— 用户评论结束 —

我感觉,把 ACLs(访问控制列表) 看作某种因 Redis Labs 的目标而强加的功能——因为“企业用户”之类的原因——的人恐怕不止她(或他,我也不确定)一个。评论中的其他几点也很有意思;我认为,非常有必要把这些问题都讲清楚,以便明确向 Redis 社区传达未来的发展方向。

为简单起见,我会把这篇博文分成几个部分,分别回应原评论中提到的每一项功能。

RESP3

正如我已经在本博客中写过的,RESP3 的目标实际上是简化客户端生态。希望每个客户端都会有一个较低层的实现,而不是试图重新发明某种高层接口:redis.call(“get”,”foo”)。现在已经不再需要协调各种转换,因为该协议已经具备足够的语义,能够告诉客户端调用者所看到的给定回复应该是什么样子;对于绝大多数命令,也不再需要事先知道命令的指纹。我认为,这位用户所指的是 RESP3 对带外通信的支持,也就是回复中的“属性”。

我确实相信,在 Redis 的未来发展中,client-side caching(客户端缓存) 将会非常重要。这是每个可扩展系统合乎逻辑的下一步。然而,如果没有服务器协助,客户端缓存失效处理将是一场噩梦。这正是 RESP3 主要支持在回复中携带属性的原因。不过,Redis 6 可能不会实现这些功能中的任何一个。将成为 Redis 6 的 Redis unstable 已经有了几乎完整的 RESP3 实现,但其中没有属性。实现 RESP3 的客户端只要愿意真正面向未来,就可以选择丢弃属性;而且,即使是未来版本的 Redis,如果用户没有启用某种特殊功能,很可能也根本不会发送属性。例如,对于客户端缓存,连接必须被置于某种特殊模式下。此外,正如你们所知,Redis 6 将完全向后兼容 RESP2。实际上,我开始相信 RESP2 支持永远都不会被移除,因为保留它几乎不需要额外成本;既然我们已经付出了实现 RESP2 与 RESP3 之间抽象层的努力,就没有充分理由再破坏向后兼容性。

通常情况下,没有充分理由我不喜欢改变东西,但 RESP2 的局限性对客户端生态产生了很大影响。我希望客户端生态能够达到这样的状态:用户从一个客户端转到另一个客户端时会感到熟悉,而 API 应该是 Redis API,而不是客户端作者自行发明的那一层接口。顺便说一句,我并不反对在较低层 API 之外再提供一个更高层的 API,但两者应该有共同基础,而且客户端应该能够在不了解这些命令任何信息的情况下发送命令。

ACLs

ACL 规范是我四年前亲自起草的。我等了这么长时间,才终于说服自己,现在确实是实现它的时候了:过去很长一段时间里,我们一直没有 ACL,只是使用各种技巧,主要是重命名命令。不过,不要以为 ACL 的主要动机是满足企业客户的安全需求。作为副作用,ACL 也能出于安全目的对用户进行身份验证,但这一功能的主要目标是运维。

让我举个例子。你有一个 Redis 实例,并计划用它来做一件新事情:延迟任务处理。你从互联网上找到了一个库,看起来运行良好。那么,为什么这样一个你无法逐行了解的库,竟然应该能够调用“FLUSHALL”并立即清空你的数据库?也许这个库的测试代码中就包含了这样的命令,而你意识到这一点时已经太晚了。或者,也许你刚刚雇用了一名初级开发者,而他一直在 Redis 实例上调用“KEYS *”,但你们公司的 Redis 策略明明是“禁止使用 KEYS 命令”。

再来看云服务提供商的场景:他们需要谨慎地重命名管理命令,甚至要防止这类命令因某种原因泄露出去。还可以使用更多技巧,例如让 MONITOR 的输出中不显示这些命令。借助 ACL,你可以对 Redis 进行配置,使默认用户在未通过某种身份验证时无法执行任何管理性或危险操作。我认为,这将极大改善运维工作。

此外,据我所知,ACL 也是我为 Redis 编写的最优秀的代码之一。除非使用键模式,否则它几乎完全没有 CPU 成本;即使使用键模式,成本也很小。整个实现完全封装在 acl.c 文件中,核心的其他部分只需调用几处 ACL API。由于它完全模块化,因此没有给系统增加复杂性。实际上,ACL 代码还促成了对 AUTH 命令的一些良好重构。

多线程

Redis 可能获得两种不同的多线程支持。我认为,这位用户所指的是类似 memcached 的多线程,也就是让单个 Redis 实例扩展到多个线程,从而提高它在 GET、SET 及其他简单命令上的每秒操作数。这需要让 I/O、命令解析等环节支持多线程。因此,我们把这种方式称为“I/O threading(I/O 多线程)”。

另一种多线程方式则是允许慢命令在不同的线程中执行,从而避免阻塞其他客户端。我们把这种线程模型称为“Slow commands threading(慢命令多线程)”。

那么,计划是这样的:据我所知,Redis 不会采用 I/O 多线程,因为经过仔细考虑后,我认为这会带来大量复杂性,却没有充分理由。实际上,许多 Redis 部署受限于网络或内存。此外,我确实信奉 share-nothing setup(无共享架构),所以我希望通过改进在同一主机上运行多个 Redis 实例的支持来扩展 Redis,尤其是通过 Redis Cluster。2019 年这方面会发生两件事:

A) Redis Cluster 中的多个实例将能够协同工作,合理使用本地实例的磁盘;也就是说,避免同时进行 AOF 重写。

B) 我们将作为 Redis 项目的一部分发布 Redis Cluster 代理,使用户能够在客户端没有良好的 Cluster 协议实现的情况下,对集群进行抽象。

还需要注意的是,Redis 不是 Memcached,但和 memcached 一样,它是一个内存系统。对于 memcached 这样数据模型非常简单的内存系统而言,实现多线程非常合理。一个多线程的磁盘存储系统则是必需的。而一个复杂的多线程内存系统处于两者之间,事情会变得棘手:Redis 的客户端彼此并不隔离,数据结构也很复杂。一个线程执行 LPUSH 时,还需要为执行 LPOP 的其他线程提供服务。这样做能获得的收益更少,却要增加大量复杂性。

相反,我真正非常希望实现的是慢操作多线程,而借助 Redis modules system(Redis 模块系统),我们已经朝着正确的方向前进了。不过,在未来(不确定是在 Redis 6 还是 7 中),我们会在模块系统中实现键级锁定,使线程能够完全获取某个键的控制权,以处理慢操作。现在,模块可以实现命令,并以完全分离的方式为客户端创建回复,但访问共享数据集时仍然需要全局锁;这一点将会改变。

更好的持久化

最近,我们投入了多项工作来改进 Redis 的这类基础功能。近期实现的最佳功能之一,是在 AOF 文件中加入 RDB 前导部分。此外,Redis 4 和 5 都在复制方面投入了大量工作,如今复制能力已经达到了与过去完全不同的水平。而且,是的,改进这些部分仍然是我的主要关注点之一。

数据结构

从 Redis 5 开始,Redis 现在拥有 Streams。对于 Redis 6 和 7,首先计划通过改变某些功能的实现方式,让现有功能更加节省内存。不过,添加新的数据结构需要考虑很多因素。为了在时间序列和流处理的场景下填补列表、发布/订阅和有序集合之间的空白,我花了好几年才想明白应该怎么做。我确实希望 Redis 是一组彼此正交的数据结构,用户可以将它们组合起来使用,而不是一组拿来即用的“工具”。Streams 是一种抽象日志,因此我认为它是非常有价值的补充。然而,其他东西是否值得放入核心,我还不能完全确定,除非经过非常长期的审慎考虑。无论如何,近些年来我们明显更加重视添加新的数据结构。HyperLogLogs、更高级的位操作、Streams、阻塞式有序集合操作(ZPOP* 和 BZPOP*)都是很好的例子。

结论

我认为,Redis 社区应该了解为什么要做某件事,以及为什么另一件事会被推迟。我犯的一个错误是过多通过 Twitter 进行沟通,好像所有人都在那里,但很多人其实有自己的生活 :-D,根本不关注 Twitter。博客是通知社区的更好方式,我需要抽时间多写博客。顺带一提,我很喜欢写文章,所以这是双赢。我希望大家意识到,Redis 并没有一份固定的路线图;这些年来我发现,机会驱动的开发远胜于制定路线图。有人提出了需求?我看到了必要性?我正好有兴趣编写它?现在正是合适的时机,因为没有其他更紧迫的大任务?还有一批用户正在帮助设计过程,提供提示、想法并测试功能?既然时机成熟,那就做吧。为 Redis 制定一份固定路线图很愚蠢,因为 OSS 核心团队规模很小,我有时会因为某个随机崩溃问题卡上几个星期……任何固定的长期计划都行不通。此外,随着 Redis 社区提供反馈,我的想法会发生很大变化,所以我每个月都得重写路线图。不过,写博客至少是展示当前优先事项和想法,并说明为什么放弃其他想法的好办法。

最后补充一点:在决定开源项目中要加入什么方面,我与 Redis Labs 之间拥有近乎无限的自由。我认为,这在业内多少算是个奇迹;或者,也许只是因为我在 Redis Labs 共事的人都是很友善的人,他们明白我们所做的一切源自开源运动,明智的做法是让它继续沿着这条道路发展。但这并不是普遍现象。如果 Redis 的路线图出了错,那肯定是我的错。