追求性能极致:Redis6.0的多线程模型

Redis系列1:深刻理解高性能Redis的本质Redis系列2:数据持久化提高可用性Redis系列3:高可用之主从架构Redis系列4:高可用之Sentinel(哨兵模式)Redis系列5:深入分析Cluster 集群模式
背景我们在第一篇《Redis系列1:深刻理解高性能Redis的本质》中就已经提到了,Redis 的网络 IO 以及键值对指令读写是由单个线程来执行的,避免了不必要的contextswitch和资源竞争 , 对于性能提升有很大的帮助 。而到了2020年的5月份,Redis官方 推出了 令人瞩目的 Redis 6.0,提出很多新特性,包含 多线程网络IO 的概念,如下:

追求性能极致:Redis6.0的多线程模型

文章插图
新特性内核优化应用优化其他ACL细粒度权限管控(包括ACL LOG)过期Key回收优化,增加配置参数新版本Module API全面支持SSL协议、并新增TSL协议客户端缓存(Client side caching)Resp3协议,兼容Resp2,更加简单、高效disque消息队列模块Redis-benchmark支持集群模式多线程处理网络 IO(Threaded I/O)优化了INFO命令,效率更高新增配置,支持Del命令如unlink执行Systemd支持重写Redis集群代理(Cluster proxy)优化阻塞命令 , 复杂度从O(n)到O(1)XINFO STREAM FULL流命令新增配置参数来删除用于在非持久性实例中进行复制的RDB文件支持linux/bsd系统的CPU和线程(包括子线程如aof、dbIO线程)亲和力绑定RDB加载速度优化CLIENT KILL USER username命令无磁盘复制副本(Diskless replication on replicas),从测试版优化,目前无磁盘复制在load rdb仍是测试版 。集群Slots命令优化Psync2优化,修复了5.0的链式复制不一致问题 。defrag优化,从试验版到正式版这其中比较引人注意的就是Threaded I/O和Client side caching这两项了 。这时候我们不免疑问,为什么6.0之前是单线程模式的,是基于什么考虑 。而现在为什么又要优化成 多线程网络IO模式,主要解决了哪些问题 ,带来了那些变化?这一篇咱们就详细就来聊下这个 Threaded I/O 。
6.0之前的单线程模式了解单线程模式之前,大家可以先回顾一下Redis系列第一篇 Redis系列1:深刻理解高性能Redis的本质。就会明白,Redis所谓的单线程并不是所有工作都是只有一个线程在执行,而是指Redis的网络IO和键值对读写是由一个线程来完成的,Redis在处理客户端的请求时包括获取 (socket 读)、解析、执行、内容返回 (socket 写) 等都由一个顺序串行的主线程处理 。这就是所谓的“单线程” 。这也是Redis对外提供键值存储服务的主要流程 。由于Redis在处理命令的时候是单线程作业的,所以会有一个Socket队列 , 每一个到达的服务端命令来了之后都不会马上被执行,而是进入队列 , 然后被线程的事件分发器逐个执行 。如下图:
追求性能极致:Redis6.0的多线程模型

文章插图
至于Redis的其他功能, 比如持久化、异步删除、集群数据同步等等,其实是由额外的线程执行的 。可以这么说,Redis工作线程是单线程的 。但是在4.0之后 , 对于整个Redis服务来说,还是多线程运作的 。
那么问题来了,6.0之前为什么要使用单线程,通过 Redis官方的文档,我们看到他们有给出了说明:
追求性能极致:Redis6.0的多线程模型

文章插图
  • 在使用 Redis 时,Redis 主要受限是在内存和网络上,CPU 几乎没有性能瓶颈的问题 。
  • 以Linux 系统为例子,在Linux系统上Redis 通过 pipelining 可以处理 100w 个请求每秒 , 而应用程序的计算复杂度主要是 O(N) 或 O(log(N)) ,不会消耗太多 CPU 。
  • 使用了单线程后,提高了可维护性 。多线程模型在某些方面表现优异,却增加了程序执行顺序的不确定性,并且带来了并发读写的一系列问题 , 增加了系统复杂度 。同时因为线程切换、加解锁 , 甚至死锁,造成一定的性能损耗 。
  • Redis 通过 AE 事件模型以及 IO 多路复用等技术,拥有超高的处理性能 , 因此没有使用多线程的必要 。
可以看出,Redis对CPU计算力的要求并不迫切,相反单线程机制让 Redis 内部实现的复杂度大大降低,同时降低了因为上下文切换和资源竞争造成的性能损耗 。那既然单线程这么好用 , 为什么要引入多线程模式 。
6.0之后的多线程主要解决什么问题我们知道 ,  近年来底层网络硬件性能越来越好,Redis 的性能瓶颈逐渐体现在网络 I/O 的读写上,单个线程处理网络 I/O 读写的速度跟不上底层网络硬件执行的速度 。从下图我们可以看到,Redis 在处理网络数据时 , 调用 epoll 的过程是阻塞的,这个过程会阻塞线程 。如果并发量很高,达到万级别的 QPS , 就会形成瓶颈,影响整体吞吐能力 。

推荐阅读