一位同事面试腾讯时,面试问了这么一个问题:Redis为什么又采用多线程了,不是一直单线程的?,听到这个问题第一时间脑子有点懵,他一直没有注意这个问题,导致回答不上来。
接下来我们就以这个问题展开讲解,Redis是目前使用非常广泛的一个内存数据库,在各个场景中都有着非常丰富的应用,Redis 6.0 之后的版本抛弃了单线程模型,原本使用单线程运行的 Redis 也开始选择性使用多线程模型,就算Redis的作者多牛,也逃不过“真香定律”!
对于这个问题可以拆分成以下两个问题进行解答:
Redis 作为一个成熟的分布式缓存框架,它由很多个模块组成,如网络请求模块、索引模块、存储模块、高可用集群支撑模块、数据操作模块等。
在很多人的理解当中认为 Redis 是单线程的,认为 Redis 中所有模块的操作都是单线程的,其实不对。我们说的 Redis 单线程指的是网络IO和键值对读写是由一个线程完成的,也就是说,Redis中只有网络请求模块和数据操作模块是单线程的。而其他的如持久化存储模块、集群支撑模块等是多线程的。
早在 Redis4.0版本的时候就已经对部分命令做了多线程化。
那么,为什么网络操作模块和数据存储模块最初并没有使用多线程呢?因为:“没必要!”
因为Redis是一个基于内存的数据库,还要处理大量的外部的网络请求,这就不可避免的要进行多次IO。好在Redis使用了很多优秀的机制来保证了它的高效率。那么为什么Redis要设计成单线程模式的呢?
基于上面几点可以总结为:基于内存而且使用多路复用技术,单线程速度很快,又保证了多线程的特点。因为没有必要使用多线程。
首先,我们可以肯定的说,Redis 不需要提升 CPU 利用率,因为 Redis 的操作基本都是基于内存的,CPU 资源根本就不是 Redis 的性能瓶颈。
虽然说采用多线程可以帮助我们提升 CPU 和 I/O 的利用率,但是多线程带来的并发问题也给这些语言和框架带来了更多的复杂性。而且,多线程模型中,多个线程的互相切换也会带来一定的性能开销。所以,在提升 I/O 利用率这个方面上,Redis 并没有采用多线程技术,而是选择了多路复用 I/O 技术。
所以,通过多线程技术来提升 Redis 的 CPU 利用率这一点是完全没必要的。
Redis 并不是完全单线程的,只是有关键的网络 IO 和键值对读写是由一个线程完成的,这点我们需要记住一下
2020年5月份,Redis正式推出了6.0版本,这个版本中有很多重要的新特性,其中多线程特性引起了广泛关注。
大家会有一个疑问:
上面不是说 Redis 号称单线程也有很高的性能么?
不是说多路复用技术已经大大的提升了 IO 利用率了么,为啥还需要多线程?
Redis 将所有数据放在内存中,内存的响应时长大约为100纳秒,对于小数据包,Redis 服务器可以处理80,000到100,000 QPS,这也是Redis处理的极限了,对于80%的公司来说,单线程的Redis已经足够使用了。
但随着越来越复杂的业务场景,有些公司动不动就上亿的交易量,因此需要更大的QPS。
常见的解决方案是在分布式架构中对数据进行分区并采用多个服务器,但该方案有非常大的缺点,例如要管理的Redis服务器太多,维护代价大;某些适用于单个 Redis 服务器的命令不适用于数据分区;数据分区无法解决热点读/写问题;数据偏斜,重新分配和放大/缩小变得更加复杂等等。
虽然现在很多服务器都是多个 CPU 核的,但是对于Redis来说,因为使用了单线程,在一次数据操作的过程中,有大量的 CPU 时间片是耗费在了网络 IO 的同步处理上的,并没有充分的发挥出多核的优势。
如果能采用多线程,使得网络处理的请求并发进行,就可以大大的提升性能。多线程除了可以减少由于网络 I/O 等待造成的影响,还可以充分利用 CPU 的多核优势。
Redis6.0引入的多线程部分,实际上只是用来处理网络数据的读写和协议解析,执行命令仍然是单一工作线程。
从上图我们可以看到 Redis 在处理网络数据时,调用epoll的过程是阻塞的,也就是说这个过程会阻塞线程,如果并发量很高,达到几万的QPS,此处可能会成为瓶颈。一般我们遇到此类网络 IO 瓶颈的问题,可以增加线程数来解决。开启多线程除了可以减少由于网络 I/O 等待造成的影响,还可以充分利用 CPU 的多核优势。Redis6.0 也不例外,在此处增加了多线程来处理网络数据,以此来提高 Redis的 吞吐量。当然相关的命令处理还是单线程运行,不存在多线程下并发访问带来的种种问题。