redis高级篇 redis的IO多路复用与Epoll函数

发布于:2024-10-09 ⋅ 阅读:(48) ⋅ 点赞:(0)

一  IO多路复用

1.1 IO多路复用*

1.1.1 IO多路复用

I/O: 网络I/O;

多路:多个客户端端连接(连接就是套接字描述符,即socket或channel),指的是多条TCP连接。

复用:用一个进程来处理多条的连接,使用单进程就能够实现同时处理多个客户端的连接。

Redis利用epoll函数来实现IO多路复用,将连接信息和事件放到队列中,依次放到文件事件分派器,事件分派器将事件分给事件处理器。

在多路复用IO模型中,会有一个内核线程不断地去轮询多个 socket 的状态,只有当真正读写事件发送时,才真正调用实际的IO读写操作。因为在多路复用IO模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有真正有读写事件进行时,才会使用IO资源,所以它大大减少来资源占用。

如下图:

1.1.2 IO多路复用流程(了解)

Redis服务采用Reactor的方式实现文件事件处理器。

所谓I/O多路复用机制,就是通过一种机制,可以监视多个描述符,一旦某个描述符就绪,能够通知程序进行相应的读写操作。这种机制的使用需要select、poll、epoll函数来配合。多个连接共同用一个阻塞对象,应用程序只需要在一个阻塞对象上等待,无需要阻塞等待所有连接,当某条连接有新的数据可以处理时,操作系统通知应用程序、线程,从阻塞状态返回,开始进行业务处理。

如下图:

1.2 同步&异步和阻塞&非阻塞

1.2.1 同步&异步

同步:调用者要一直等待调用结果的通知后,才能进行后续的执行;

异步被调用方先返回应答,让调用者先回去,然后再计算调用结果,计算完成最终结果后再通知并返回给调用方。

重点在于:同步&异步讨论对象是被调用者(服务提供者),重点在于获得服务提供者给予提供消息的通知方式上。

1.2.2 阻塞&非阻塞

阻塞调用方一直在等待而且别的事情什么都不做,当前进/线程会被挂起,啥都不干。

非阻塞:调用在发出去后,调用方向去忙别的事情,不会阻塞当前进/线程,而会立即返回。

重点在于:阻塞、非阻塞的讨论对象是调用者(服务请求者);重点强调等消息时候的行为,调用者是否能干其他事情。

1.2.3 场景案例

场景说明:

1)同步阻塞:服务员说快到你了,先别离开,我后台看一眼马上通知你。客户在海底捞火锅前台干等着,啥都不干。

2)同步非阻塞:服务员说快到你了,先别离开,客户在海底捞火锅前台边刷抖音等着叫号。

3)异步阻塞:服务员说还要在等等,你先去逛逛,一会通知你,客户怕过号,在海底捞火锅前台拿着排号小票,啥都不干,等着电源通知。

4)异步非阻塞:服务员说还要再等等,你先去逛逛,一会通知你,拿着排号小票+刷着抖音,等着电源通知。

 二   NIO

2.1 NIO的特性

在NIO模式中,一切都是非阻塞的。

accept()方法是非阻塞的,如果没有客户端连接,就返回无连接标识。

read()方法是非阻塞的,如果read()方法读取不到数据或返回空闲中标识,如果读取到数据时,只阻塞read()方法读取数据的时间。

在NIO模式中,只有一个线程;

当一个客户端与服务端进行连接,这个socket就会加入到一个数组中,隔一段时间听一次。看这个socket的read()方法能否读取到数据,这样一个线程就能处理多个客户端的连接和读取了。

三  IO多路复用例子

3.1  IO多路复用例子

模拟一个tcp服务端处理30个客户socket,一个监考老师考试多个学生:

第1种选择:按顺序逐个验收;如ABCD;

第2种选择:每一个用户对应一个线程/进程进行处理。

第3种选择:采用IO多路复用,在使用select、poll函数、epoll函数时阻塞,收发客户消息是不会阻塞的。如老师站在讲台上,这时C,D举手,表示他们答题完毕,老师依次检查C,D的答案。然后老师回讲台上进行等,然后去处理E和A.....

3.2  Reactor模式*

Reactor模式有2个关键组成:

1.reactor在一个单独的线程中运行,负责监听和分发事件,分发给适当的处理程序来对IO事件做出反应。例如公司接线员。

2.Handlers:处理程序执行I/O事件要完成的实际事件。例如公司实际干活的人。

四  多路复用底层涉及的函数

4.1 select函数

4.1.1 select函数特点

Select函数其实就是把NIO中用户态要遍历的fd数组拷贝到内核态;让内核态来遍历,因为用户态判断socket是否有数据,最后还是要调用内核态的。拷贝到内核态,不用一直在用户态和内核态频繁切换了。

从代码中可以看出,select系统调用后,返回了一个置位后的&rset,这样用户态只需要进行简短的二进制比较,就能很快知道哪些socket需要read数据,有效提高了效率。

Select方式,既做到了一个线程处理多个客户端连接,又减少了系统调用的开销(多个文件描述符只有一次select的系统调用+N此就绪状态的文件描述符的read系统调用) 

4.1.2 select函数缺点

1.bitmap最大1024位,一个进程最多只能处理1024个客户端

2.&rset不可重用,每次socket有数据就相应的位会被置位

3.文件描述符被拷贝到内核态,仍然又开销,select调用需要传入fd数组,需要拷贝一份到内核,高并发场景下,这种拷贝方式也是耗费资源惊人的。

4.select并没有通知用户态哪一个socket有数据,仍然需要0(n)的遍历,select仅仅返回可读文件描述符的个数,具体那个可读还是要用户自己遍历。

4.2 poll函数

4.2.1 poll函数流程

Poll的执行流程:

 1.将5个fd从用户态拷贝到内核态。

2.poll为阻塞的方法,执行poll方法,如果有数据会将fd对应的revents置为POLLIN

3.poll方法返回

4.循环遍历,查找哪个fd被置位为POLLIN了

5.将revents重置为0,便于复用

6.对置位的fd进行读取和处理

4.2.2 poll函数缺点

解决问题:

1.解决了bitmap的大小限制

2.解决了reset不可重用的情况

Poll函数解决了select缺点中的前两条,其本质原理还是select的方法,还存在select中原来的问题。

1.pollfds数组拷贝到了内核态,仍然有开销。

2.poll并没有通知用户态哪一个socket有数据,仍然需要O(n)的遍历。

 4.3 epoll函数

在多路复用IO模型中,会有一个内核线程不断地去轮询多个 socket 的状态,只有当真正读写事件发送时,才真正调用实际的IO读写操作。因为在多路复用IO模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有真正有读写事件进行时,才会使用IO资源,所以它大大减少来资源占用。

4.4 三个函数的区别

select

poll

epoll

操作方式

遍历

遍历

遍历

数据结构

Bitmap

数组

红黑树

最大连接数

1024

无上限

无上限

最大支持文件描述符号

一般有最大值限制

65535

65535

Fd拷贝

每次调用select,都需要把fd集合从用户态拷贝到内核态

每次调用poll,都需要把fd集合从用户态拷贝到内核态

Fd首次调用epoll_ctl拷贝,每次调用epoll_wait不靠边

时间复杂度

O(n)

o(n)

o(1)