1 单线程模型设计
我们通常说Redis是单线程,主要指Redis的网络I/O和KV对读写是由一个线程完成,是Redis对外提供KV存储服务的主要流程。
但Redis其它功能如持久化、异步删除、集群数据同步等,是由额外线程执行的。
所以,严格来说,Redis并不是单线程,但一般把Redis称为单线程高性能,显得像 UC 编辑部。所以都说Redis是单线程模式。
为何单线程模型
要弄明白这个问题,需研究Redis的单线程设计机制以及多路复用机制。
调优Redis性能时,也能针对性避免会导致Redis单线程阻塞的操作,例如执行复杂度高命令。
多线程的开销
“使用多线程,可增加系统吞吐率或增加系统扩展性”。
一个多线程系统,在合理资源分配时,可增加系统中处理请求操作的资源实体,进而提升系统能够同时处理的请求数,即吞吐率。
左图是我们采用多线程时所期待的结果。
但通常情况用多线程后,若无良好系统设计,实际得到的结果,其实是右图那样。为什么会这样?‘
核心瓶颈在于,系统通常会存在被多线程同时访问的共享资源,如一个共享的数据结构。当多线程修改共享资源时,为保证共享资源正确性,就需额外机制保证,就直接带来额外开销。
比如Redis有List数据类型,并提供出队(LPOP)和入队(LPUSH)操作。假设Redis采用多线程设计,现有两个线程A、B:
A对一个List做LPUSH操作,并对队列长度加1
同时,线程B对该List执行LPOP操作,并对队列长度减1
为保证队列长度正确性,Redis要让线程A和B的LPUSH和LPOP串行执行,这样Redis可无误地记录它们对List长度修改。否则,可能得到错误结果。
这就是多线程编程面临的共享资源的并发访问控制问题。
如果无精心设计,比如只是简单采用一个粗粒度的互斥锁,就会出现不理想结果:即使增加了线程,大部分线程也在等待获取访问共享资源的互斥锁,并行变串行,系统吞吐率并没有随着线程的增加而增加。
也会引入同步原语保护共享资源的并发访问,降低系统代码的易调试性和可维护性。
为避免这些Redis直接单线程模式。
讲到这里,你应该已经明白了“Redis为什么用单线程”,那么,接下来,我们就来看看,为什么单线程Redis能获得高性能。
纯内存操作
基于非阻塞的IO多路复用机制
避免了多线程的频繁上下文切换
2 文件事件处理器
Redis 基于 Reactor 模式开发了自己的网络事件处理器 - 文件事件处理器(file event handler,后文简称为 FEH
),而该处理器又是单线程的,所以redis设计为单线程模型。
采用I/O多路复用同时监听多个socket,根据socket当前执行的事件来为 socket 选择对应的事件处理器。
当被监听的socket准备好执行accept、read、write、close等操作时,和操作对应的文件事件就会产生,这时FEH就会调用socket之前关联好的事件处理器来处理对应事件。
所以虽然FEH是单线程运行,但通过I/O多路复用监听多个socket,不仅实现高性能的网络通信模型,又能和 Redis 服务器中其它同样单线程运行的模块交互,保证了Redis内部单线程模型的简洁设计。
下面讲讲文件事件处理器的几个组成部分。