EchoDemo's Blogs

Redis中的I/O多路复用

人真正活过的那段生命仅仅是一小部分。其余的部分不能算是生命,仅仅是时间而已。—-伊坂幸太郎《死神的精确度》

在谈I/O多路复用机制之前,我们先来讲讲阻塞I/O和非阻塞I/O。

1、阻塞I/O

我们常用的IO操作(比如read和write)都是阻塞I/O,也就是说当你调用read时,如果没有收到数据的返回,那么线程或者进程就会被挂起,直到收到数据。阻塞的意思,就是一直等着。阻塞I/O就是等着数据返回,进行读写操作。通过应用的函数进行调用,但是内核一直没有返回,就一直等着。应用的函数长时间处于等待数据返回的状态,我们就称之为阻塞I/O。

2、非阻塞I/O

非阻塞IO是通过fcntl(POSIX)或ioctl(Unix)设为非阻塞模式。此时,当你调用read时,如果有数据收到,就返回数据;如果没有数据收到,就立刻返回一个错误。这样的话就不存在线程的阻塞了,但是你还是要不断的轮询来读取或写入。相当于你去查看有没有数据,告诉你没有,过一会再来吧!应用过一会再来问,有没有数据?没有数据,会有一个返回。但是依旧很不好。应用必须得过一会来一下,问问内核有木有数据啊。

3、I/O多路复用

这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。多路复用是指使用一个线程来检查多个Socket的就绪状态,比如调用select和poll函数,传入多个文件描述符(FileDescription,简称FD),如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)。什么意思呢?就是派一个代表,同时监听多个文件描述符是否有数据到来。等着等着,如果有数据,就告诉某某你的数据来啦!赶紧来处理吧。

我们先来看一张JDK的基于I/O多路复用技术的NIO实现的图片,如下:

"JDK的基于I/O多路复用技术的NIO实现"

从图片中我们可以看到,很多的网络连接都向Selector进行注册,Selector会对已经向它注册了的网络连接进行轮询监控,一旦发现某个网络连接有了数据的返回,便通过SelectKey通知相应的Channel。从而达到多路复用的目的。

Redis的I/O多路复用程序的所有功能都是通过包装常见的select、epoll、evport和kqueue这些I/O多路复用函数库来实现的,每个I/O多路复用函数库在Redis源码中都对应一个单独的文件,比如ae_select.c、ae_epoll.c、ae_kqueue.c,诸如此类。因为Redis为每个I/O多路复用函数库都实现了相同的API,所以I/O多路复用程序的底层实现是可以互换。下面是redis线程模型,如图所示:

"redis线程模型"

我们的redis-client在操作的时候,会产生具有不同事件类型的socket。在服务端,有一段I/0多路复用程序,将其置入队列之中。然后,文件事件分派器,依次去队列中取,转发到不同的事件处理器中。更详细的关于redis中的事件处理请参见我的这篇文章:Redis中的事件处理

🐶 您的支持将鼓励我继续创作 🐶
-------------本文结束感谢您的阅读-------------