「这是我参与2022首次更文挑战的第27天,活动详情查看:2022首次更文挑战」
前言
Redis,在工作中我们经常使用到,这也决定了它是面试的常客。我们总说Redis是单线程的,其实这也没错也错了,因为Redis不仅仅是单线程,从4.0开始,Redis就引进了多线程的概念。
单线程
我们常说的Redis单线程其实是指事件处理上的。在了解事件处理上,先了解下redis的工作机制。
Redis服务器是一个事件驱动程序,服务器需要处理以下两类事件:
文件事件
:Redis服务器通过套接字与客户端(或者其他Redis服务器)进行连接,而文件事件就是服务器对套接字操作的抽象;服务器与客户端的通信会产生相应的文件事件,而服务器则通过监听并处理这些事件来完成一系列网络通信操作,比如连接accept
,read
,write
,close
。时间事件
:Redis服务器中的一些操作需要在给定的时间点执行,而时间事件就是服务器对这类定时操作的抽象,比如过期键清理
Redis将文件事件和时间事件进行抽象,时间轮训器会监听I/O事件表,一旦有文件事件就绪,Redis就会优先处理文件事件,接着处理时间事件。
在所有事件处理上,Redis都是以单线程
形式处理,所以说Redis是单线程的。
同时,Redis基于Reactor模式开发了自己的I/O事件处理器,也就是文件事件处理器,Redis在I/O事件处理上,采用了I/O多路复用技术,同时监听多个套接字,并为套接字关联不同的事件处理函数,通过一个线程实现了多客户端并发处理。
但是,实际上Redis也并不是单线程的,比如生成RDB文件,就会fork一个子进程来实现。
在4.0之前,Redis在处理客户端命名时都是以单线程形式运行的,期间不响应其他客户端请求;但是如果发生一条耗时很长的命令,如:删除一个大key,导致Redis服务器卡住了几秒,对负载高的缓存系统是一种灾难,因此,在4.0版本引入了Lazy Free机制
,将慢操作
异步化了。
I/O多路复用
- I/O :网络 I/O
- 多路:多个客户端连接(连接就是套接字描述符,即 socket 或者 channel)
- 复用:复用一个或几个线程。就是说一个或一组线程处理多个 TCP 连接,使用单进程就能够实现同时处理多个客户端的连接
就是说`一个服务端进程可以同时处理多个套接字描述符`
Lazy Free机制
将大键的删除操作异步化,采用非阻塞删除(对应命令UNLINK
),大键的空间回收交由单独线程实现,主线程只做关系解除,可以快速返回,继续处理其他事件,避免服务器长时间阻塞。
Redis6.0后
Redis在4.0版本引入了Lazy Free
,自此Redis有了一个Lazy Free
线程专门用于大键的回收,但是大家都知道,Redis的性能瓶颈并不在CPU上,而是在内存和网络上。所以6.0发布的多线程并未将事件处理改成多线程,而是在I/O上
。
如果把事件处理改成多线程,不但会导致锁竞争,而且会有频繁的上下文切换,即使用分段锁来减少竞争,对Redis内核也会有较大改动,性能也不一定有明显提升。
但是,6.0版本的多线程并非彻底的多线程,I/O线程
只能同时执行读或者同时执行写操作,期间事件处理线程
一直处于等待状态,并非流水线模型,有很多轮训等待开销。
在 Redis6.0 中,多线程机制默认是关闭的,需要在 redis.conf 中完成以下两个设置才能启用多线程。
-
设置 io-thread-do-reads 配置项为 yes,表示启用多线程。
io-threads-do-reads yes
-
设置线程个数
io-threads 6
⼀般来说,线程个数要小于 Redis 实例所在机器的 CPU 核数,例如,对于⼀个 4 核的机器来说,Redis 官⽅建议配置 2 个 或 3个IO 线程。 线程数并不是越大越好,
官方认为超过了8个基本就没什么意义了
。总结
Redis的单线程,主要是指
事件处理
上,但是Redis的其他功能,如持久化、异步删除
这些都是由额外的线程执行的。在Redis6.0版本的多线程并非彻底的多线程,I/O线程
只能同时执行读或者同时执行写操作。在开启多线程后,并不会存在线程并发的安全问题,因为Redis的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程顺序执行。