参考:
《「面试突击」-Redis 篇》-Redis 的线程模型了解吗?为啥单线程效率还这么高? (juejin.cn)
石杉的架构笔记
Redis 内部使用 文件事件处理器(file event handler
)来处理客户端请求。
这个文件事件处理器是单线程的,因此 Redis 才叫做 单线程模型。
文件事件处理器 采用 I/O多路复用机制,同时监听多个 socket,将产生事件的 socket 压入 内存队列 中,I/O多路复用程序 每次从 内存队列 取出一个 socket 交给 事件分派器, 事件分派器 根据 socket 上的事件类型来选择对应的 事件处理器 进行处理。
整个过程都是单线程的。一个 socket 的事件处理完成之后,I/O多路复用程序才会去处理队列中的下一个 socket。
文件事件处理器 的结构包括 4 部分:
- I/O多路复用程序
- 内存队列
- 文件事件分派器
- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
1 五个重点概念
(1)I/O多路复用程序
负责监听客户端请求,每个客户端请求都是一个 socket,并将多个产生事件的 socket 压入内存队列中。
(2)内存队列
多个 socket 产生多个不同的事件,I/O多路复用程序会将这些 socket 压入内存队列中,文件事件分派器会从内存队列中一个一个的去取 socket 并分派给对应的事件处理器处理。
(3)文件事件分派器
负责从内存队列取出绑定了事件的 socket,并分配给对应的事件处理器来处理。
(4)事件处理器
事件处理器就是匹配对应的事件并处理 socket 的。
事件处理器分类:
- 连接应答处理器:处理 socket 连接的建立。
- 命令请求处理器:从 socket 中获取 Redis 命令信息。
- 命令回复处理器:向 socket 输出本次操作的结果。
(5)文件事件
-
当 socket 变得 可读 的时候(比如客户端对 redis 执行 write 操作,或者 close 操作),或者有新的可以应答的 socket 出现时(客户端对 redis 执行 connect 操作),socket 就会产生一个 AE_READABLE 事件。
-
当 socket 变得 可写 的时候(客户端对 redis 执行 read 操作),socket 会产生一个 AE_WRITABLE 事件。
I/O多路复用程序可以同时监听 AE_REABLE 和 AE_WRITABLE 两种事件,如果一个 Socket 同时产生了这两种事件,那么文件事件分派器优先处理 AE_READABLE 事件,然后才是 AE_WRITABLE 事件。
2 客户端与 Redis 通信的一次流程
如图(图片来源:石杉的架构笔记):
流程如下:
- ⾸先,redis 服务端进程初始化的时候,会将 server socket 的 AE_READABLE 事件与连接应答处理器关联。
- 客户端 socket01 向 redis 进程的 server socket 请求建⽴连接,此时 server socket 会产⽣⼀个 AE_READABLE 事件。IO 多路复⽤程序监听到 server socket 产⽣的事件后,将该 socket 压⼊队列中。⽂件事件分派器从队列中获取 socket,交给连接应答处理器。连接应答处理器会创建⼀个能与客户端通信的 socket01,并将该 socket01 的 AE_READABLE 事件与命令请求处理器关联。
- 假设此时客户端发送了⼀个
set key value
请求,此时 redis 中的 socket01 会产⽣AE_READABLE 事件,IO 多路复⽤程序将 socket01 压⼊队列,此时事件分派器从队列中获取到 socket01 产⽣的 AE_READABLE 事件,由于前⾯ socket01 的 AE_READABLE 事件已经与命令请求处理器关联,因此事件分派器将事件交给命令请求处理器来处理。命令请求处理器读取 socket01 的 key value 并在⾃⼰内存中完成 key value 的设置。操作完成后,它会将 socket01 的 AE_WRITABLE 事件与命令回复处理器关联。 - 如果此时客户端准备好接收返回结果了,那么 redis 中的 socket01 会产⽣⼀个 AE_WRITABLE 事件,同样压⼊队列中,事件分派器找到相关联的命令回复处理器,由命令回复处理器对 socket01 输出本次操作的⼀个结果,⽐如 ok ,之后解除 socket01 的 AE_WRITABLE 事件与命令回复处理器的关联。
3 为什么单线程模型的 Redis 也能有很高的效率?
主要有以下几个原因:
- 纯内存操作,响应时间大约 100 纳秒,非常快。
- 核心是基于 I/O多路复用机制,读写 I/O 不必再阻塞了,能够同时管理多个 socket 连接。
- Redis 是 C 语言实现的,执行效率高。
- 单线程模型避免了多线程模型中频繁的上下文切换问题,并且避免了多线程竞争资源产生的阻塞等待问题。
Redis 采用单线程模型,对于每个指令的执行时间是有要求的,如果某个指令执行时间过长,会造成其他指令阻塞,导致 Redis 服务器卡顿。所以 Redis 适合于那些需要快速执行操作的场景。