Redis 官方在 2020 年 5 月份正式推出了 6.0 版本,这个版本中有很多的新特性,分别是面向网络处理的多 IO 线程、客户端缓存、细粒度的权限控制,以及 RESP 3 协议的使用。
在 Redis 6.0 中,非常受关注的第一个新特性就是多线程。因为 Redis 一直被大家熟知的就是它的单线程架构,虽然有些命令操作可以用后台线程或子进程执行(比如数据删除、快照生成、AOF 重写),但是从网络 IO 处理到实际的读写命令处理,都是由单线程完成的。
随着网络硬件的性能提升,Redis 的性能瓶颈有时会出现在网络 IO 的处理上,单个主线程处理网络请求的速度跟不上底层网络硬件的速度。
应对这个问题有两种方法:
在 Redis 6.0 中,主线程和 IO 线程协作完成请求处理的原理,把主线程和多 IO 线程的协作分成四个阶段:
服务端和客户端建立 Socket 连接,并分配处理线程:
首先,主线程负责接收建立连接请求。当有客户端请求和实例建立 Socket 连接时,主线程会创建和客户端的连接,并把 Socket 放入全局等待队列中。紧接着主线程通过轮询方法 把 Socket 连接分配给 IO 线程。
IO 线程读取并解析请求:
主线程一旦把 Socket 分配给 IO 线程,就会进入阻塞状态,等待 IO 线程完成客户端请求读取和解析。因为有多个 IO 线程在并行处理,所以这个过程很快就可以完成。
主线程执行请求操作:
等到 IO 线程解析完请求,主线程还是会以单线程的方式执行这些命令操作。
IO 线程回写 Socket 和主线程清空全局队列:
当主线程执行完请求操作后,会把需要返回的结果写入缓冲区,然后主线程会阻塞等待 IO 线程把这些结果回写到 Socket 中,并返回给客户端。和 IO 线程读取和解析请求一样,IO 线程回写 Socket 时,也是有多个线程在并发执行, 所以回写 Socket 的速度也很快。等到 IO 线程回写 Socket 完毕,主线程会清空全局队列,等待客户端的后续请求。
下图展示了这个阶段主线程和 IO 线程的操作,你可以看下。
在 Redis 6.0 中, 多线程机制默认是关闭的,如果需要使用多线程功能,需要在 redis.conf 中完成两个设置:
io-threads-do-reads yes
io-threads 6
如果在实际应用中,发现 Redis 实例的 CPU 开销不大,吞吐量却没有提升,可以考虑使用 Redis 6.0 的多线程机制,加速网络处理,进而提升实例的吞吐量。
Redis 6.0 新增了一个重要的特性,实现了服务端协助的客户端缓存功能,也称为跟踪(Tracking)功能。有了这个功能,业务应用中的 Redis 客户端可以把读取的数据缓存在业务应用本地了,应用就可以直接在本地快速读取数据了。
不过当把数据缓存在客户端本地会面临一个问题:如果数据被修改了或是失效了,如何通知客户端对缓存的数据做失效处理?
6.0 实现的 Tracking 功能实现了两种模式来解决这个问题:
CLIENT TRACKING ON|OFF
CLIENT TRACKING ON BCAST PREFIX user
不过普通模式和广播模式,需要客户端使用 RESP 3 协议,RESP 3 协议是 6.0 新启用的通信协议。
使用 RESP 2 协议的客户端需要使用另一种模式,也就是重定向模式 (redirect)。在重定向模式下,想要获得失效消息通知的客户端,就需要执行订阅命令 SUBSCRIBE,专门订阅用于发送失效消息的频道 redis:invalidate。同时再使用另外一个客户端,执行 CLIENT TRACKING 命令,设置服务端将失效消息转发给使用 RESP 2 协议的客户端。
例如,使用 RESP 2 协议的客户端接受失效消息。假设客户端 B 想要获取失效消息,但是客户端 B 只支持 RESP 2 协议,客户端 A 支持 RESP 3 协议。可以分别在客户端 B 和 A 上执行 SUBSCRIBE 和 CLIENT TRACKING,如下所示:
//客户端B执行,客户端B的ID号是303 SUBSCRIBE _redis_:invalidate //客户端A执行 CLIENT TRACKING ON BCAST REDIRECT 303
这样设置以后,如果有键值对被修改了,客户端 B 就可以通过 redis:invalidate 频道, 获得失效消息了。
Redis 6.0 提供了实例的访问权限控制列表功能(Access Control List,ACL),提升 Redis 的使用安全性
在 Redis 6.0 版本之前,要想实现实例的安全访问,只能通过设置密码来控制,例如,客户端连接实例前需要输入密码。对于一些高风险的命令(例如 KEYS、FLUSHDB、FLUSHALL 等),在 Redis 6.0 之前,也只能通过 rename-command 来重新命名这些命令,避免客户端直接调用。
Redis 6.0 提供了更加细粒度的访问权限控制,有两方面的体现:
ACL SETUSER normaluser on > abc
假设要设置用户 normaluser 只能调用 Hash 类型的命令操作,而不能调用 String 类型的命令操作,执行如下命令:
ACL SETUSER normaluser +@hash -@string
6.0 版本还支持以 key 为粒度设置访问权限,使用波浪号“~”和 key 的前缀来表示控制访问的 key。执行下面命令,设置用户 normaluser 只能对以“user:”为前缀的 key 进行命令操作:
ACL SETUSER normaluser ~user:* +@all
Redis 6.0 可以设置不同用户来访问实例,而且可以基于用户和 key 的粒度,设置某个用户对某些 key 允许或禁止执行的命令操作。在有多用户的 Redis 应用场景下,非常方便和灵活地为不同用户设置不同级别的命令操作权限,这对于提供安全的 Redis 访问非常有帮助。
Redis 6.0 实现了 RESP 3 通信协议,之前都是使用的 RESP 2。在 RESP 2 中,客户端和服务器端的通信内容都是以字节数组形式进行编码的,客户端需要根据操作的命令或是数据类型自行对传输的数据进行解码,增加了客户端开发复杂度。
RESP 3 直接支持多种数据类型的区分编码,包括空值、浮点数、布尔值、有序的字典集合、无序的集合等。
区分编码指直接通过不同的开头字符,区分不同的数据类型,这样客户端就可以直接通过判断传递消息的开头字符,来实现数据转换操作了,提升了客户端的效率。除此之外,RESP 3 协议还可以支持客户端以普通模式和广播模式实现客户端缓存。
Redis 6.0 的新特性:
Redis 6.0 新的功能特性需要在实际应用中进行部署和验证,所以如果想试用 Redis 6.0,可以尝试先在非核心业务上使用 Redis 6.0,一方面可以验证新特性带来的性能或功能优势,另一方面,也可以避免因为新特性不稳定而导致核心业务受到影响。