【译】antirez:Redis6将支持客户端缓存

本文翻译自Redis做者antirez的一篇博客,原文地址是:antirez.com/news/130redis

纽约Redis日已经结束了,我仍然与意大利时区同步,早上5点30起床,并当即走上了曼哈顿的街道,我很喜欢这里的风景,而且享受着成为这里的一部分。当时我正在考虑发布Redis 6的release版本,这是在将来一段时间最重要的事了。新版本的Redis协议(RESP3)推动得还很慢,若是没有一个好的理由,明智的人是不会更换工具的。但我为何要坚持提高协议呢?有两个主要的缘由,一是须要给客户端提供更加具备语义的回复,二是提供一个旧版本不能实现的新功能:客户端缓存。缓存

时间倒回一年前,我到达圣安东尼奥的Redis Conf 2018。当时公司就有一个共识是客户端缓存是Redis在将来很是重要的事情。若是咱们须要更快的存储和更快的缓存,咱们就须要在客户端存储一部分信息。这是提供低延迟和大规模数据服务的很天然的想法。事实上,基本上每一个大公司也都是这样作的,由于这是惟一的办法。然而Redis没有办法在这一过程当中协助客户。一个巧合让Ben Malec想要在Redis Conf上作一些关于客户端缓存的演讲,他只使用Redis提供的工具和一些很是聪明的想法。bash

做者注:演讲地址是https://www.youtube.com/watch?v=kliQLwSikO4服务器

Ben的演讲启发了我,为了实现Ben的设计,其中有两个关键点。第一个是使用Redis Cluster的“hash slot”的概念,把key分红了16k个组。采用这种方式使得客户端不须要追踪每个key的位置,可使用一个简单的元数据来定位key所在的group。Ben使用Pub/Sub模式来通知key的改变,因此他须要应用程序的一些帮助,然而这种模式是很固定的。要修改一个key?还须要发布失效消息。在客户端是否缓存了key呢?要记住缓存每一个key和收到失效消息时的时间戳,记住每一个slot的失效时间。当使用一个缓存的key时,先作一个懒清除,经过检查缓存key的时间戳是否早于slot收到失效信息的时间戳。这种状况下,这个key就是过期的数据,你能够再次访问服务器。函数

在看完演讲以后,我意识到这是一个在服务器内使用的好主意,为了让Redis可以为客户端作一部分工做,是客户端缓存更加简单高效,因此我回到家写下了个人设计文档:groups.google.com/d/msg/redis…工具

但为了实现个人设计,我必须专一于修改Redis协议使它变得更加完善,因此我开始编写RESP3和Redis 6的其余特性(好比ACL)的规范和代码,客户端缓存是Redis许多迭代想法中的一种,有些想法由于时间不够放弃了。google

当时我在纽约街头思考这个想法。后来和一些朋友去吃午餐喝咖啡。当我返回酒店房间后,距离次日起飞还有一整晚的时间,因此我开始按照一年前写的提案来写Redis 6的客户端缓存的实现。spa

Redis服务器助理客户端缓存,最终叫作“tracking”(我也可能改主意),是一个由几个关键想法组成的很是简单功能。线程

key空间被分割到”caching slots“,但他们比Ben使用的hash slots要多得多。咱们使用CRC64的24位输出,因此有超过1600万个不一样的slot。为何这么多呢?由于我认为你想要有一个1亿key的服务器。然而一个失效信息影响的key不该该多于客户端缓存中的key。Redis中失效表占用130M的内存:8字节的指针指向16M的条目。这对我来讲是能够接受的,若是你想要使用新功能,你将充分利用你在客户端的全部内存,因此使用130MB在服务器端是好的,你能够得到更细粒度的失效。翻译

客户端使用“opt in”方法开启这个功能,只须要一个简单的命令:

CLIENT TRACKING on
复制代码

服务器老是返回+OK,从这时起,每一个命令都在命令表中被标记为“只读”,再也不给调用者返回keys,并记住客户端请求的全部的key。保存这种信息时很是简单的,每一个Redis客户端都有本身的惟一ID,因此若是ID是123的客户端发送了MGET命令,须要从slot 1,2和5获取key,那么失效表中咱们就须要记录以下信息:

1 -> [123]
2 -> [123]
5 -> [123]
复制代码

接着,ID为444的客户端也须要到slot5请求key了,那么表信息将变成:

5 -> [123,444]
复制代码

如今其余客户端修改了slot 5中的某个key,Redis将会检查失效表,发现客户端123和444都缓存了这个slot上的key。咱们将会给这些客户端发送失效信息,而后会记录下slot最后的失效时间戳,并在之后懒检查缓存对象的时间戳,并对照后判断是否失效。此外,客户端能够回收表中缓存的指定slot的对象。这种具备24位hash函数的方法不是问题,由于咱们即便缓存几千万的key,也不会有很长的列表。发送了失效信息后,咱们就能够删除失效表中的项,这样直到这些客户端再也不读这些slot的key,咱们就再也不向他们发送失效消息。

须要注意的是,客户端没必要强制使用24位hash函数。也可能使用20位,而后移动Redis发送的失效消息的slot。不肯定是否有不少很好的理由这样作,可是内存受限时,这多是一种想法。

若是你密切关注我说的话,你会开始考虑同一链接既会接收到正常的客户端回复,又会接收失效消息。这能够经过RESP3实现,由于失效做为“推送”消息类型发送。若是客户端是一个阻塞类型的,而且不是事件驱动类型的客户端,就会变得比较复杂:

应用程序须要一些方法来不时读取新数据,这看起来既复杂又脆弱。在这种状况下,为了接收失效消息,使用另外一个应用程序线程和不一样的客户端可能会更好。因此你可使用如下命令来容许这样的操做:

CLIENT TRACKING on REDIRECT 1234
复制代码

基本上咱们能够说咱们使用当前链接得到的全部key,并但愿失效消息发送到客户端1234。在链接池的状况下多个客户端可能会要求将失效消息重定向到单个客户端。你须要作的就是建立特殊链接以接收失效消息,调用CLIENT ID以了解此客户端链接哪一个ID,而后启用跟踪。

如今只剩下一个问题了:若是咱们失去了失效链接怎么办?咱们可能由于不能接收到失效消息而陷入麻烦。一般,应用会检测链接,尝试重连,并清除缓存。为了确保失效链接处于链接状态,不时地向服务器发送ping请求多是一个更好的主意。然而,为了下降过时数据的风险,Redis也将开始通知客户端将失效消息重定向到其余客户端,只要使用特殊的推送消息:下一个请求就会使客户端知道链接已经断开。

我刚才描述的已经合并到Redis的unstable分支。可能不是最终的处理方法,可是在第一个Redis 6发布版本以前还有几个月的时间,咱们还有时间修改全部的事情:能够告诉我你的反馈。我也会再寻找其余RESP2可行的方法。这只有在重定向开启时才有效,而且客户端要进入Pub/Sub模式监听消息。经过这种方式,彻底能够复用旧客户端。

我但愿这足以刺激你的胃口:若是咱们在Redis中运行的很好,而后记录下来,让客户端做者知道该如何支持,数据可能比以往更接近应用程序,甚至在小型团队运行的应用程序中,到目前为止尚未尝试客户端缓存。对于正在准备作的大型团队和很是大的应用程序,下降实现成本和复杂性。

相关文章
相关标签/搜索