带你快速通关Redis网络模型
几种IO模型
IO分为内存IO,磁盘IO,网络IO。本文中的IO以网络IO为例
先来看IO模型的对比。
阻塞IO
单线程阻塞
当client1端向server端发起连接请求之后,必须等待server端请求处理完成之后才可以向下继续运行。
再有client2向server端发送连接请求,只能等待client1关闭之后,才能处理client2的连接。
多线程阻塞
当client1端向server端发起连接请求之后,必须等待server端请求处理完成并且返回数据之后才可以向下继续运行。
再有client2端向server端发起连接,server端会创建另外一个线程去处理client2的发送的连接请求。
此时,如果client越来越多,开启的线程也越来越多,并且每一个线程只能处理自己绑定的那一个客户端,程序也需要保存每一个客户端的连接信息。在很少几个客户端的情况下,效率可能会很高,但是从高并发的角度看,资源消耗太大。
非阻塞IO
当client1端向server端发起连接请求后,会不断的向server端轮询是否成功。这样的话,CPU持续空转,占用大量资源。

IO多路复用
IO多路复用的思想是,server端不再需要去轮询等待内核空间的数据,而是采用事件触发机制。用一个线程去轮询各个客户端,只要数据准备好,立马交由处理线程去执行。这里主要讲述Redis网络模型,这里先略过。
关于IO多路复用的实现有以下常见的几种select(),poll(),epoll(),kqueue()等,Redis会选择操作系统提供的最优的IO接口作为实现。
Redis统一封装了系统调用函数,这样只需要调用统一的API即可。


了解了IO模型之后,我们来了解一下高并发网络编程模型**Reactor **
Reactor模型
模型中的角色
Reactor:负责分发事件,类似于IO多路复用的select
Acceptor:处理客户端连接线程
Handler:处理读、写事件
单Reactor单线程模式
处理流程
一个Reactor负责监听Socket,一旦有客户端连接、与其他读写事件,Reactor将连接操作交予Acceptor线程处理,而其他读写事件则交由Handler处理。
优点
- 职责清晰,模块解耦简单,代码易编写
- 在流量不是特别大、业务处理比较快的时候系统可以有很好的表现,
缺点
- 连接处理和业务处理共用一个线程,无法充分利用CPU多核的优势。
- 当流量比较大、读写事件比较耗时情况下,容易导致系统出现性能瓶颈。
Redis 6.0 之前,采用的便是这种结构。Redis直接对内存进行操作,所以执行速度非常快,性能瓶颈并不在CPU上。
单Reactor多线程模式
这种模型相对单Reactor单线程模型,只是将业务逻辑的处理逻辑交给了一个线程池来处理。
处理流程
- Reactor监听连接事件、Socket事件,当有连接事件过来时交给Acceptor处理,当有Socket事件过来时交个对应的Handler处理。
- Handler完成读事件后,包装成一个任务对象,交给线程池来处理,把业务处理逻辑交给其他线程来处理。
优点
- 让主线程专注于通用事件的处理(连接、读、写),从职责上再进行细粒度的划分;
缺点
- 貌似这种模型已经很完美了,我们再思考下,如果客户端很多、流量特别大的时候,通用事件的处理(读、写)也可能会成为主线程的瓶颈,因为每次读、写操作都涉及系统调用。
多Reactor多线程模式
这种模型相对单Reactor多线程模型,只是将Scoket的读写处理从mainReactor中拎出来,交给subReactor线程来处理。
处理流程
-
mainReactor主线程负责连接事件的监听和处理,当Acceptor处理完连接过程后,主线程将连接分配给subReactor。
-
subReactor负责mainReactor分配过来的Socket的监听和处理,当有Socket事件过来时交个对应的Handler处理。
-
Handler完成读事件后,包装成一个任务对象,交给线程池来处理,把业务处理逻辑交给其他线程来处理。
优点
- 让主线程专注于连接事件的处理,子线程专注于读写事件,上进一步解耦;
- 利用CPU多核的优势。
从Reactor模型中我们可以总结出,要实现一个Reactor模型,必须要有的几个核心组件,事件,事件监听,事件分发,事件处理,事件捕获。
Redis的网络模型
概述
Redis基于Reactor模型实现了自己的事件驱动框架。Redis 的事件驱动框架定义了两类事件
文件事件
客户端发送的命令
时间事件
Redis 自身的周期性操作。
例如下面是文件事件的数据结构
/* File event structure */
typedef struct aeFileEvent {
int mask;
aeFileProc *rfileProc; //读事件处理函数指针
aeFileProc *wfileProc; //写事件处理函数指针
void *clientData; //客户端私有数据
} aeFileEvent;
模型图
IO多路复用程序负责各事件的监听(连接、读、写等),当有事件发生时,将对应事件放入事件有序队列中,由事件派发器根据事件类型来进行分发。
如果是连接事件,则分发至连接处理器,将连接的客户端与服务器进行绑定。如果是Redis命令则分发至命令请求处理器,命令处理完后产生命令回复事件,再由事件队列,到事件派发器,到命令回复处理器,回复客户端响应。
连接事件处理
-
Redis监听固定端口,并将连接事件交由连接处理器处理。
-
客户端发起连接,触发连接事件,IO多路复用程序将连接事件包装好后放入事件队列,然后由事件派发器分发给连接应答处理器。
-
连接应答处理器创建client对象以及事件对象,并产生ae_readable事件,和命令处理器关联,标识后续该事件是可读事件,也就是开始接收客户端的命令操作。
注意:上述过程都是主线程执行。
请求事件处理
-
客户端发起命令,IO多路复用程序监听到该事件后(读事件),将数据包装成事件丢到事件队列中(事件在上个流程中绑定了命令请求处理器)。有读写事件则先处理读事件,再处理写事件。
-
事件分发处理器根据事件类型,将事件派发给对应的命令请求处理器。
-
命令请求处理器,读取事件中的数据,执行命令,然后产生ae_writable事件,并绑定命令回复处理器。
-
IO多路复用程序监听到写事件后,将数据包装成事件丢到事件队列中,事件分发处理器根据事件类型分发至命令回复处理器。
-
命令回复处理器,将数据返回给客户端。
总结
Redis的网络编程模式,结合了IO多路复用以及高并发网络编程模式Reactor。这也是Redis为什么能这么快的核心原因之一。
转载自:https://juejin.cn/post/7244407068110995513