Redis的I/O多路复用模型有效的解决单线程的服务端,使用不阻塞方式处理多个client端请求问题。在看I/O多路复用知识之前,我们先来看看Redis是客服端怎么跟客服端建立连接的、单线程socket服务端为什么会存在I/O阻塞。

Redis客户端连接

Redis通过监听一个 TCP 端口或者 Unix socket 的方式来接收来自client端的连接,当一个连接建立后,Redis 内部会进行以下一些操作:

  • 首先,客户端 socket 会被设置为非阻塞模式,因为 Redis 在网络事件处理上采用的是非阻塞多路复用模型。

  • 然后为这个 socket 设置 TCP_NODELAY 属性,禁用 Nagle 算法。

  • 然后创建一个可读的文件事件用于监听这个客户端 socket 的数据发送。

阻塞I/O

在socket连接中,一个服务器进程和一个客户端进行通信时,当一个client端向服务端写数据时,如果client端没有发送数据,那么服务端的read将一直阻塞,直到客户端write发来数据。在一个客户和服务器通信时没什么问题,当多个客户 与 一个服务器通信时,就存在问题了。如下图,两个客服端同时连接一个服务端进行写数据的时序图。

从上图可以看出一个服务器进程和多个客户端进程通信的问题:

  • (1) client1和服务端建立连接后,服务端会一直阻塞于client1,直到client1客户端write发来数据才开始后面的操作。服务端阻塞期间,即使其他客服端client2的数据提前到来,也不能处理client2客服端的请求。

  • (2) 有一个严重的问题就是,如果客户端client1一直没有write数据到来,那么服务端service会一直阻塞,不能处理其他客户的服务。

上面就是Redis通过Unix socket 的方式来接收来自client端的连接存在的I/O阻塞问题,而 I/O 多路复用就是为了解决服务端一直阻塞等待某一个client的数据到来,即使其他client的数据提前到来,也不会被处理的问题。

I/O多路复用

为什么Redis中要使用 I/O 多路复用这种技术呢?因为Redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入 或 输出都是阻塞的,所以 I/O 操作在一般情况下往往不能直接返回,这会导致某一文件的 I/O 阻塞导,致整个进程无法对其它客户提供服务。而 I/O 多路复用就是为了解决这个问题而出现的。为了让单线程(进程)的服务端应用同时处理多个客户端的事件,Redis采用了IO多路复用机制。

这里“多路”指的是多个网络连接客户端,“复用”指的是复用同一个线程(单进程)。I/O 多路复用其实是使用一个线程来检查多个Socket的就绪状态,在单个线程中通过记录跟踪每一个socket(I/O流)的状态来管理处理多个I/O流。如下图是Redis的I/O多路复用模型:

#1.文件描述符(file descriptor):
    Linux 系统中,把一切都看做是文件,当进程打开现有文件或创建新文件时,内核向进程返回一个文件描述符。可以理解文件描述符是一个索引,这样,要操作文件的时候,我们直接找到索引就可以对其进行操作了。我们将这个索引叫做文件描述符(file descriptor),简称fd。

如上图对Redis的I/O多路复用模型进行一下描述说明:

  • (1)一个socket客户端与服务端连接时,会生成对应一个套接字描述符(套接字描述符是文件描述符的一种),每一个socket网络连接其实都对应一个文件描述符。

  • (2)多个客户端与服务端连接时,Redis使用 I/O多路复用程序 将客户端socket对应的FD注册到监听列表(一个队列)中,并同时监控多个文件描述符(fd)的读写情况。当客服端执行accept、read、write、close等操作命令时,I/O多路复用程序会将命令封装成一个事件,并绑定到对应的FD上。

  • (3)当socket有文件事件产生时,I/O 多路复用模块就会将那些产生了事件的套接字fd传送给文件事件分派器。

  • (4)文件事件分派器接收到I/O多路复用程序传来的套接字fd后,并根据套接字产生的事件类型,将套接字派发给相应的事件处理器来进行处理相关命令操作。

#例如:以Redis的I/O多路复用程序 epoll函数为例
    多个客户端连接服务端时,Redis会将客户端socket对应的fd注册进epoll,然后epoll同时监听多个文件描述符(FD)是否有数据到来,如果有数据来了就通知事件处理器赶紧处理,这样就不会存在服务端一直等待某个客户端给数据的情形。

#(I/O多路复用程序函数有select、poll、epoll、kqueue)
  • (5)整个文件事件处理器是在单线程上运行的,但是通过 I/O 多路复用模块的引入,实现了同时对多个 FD 读写的监控,当其中一个client端达到写或读的状态,文件事件处理器就马上执行,从而就不会出现I/O堵塞的问题,提高了网络通信的性能。
  • (6)如上图,Redis的I/O多路复用模式使用的是 Reactor设计模式的方式来实现。

总结:

  • (1) Redis的I/O多路复用程序函数有select、poll、epoll、kqueue。select 作为备选方案,由于其在使用时会扫描全部监听的文件描述符,并且只能同时服务 1024 个文件描述符,所以是备选方案。

  • (2) I/O 多路复用模型是利用select、poll、epoll函数可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉。当有一个或多个流有I/O事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll是只轮询那些真正发出了事件的流),依次顺序的处理就绪的流,这种做法就避免了大量无用的等待操作。

                                                                                                    2020年06月14日 晚 于北京记

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐