Redis 6.0多线程探秘(上)

2021-04-25 00:00:00 集群 版本 线程 多线程 单线程

1老牌八股面试题

想必我们经常听到一个烂大街面试题:

Redis为什么选择单线程?

这种问法其实并不严谨,为啥这么说呢:

  • Redis的版本很多3.x、4.x、6.x,版本不同架构也是不同的,不限定版本问这种问题,是不是有点耍流氓。
  • 限定版本之后 比如4.x,严格意义来说Redis也不是单线程,而是负责处理客户端请求的线程是单线程。
  • 新版本的6.0版本,告别了大家印象中的单线程,用一种全新的多线程来解决问题。

你要是这么一回答,面试官估计都会想:

啊呀,碰到行家了,反正这个问题我也不太清楚,好好听下他咋解决吧!

Redis的版本迭代和里程碑

Redis从发布至今,已经有十余年的时光了,一直遵循着自己的命名规则:

  • 版本号第二位如果是奇数,则为非稳定版本 如2.7、2.9、3.1
  • 版本号第二位如果是偶数,则为稳定版本 如2.6、2.8、3.0、3.2
  • 当前奇数版本就是下一个稳定版本的开发版本,如2.9版本是3.0版本的开发版本

我们在生产环境一般都会选择稳定版本来部署,在每个大版本之间还会有若干个小版本,目前新的版本是6.2.2。

我们可以通过redis.io官网来下载自己感兴趣的版本进行源码阅读:

历史发布版本的源码:https://download.redis.io/releases/

其中有几个里程碑式的版本,需要我们了解下:

5.0版本是直接升级到6.0版本,对于这个激进的升级,antirez表现得很有信心和兴奋,所以时间发文来阐述6.0的一些重大功能"Redis 6.0.0 GA is out!":

http://antirez.com/news/132

注:GA是Generally Available的缩写,意思是开发团队认为该版本是稳定版。

扯了这么多,就是希望大家清楚一点,Redis是与时俱进的,千万不要以为Redis就是一直是那个单线程。

2不一样的Redis之父

我们常说字如其人,对于我们程序员来说,码如其人,也是十分贴切。

从多个历史版本中我们隐约可以感觉到Redis之父Antirez是个很特别的人。

大白你这说的不是废话嘛,毕竟是顶流扛把子程序员,怎么会轻易随波逐流。

还是举个实际的例子感受一样,什么叫总能搞点不一样的。

集群方案

Redis单机版出来之后,官方集群版迟迟没有发布,这个时候业界就出了一些集群方案,比较有代表性的是codis和Tweproxy,这两种方案都是中心化的方案以及中间层的思想。

由于市场的需求旺盛,这两种方案很快被很多公司应用到生产环境,然而官方集群方案却迟迟没有发布,这一等就是4年,直到2015年4月1号才发布。

同样地Antirez还是激动地发了一篇文章"Redis cluster, no longer vaporware."

http://antirez.com/news/79

备注:标题可以译为Redis集群不再是幻想。

在官方集群方案中采用了P2P模式去中心化的思想、借助slot来实现一致性哈希、以及gossip协议来实现集群通信,整体架构更加简洁。

3Redis 6.0多线程的神秘面纱

Redis作为内存型NoSQL可以说是高性能的代名词,生产环境中数万QPS都是家常便饭。

试想一下,Redis如何进一步来提高性能呢?这恐怕也是Redis之父苦苦思索的问题。

擒贼先擒王,要提高性能,就要看看是什么卡脖子了。

Redis的瓶颈是什么

通常来说多线程对于提高CPU利用率有重要作用,但是Redis对于提高CPU利用率并不感冒,在Redis看来如果要提高CPU利用率,那在一台机器部署多个实例就好了。

其实想想Redis之所以那么青睐单线程,肯定是尝到了单线程的甜头:

  • 业务模型简单,并发读写没问题
  • 单线程完全无锁化 不死锁无线程切换损耗,性能贼好
  • 处理底层复杂的数据结构时有线程安全做保证,十分放心
  • ......

其实在Redis 4.0就引入了多个线程来实现数据的异步删除等功能,但是其处理读写请求的仍然只有一个线程,所以仍然算是狭义上的单线程。

抛开CPU之后,影响Redis性能的地方主要就剩下:内存和网络IO

内存更多属于硬件范畴的东西,比如我们用容量更大、吞吐率更高的内存介质来进行优化,因此对于Redis来说可以优化的空间有限。

后Redis的瓶颈可以初步定为:网络IO。

Redis的基本架构

在优化网络IO之前,我们有必要回顾下Redis单线程整体架构:

Redis采用Reactor模式的网络模型,对于一个客户端请求,主线程负责一个完整的处理过程:

从socket中读取数据和往socket写数据都是比较耗时的网络IO操作,解析请求和内存交互耗时可能远小于IO操作。

对于这种问题,我们常见的解决方法是标准的多线程化:

该方案中工作线程的功能是一样的,MemCached就是采用这种方案,具体的流程:

Memcached采用 master-woker 模式进行工作,主线程采用 Libevent 监听和处理事件,主线程将连接请求封装为任务放到队列中,根据算法选择空闲的工作线程,相应的工作线程处理完成后通过soeket与客户端进行数据交互。

但是Redis 6.0的多线程并没有这么做。

Redis自己的多线程

单线程给Redis带来的好处,或许更大。

另外一点如果做成标准化的多线程,对于Redis来说可能更不好处理,因为多线程带来的线程安全问题和底层复杂的数据结构操作都十分棘手。

Redis 6.0将处理过程中耗时的Socket的读取、请求解析、写入单独外包出去,剩下的命令执行仍然由单线程来完成和内存的数据交互。

这样一来,网络IO操作就变成多线程化了,其他核心部分仍然是线程安全的,确实是个不错的折中办法。

画外音:Redis 6.0 将网络数据读写、请求协议解析通过多个IO线程的来处理 ,对于真正的命令执行来说,仍然使用主线程操作,真是个很特别的多线程啊!

4小结

本文多算个入门篇,关于Redis多线程的更大细节,我们下期再搞。

原文链接:https://mp.weixin.qq.com/s/KQ_j4-3BP32aBH2-XOIw9Q


相关文章