优质博文:IT-BLOG-CN
一、单线程的优缺点
对于一个请求操作Redis
主要做3
件事情:从客户端读取数据/解析、执行Redis
命令、回写数据给客户端。所以主线程其实就是把所有操作的这3
件事情串行一起执行,因为是基于内存,所以执行速度非常快。
优点&缺点:
【1】优点:不存在锁的竞争问题和避免线程间CPU
的切换。
【2】缺点:单线程无法利用多CPU
和串行操作,某个操作“出问题”会“阻塞”后续操作。
Redis6.0
之前为什么一直不使用多线程?
使用Redis
时,几乎不存在CPU
成为瓶颈的情况,Redis
主要受限于内存和网络。例如:在一个普通的Linux
系统上,Redis
通过使用pipelining
每秒可以处理100W
个请求,如果应该程序主要使用O(N)
或O(log(N))
的命令,它几乎不会占用太多的CPU
。如果要用到多核CPU
,可以搭建多个Redis
实例来解决。
为什么说Redis
的瓶颈不在CPU
?
Redis
绝大部分操作是基于内存的,而且是存KV(key-value)
操作,命令执行的速度非常快。我们可以这么理解:Redis
的数据存储在一个大的HashMap
中,而HashMap
的优势就是查找和写入的时间复杂度都是O(1)
,Redis
内部采用这种结构存储数据,就奠定了Redis
高性能的基础。
二、Redis 的多线程
Redis
基于内存操作,内存的响应时长大约为100
纳秒,单线程的Redis
处理数据的极限是80,000
到100,000 QPS
,对于80%
的公司来说,单线程的Redis
已经足够使用了。
因为网络I/O
在Redis
执行期间占用了大部分CPU
时间,所以把网络I/O
部分单独抽离出来,做成多线程的方式。这里所说的多线程,其实就是将Redis
单线程中做的这两件事情“从客户端读取数据、回写数据给客户端”(也可以称为网络I/O
),处理成多线程的方式,但是“执行Redis
命令”还是在主线程中串行执行,这个逻辑保持不变。
主线程和多个I/O
线程,都同时处理图中的“队列”,是不是会存在锁竞争的关系尼?这里有个巧妙的设计,就是当epoll
获取socket
链接时,会将该事件先全部扔进队列中,比如扔了N
个事件,这时主线程就会处于忙碌状态。然后多个I/O
线程开始去并行进行网络I/O
,并对数据进行协议解析,当队列全部处理完毕后,主线程会对队列中请求串行“执行Redis
命令”,然后清空该队列。所以整个执行流程总结下来:主线程执行请求入队列 -> I/O
线程并行进行网络读 -> 主线程串行执行Redis
命令 -> I/O
线程并行进行网络写 ->主线程清空队列,并接收下一批请求。
Redis 6.0
的多线程是禁用的,默认使用是主线程。官方建议:只在机器至少有4
个内核时才启用多线程模型,且至少留下一个备用内核。如果需要开启多线程需修改redis.conf
配置文件:
io-threads-do-reads yes
开启多线程后,还需要设置线程数,否则是不生效的。同样修改redis.conf
配置文件:
io-threads 3
Redis
官方建议:只在机器至少有4个内核时才启用多线程模型,且至少留下一个备用内核。4
核的机器建议设置为2
或3
个线程,8
核的建议设置为6
个线程,线程数一定要小于机器核数。还需要注意的是,线程数并不是越大越好,官方认为超过了8
个基本就没什么意义了。
三、Redis6 多线程原理解析
近年来底层网络硬件性能越来越好,Redis
的性能瓶颈逐渐体现在网络I/O
的读写上,单个线程处理网络I/O
读写的速度跟不上底层网络硬件执行的速度。
Redis6.0
多线程是把主线程处理网络IO
和协议解析这两件事给了一组独立的线程处理,使得多个socket
读写可以并⾏化,但Redis
命令还是主线程串⾏执⾏。
主要流程如下:
【1】主线程负责接收并建立(多个)连接请求,获取socket
后放入全局等待处理队列;
【2】主线程处理完这些事件之后,通过RR
(Round Robin
轮询)将可读socket
分配给这些IO
线程;
【3】主线程阻塞,等待IO
线程完成命令的读取、解析;
继续使⽤单线程执⾏读写命令,不需要为了保证
Lua
脚本、事务、等开发多线程安全机制,实现更简单。
【4】主线程执⾏IO
线程读取和解析出来的Redis
请求命令,并将结果写到输出缓冲区;
【5】主线程阻塞,等待IO
线程将命令执⾏结果写回socket
(客户端);
【6】主线程执行所有命令并清空整个等待队列,等待客户端后续的请求队列;
三、性能对比
压测配置:
Redis Server
: 阿里云Ubuntu 18.04,8 CPU 2.5 GHZ
, 8G
内存,主机型号ecs.ic5.2xlarge
Redis Benchmark Client
: 阿里云Ubuntu 18.04,8 2.5 GHZ CPU
, 8G
内存,主机型号ecs.ic5.2xlarge
压测命令: redis-benchmark -h 192.168.0.49 -a foobared -t set,get -n 1000000 -r 100000000 --threads 4 -d ${datasize} -c 256
从上面可以看到GET/SET
命令在4
线程IO
时性能相比单线程是几乎是翻倍了。另外,这些数据只是为了简单验证多线程IO
是否真正带来性能优化,并没有针对严谨的延时控制和不同并发的场景进行压测。数据仅供验证参考而不能作为线上指标,且只是目前的unstble
分支的性能,不排除后续发布的正式版本的性能会更好。