Redis作为内存数据库,使用单线程模型设计,为什么能够抗住每秒几百万的播放量呢?同时,在Redis4.0之后的版本却抛弃了单线程模型这一设定,选择性使用多线程模型,这一看似有些矛盾的设计决策是今天需要讨论的另一个问题。
Redis作为一个内存服务器,需要处理很多来自外部的网络请求,它使用I/O多路复用机制同时监听多个文件描述符的可读和可写状态,一旦收到网络请求就会在内存中快速处理,由于绝大多数操作都是纯内存的,所以处理的速度会非常的快
在Redis4.0之后的版本,新版的Redis服务在执行一些命令时会使用主处理线程之外的其他线程,例如UNLINK
,FLUSHALL
,ASYNC
,FLUSHDB ASYNC
等非阻塞的删除操作
虽然Redis引入了多线程,但是是在部分命令上引入的,从整体架构上来看,主处理程序仍然是单线程模型
所以,两个问题为:
- 为什么Redis服务使用单线程模型处理绝大多数的网络操作
- 为什么Redis服务增加了多个非阻塞的删除操作
单线程模型
1、可维护
引入了多线程,增加了代码的复杂度和维护难度
2、并发处理
使用I/O多路复用机制并发处理来自客户端的多个连接,同时等待多个连接发送的请求
重要的函数调用就是 select以及类似函数,该方法的能够同时监控多个文件描述符(也就是客户端的连接)的可读可写情况,当其中的某些文件描述符可读或者可写时,select方法就会返回可读以及可写的文件描述符个数。
减少了系统的开销,不再需要额外创建和维护线程来监听来自客户端的大量连接
3、性能瓶颈
Redis并不是CPU密集型的服务,整个服务的瓶颈在于网络传输带来的延迟和等待客户端的数据传输,也就是网络 I/O,
AOF 是 Redis 的一种持久化机制,它会在每次收到来自客户端的写请求时,将其记录到日志中,每次 Redis 服务器启动时都会重放 AOF 日志构建原始的数据集,保证数据的持久性。
多线程虽然会更充分的利用CPU资源,但是操作系统上线程的切换会带来额外的开销
1、保存线程1的执行上下文
2、加载线程2的执行上下文
频繁的对线程的上下文进行切换可能还会导致性能地急剧下降,这可能会导致我们不仅没有提升请求处理的平均速度,反而进行了负优化,所以这也是为什么 Redis 对于使用多线程技术非常谨慎
引入多线程
eg 删除操作
对于Redis中的一些超大键值对,Redis可能会在释放内存空间上消耗较多的时间,这些操作会阻塞待处理的任务,影响Redis服务处理请求的PCT99和可用性
然而释放内存空间的工作其实可以由后台线程异步进行处理,这也就是 UNLINK 命令的实现原理,它只会将键从元数据中删除,真正的删除操作会在后台异步执行。
注
引入 IO 多路复用可以同时监听多个连接的可读和可写,不需要多个线程来做这个事情,引入了不必要的复杂性
I/O 多路复用的主要作用是让我们可以使用一个线程来监控多个连接是否可读或者可写,但是从网络另一头发的数据包需要先解序列化成 Redis 内部其他模块可以理解的命令,这个过程就是 Redis 6.0 引入多线程来并发处理的。
I/O 多路复用模块收到数据包之后将其丢给后面多个 I/O 线程进行解析,I/O 线程处理结束后,主线程会负责串行的执行这些命令,由于向客户端发回数据包的过程也是比较耗时的,所以执行之后的结果也会交给多个 I/O 线程发送回客户端。