前言
在redis版本6之前,网络IO和键值对读写都是由一个线程来完成的。而redis的其他功能,比如持久化、异步删除、集群数据同步等,是由其他线程完成的。
为什么采用单线程
多线程有助于提升吞吐率(系统同时处理的请求数),但处理共享资源时,会带来额外的开销。设计有问题时,采用多线程甚至会造成性能下降。为了减少并发访问控制问题,redis直接采用单线程模式。
redis的大部分操作都是在内存上完成的,而且redis采用哈希表、跳表等性能良好的数据结构,以及多路复用机制,使得redis在单线程模式下也能实现高性能和高吞吐率。
linux的IO多路复用机制
Linux的IO多路复用机制指的是一个线程处理多个IO流,即select/epoll
机制。内核一直监听套接字的请求,一旦有相关请求,就交给redis处理,从而实现redis线程处理多个IO流的效果。
为了在请求到达时能通知到Redis线程,select/epoll
提供了基于事件的回调机制,即针对不同事件的发生,调用相应的处理函数。相关事件被放到一个事件队列,redis单线程对该事件队列不断处理,避免一直轮询是否有请求发生造成CPU资源浪费。因为redis一直在处理事件队列,所以能及时响应请求。
redis单线程处理IO请求瓶颈
- 任意一个请求在server中一旦耗时较长,就会影响整个server的性能,也就是说,后面的请求都要等前面的先处理完。耗时操作包括以下几种:
- 操作big key:写入一个big key在分配内存时需要耗费一定时间。删除一个big key去释放内存也需要一定时间。
- 操作大量数据的命令。类似于mysql的
select * from xxx
的操作会占用大量时间。 - 大量key集中过期。
- 淘汰策略。内存不够用时,每次写入都要淘汰一些key,淘汰会耗时较长。
- AOF开启always机制,每次写入都将操作进行刷盘,拖慢redis性能
- 主从全量同步生成RDB:虽然采用fork子进程生成快照,但fork的一瞬间也会阻塞整个线程,实例越大,阻塞越久。
- 并发量非常大时,虽然采用了IO多路复用,但读写客户端数据依旧是同步IO,只能单线程依次读取客户端数据,无法利用CPU的多核。
redis6.0的多线程
redis6.0引入了多线程,但只是多线程处理网络IO请求,对于读写命令,redis仍然使用单线程处理。redis 6.0中,多线程机制默认是关闭的,相关配置:
# 启用多线程 io-threads-do-reads yes # 设置线程数。官方建议小于主机CPU核数 io-threads 6
参考
- 极客时间 - Redis核心技术与实战