Redis通过使用I/O多路复用程序来监听过个socket,文件事件处理器既实现高性能网络通讯模型,又可以很好的与Redis以单线程运行的模块进行对接,保持了Redis内部单线程设计的简单性。
文件事件处理器的四个部分:套接字、I/O多路复用程序、文件事件分发器、事件处理器
套接字socket
- 文件事件就是对套接字的抽象,每当一个套接字准备好执行连接、写入、读取、关闭等操作时,都会产生一个文件事件。
- 因为一个Redis服务器会有多个客户端连接,也就是说有多个套接字,所以多个文件事件可能会并发出现。
I/O多路复用程序
- I/O多路复用程序,负责监听多个套接字,按照处理顺序,将套接字存放在一个队列中。
- 套接字队列以有序(sequentially)、同步(synchronously)、每次一个套接字的方式向文件事件分派器传送套接字。
- 当上一个套接字产生的事件被处理完毕之后, I/O 多路复用程序才会继续向文件事件分派器传送下一个套接字。
Redis的I/O模型主要是基于epoll实现的,不过也提供了select和kquque的实现,默认是采用epoll
一个客户端连接后,将这个文件描述符(connfd)放到一个数组里。
select
select 是操作系统提供的系统调用函数,select 只能监听 1024 个文件描述符,通过它,我们可以把一个文件描述符的数组发给操作系统, 让操作系统去遍历,确定哪个文件描述符可以读写, 然后告诉我们去处理,不过,当 select 函数返回后,用户依然需要遍历刚刚提交给操作系统的 list。
只不过,操作系统会将准备就绪的文件描述符做上标识,用户层将不会再有无意义的系统调用开销。
poll
它和 select 的主要区别就是,去掉了 select 只能监听 1024 个文件描述符的限制。
epoll
epoll 是最终的大 boss,它解决了 select 和 poll 的一些问题。
所以 epoll 主要就是针对这三点进行了改进。
1. 内核中保存一份文件描述符集合,无需用户每次都重新传入,只需告诉内核修改的部分即可。
2. 内核不再通过轮询的方式找到就绪的文件描述符,而是通过异步 IO 事件唤醒。
3. 内核仅会将有 IO 事件的文件描述符返回给用户,用户也无需遍历整个文件描述符集合。
具体,操作系统提供了这三个函数。
第一步,创建一个 epoll 句柄
int epoll_create(int size);
第二步,向内核添加、修改或删除要监控的文件描述符。
int epoll_ctl( int epfd, int op, int fd, struct epoll_event *event);
第三步,类似发起了 select() 调用
int epoll_wait( int epfd, struct epoll_event *events, int max events, int timeout);
文件事件分发器
- 文件事件分派器接受I/O多路复用程序传递的套接字,根据套接字的事件类型,调用相应的事件处理器进行处理。
事件处理器
- 连接应答处理器:对连接服务器的各个客户端进行应答。
- 命令请求处理器:接收客户端传来的命令请求。
- 命令回复处理器:向客户端返回命令的执行结果
- 复制处理器:当主服务器和从服务器进行复制操作时, 主从服务器都需要关联此处理器。