「如何实现流动式软件发布」线上课堂开课啦,快来报名参与课堂抽奖吧~ 了解详情
写点什么

Redis 6 的客户端缓存

2020 年 3 月 01 日

Redis 6 的客户端缓存

纽约 Redis 节结束了,我 5:30 起床,与意大利时区保持着很好的同步,并立即走上曼哈顿的街道,我完全爱上了这里的风景,享受着成为这里一份子的美妙感觉。然而,我还在思考着 Redis 6 的 release 版本,它是目前最重要的特性了,新版本的 Redis 协议(RESP3)推进的很慢,这是有充分理由的:聪明的人在没有充分理由的情况下是不会切换工具的。但是我为什么这么想改进协议呢?主要有两个原因,一是为客户端提供更多的语义应答,二是因为需要提供一个难以用旧协议实现的新特性,特别是一个对我来说最重要的特性:客户端缓存。


回到大约一年前,我参加了旧金山的 Redis Conf 2018,坚信客户端缓存是 Redis 未来最重要的特性。如果我们需要快速存储和快速缓存,那么我们需要在客户端存储部分信息。这是为大规模数据提供低时延服务很自然的想法。实际上,几乎所有的大公司都已经这样做了,因为这是最终唯一可行的途径。然而,Redis 没有在这个过程中帮助客户端。很幸运的是,Ben Malec 在 Redis Conf 上做了一个关于客户端缓存的演讲,仅仅使用了 Redis 提供的工具和一些非常聪明的想法。| 编者注:Ben Malec 的演讲视频见 https://www.youtube.com/watch?v=kliQLwSikO4


Ben 的方法给了我很大的启发。Ben 的设计中有两个关键的想法。第一个是使用 Redis 集群“哈希槽”的思想,将 key 分成 16k 个组。这样,客户端就不需要跟踪每个 key 的有效性,而是可以为一组 key 使用单个元数据项。Ben 使用了 Pub/Sub,在 key 更改时发送通知,因此他需要应用程序提供一些帮助,但是这种模式是非常固定的。要修改一个 key ?还要发布一条消息,使其失效。在客户端,是否缓存 key ?记住缓存每个 key 和收到失效消息的时间戳,记住每个槽的失效时间。当使用一个给定的缓存 key 时,通过检查你缓存 key 的时间戳是否比槽失效的时间戳早,来做一个懒驱逐:在这种情况下,这个 key 就是过期的数据,你就需要再次访问服务器。


看完这个演讲之后,我意识到这是一个能够用于服务器内的非常棒的想法,为了能让 Redis 为客户端做部分工作,并且让客户端缓存更加简单,更加高效。因此我回到家就写了一篇设计文档。| 文档 URL:https://groups.google.com/d/msg/redis-db/xfcnYkbutDw/kTwCozpBBwAJ


但是为了实现我的设计,我必须把 Redis 协议换成更好的,因此我开始编写 RESP3 的规范和代码,还有 Redis 6 的其他特性,如 ACL 等,于是客户端缓存就成为了我基于 Redis 产生的众多 idea 之一。


我在纽约的街头思考着这个想法。之后和朋友去吃了午饭喝了咖啡。当我回到酒店的时候,距离第二天坐飞机就只剩一个晚上了,因此我开始编写 Redis 6 的客户端缓存的实现。这个想法是我在一年前就提出来了,现在它仍然看起来很棒。


Redis server 的客户端缓存,最终叫”tracking”(我有可能还会改),是一个由几个关键想法组成的非常简单的特性。


键空间被划分为多个“缓存槽”,但是要比 Ben 使用的哈希槽要多很多。我们使用 CRC64 的 24 位输出,因此有超过 1600 万个不同的槽。为什么会有这么多呢?因为我认为你可能会在 server 中存储 1 亿多个 key,并且一个失效消息影响的 key 不应该比在客户端缓存中的 key 多。Redis 中获取失效表的内存开销是 130M:8 字节的指针数组指向 16M 的条目。这对我来说是可以接受的,如果你想要这个特性,你就要充分利用客户端的内存,因此使用 130M 在服务端侧是好的,你可以获得更加细粒度的失效。


客户端使用“optin”的方式开启这个特性,仅需一个简单的命令:


CLIENT TRACKING on


服务端回复 +OK,从这一时刻开始,在命令表中的每个命令都会被标记为“只读”,不再给调用者返回 keys,而是记住客户端请求的所有 key(但也只是使用了只读命令的 key,这是服务器和客户端之间的协议)。Redis 保存这种信息的方式非常简单。每个 Redis 客户端都有一个唯一的 ID,所以如果是客户端 ID 123 执行了一个 MGET 命令,需要从槽 1,2,5 中获取 keys,我们就会在失效表中记录以下条目:


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


但是之后客户端 ID 444 也会到槽 5 请求 keys,因此表就会变成:


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


现在一些其他的客户端修改了槽 5 中的某个 key。Redis 就会检查失效表,发现客户端 123 和 444 都缓存了这个槽上的 key。我们将会向两个客户端发送一条失效消息,然后他们可以用任意一种方式处理:要么记录最后一次槽失效的时间戳,然后懒检查缓存对象的时间戳(或者使用递增的“epoch”:这样会更安全),并在比较之后将其逐出。另外,客户端可以通过获取表直接回收缓存到特定槽中的对象。这种具有 24 位哈希函数的方法不是一个问题,因为我们即使缓存了成百上千万个 key,都不会有一个很长的列表。在发送失效消息后,我们就能把这些条目项从失效表中移除,这样直到这些客户端不再从槽中读取 key 时,我们才不再给他们发送失效消息。


需要注意的是客户端不必全部使用 24 位的哈希函数。他们也可能只使用 20 位的,然后只用移动 Redis 发送给他们的失效消息槽。虽然不确定这么做会不会不好,但是对于内存紧张的系统可能会有用。


如果你严格按照我说的去做,你会认为同一连接会同时收到正常的客户端响应和失效消息。对于 RESP3,这是可能的,因为失效消息是作为“push”消息类型发送的。但是,如果客户端是阻塞客户端,而不是事件驱动的客户端,那么情况将变得很复杂:应用程序需要某种方式不时读取新数据,这看起来复杂而脆弱。在这种情况下,最好使用另一个应用程序线程和另一个客户端连接,以接收失效消息。因此,您可以执行以下操作:


CLIENT TRACKING on REDIRECT 1234
复制代码


基本上,我们可以说通过当前连接获得的所有 key,我们希望将失效消息发送给客户端 1234。例如,在连接池的情况下,多个客户端可能会要求将失效消息重定向到单个客户端。你需要做的就是创建此特殊连接以接收失效消息,调用 CLIENT ID 来获取客户端 ID,然后开启追踪。


还有一个问题:如果我们从失效连接中丢失了服务器的连接,那会发生什么?一旦接收不到失效消息我们可能会遇到麻烦。通常应用会检测到连接中断,然后重新连接,并且刷新当前缓存(或采取更柔和的方法,比如把所有哈希槽的失效时间戳设置为几秒后,以便有时间填充缓存,代价就是提供了几秒的过期数据)。但是更好的办法是失效的线程不时地发送 ping 命令以确保它的状态是 active 的。然而为了降低过期数据的风险,Redis 也会使用特殊的 push 消息,通知其客户端将失效消息重定向到其他客户端,告诉它们当前连接已经断开,这样在下一个查询时,客户端就会收到该消息。


我描述的整个过程只是合入到 Redis unstable 分支的内容。这也许不是最终的过程,但是在我们发布 Redis 6 之前还有好几个月,因此还有时间来进行改变:发送反馈给我就行了。我也在想办法让 RESP2 也能支持该特性。这也只是在重定向开启的时候才起作用,监听消息的客户端进入 Pub/Sub 模式,才会发送 Pub/Sub 消息。这样旧客户端也可以使用该特性了。


我希望这能激发你的兴趣:如果客户端缓存执行的不错,我们会提供文档给客户端库开发者,让他们知道如何支持该特性,这样数据会离应用更近,甚至就在小团队支持的、避免实现客户端缓存的应用中。而对于已经实现客户端缓存的大团队和大型应用,也可以减少开销和实现的复杂性。


本文转载自 中间件小哥 公众号。


原文链接:https://mp.weixin.qq.com/s/4hpu4R-IG3CgLTi_U7rjig


2020 年 3 月 01 日 21:43679

评论

发布
暂无评论
发现更多内容

docker 安装consul

Shae

案例解析丨Spark Hive自定义函数应用

华为云开发者社区

spark UDF

鹰眼 | 分布式日志系统上云的架构和实践

小小的一朵云

大数据

基于electron桌面级脚手架的设计

久违

前端 设计 全栈

中途转行学Java,如何赛过科班生?你只需要做到这几点

小Q

Java 程序员 架构 技术 基础

记一次node项目重构改进

华为云开发者社区

Java 项目 方案

程序员快乐器之JAVA代码生成工具

Philips

敏捷开发 程序设计 软件架构 开发工具

大项目写代码写到晕头转向?敏捷多项目框架解君愁

Learun

敏捷开发 软件开发

ASP.NET Core 性能优化最佳实践

newbe36524

微服务 性能优化 .net core ASP.NET Core

java中实现List集合中对象元素按其属性的中文拼音排序

Shae

同城双活与异地多活架构分析

vivo互联网技术

架构 高可用 架构设计 高可用系统的架构

东方证券企业架构之技术架构转型实践

BoCloud博云

云计算 PaaS 容器云 博云 微服务治理

使用Valgrind调试Linux C++程序

Simon

c++ gdb Valgrind memcheck 内存泄漏

实践案例丨基于Raft协议的分布式数据库系统应用

华为云开发者社区

raft 华为云

神盾首创非对称联邦学习,深度保障数据隐私

小小的一朵云

大数据

从linux源码看epoll

无毁的湖光

Linux TCP Linux Kenel

想学习数据结构和算法,推荐给你 10 本优质书单

沉默王二

数据结构 算法 书单推荐

不想码代码,你还能做什么?(一)

技术管理Jo

项目管理 技术管理 PMO

USDT承兑商支付系统搭建,区块链支付平台开发

13823153121

Python中的with是测试常用到的资源打开利器

陈磊@Criss

极客大学-架构师训练营

9527

PB级大规模Elasticsearch集群运维与调优实践

小小的一朵云

大数据

碰撞率下降75%!Mobileye与所托瑞安宣布双方合作重大进展

最新动态

看动画学算法之:排序-快速排序

程序那些事

排序 快速排序 数据结构和算法 看动画学算法

创建spring boot starter

曾彪彪

Java spring Boot Starter

将DevOps视为哲学——实施DevOps的绝佳方式

禅道项目管理

DevOps 测试 开发 持续交付

5分钟带你掌握Makefile分析

华为云开发者社区

makefile 脚本

Elasticsearch索引容量管理实践

小小的一朵云

大数据

图计算黑科技:打开中文词嵌入训练实践新模式

小小的一朵云

大数据

Docker映射详解,没问题了!

程序员的时光

Docker

一定要写点什么?!

Redis 6 的客户端缓存-InfoQ