IO多路复用
基本概念
IO多路复用指用一个线程来处理多个客户端请求
Epoll是一种IO事件通知机制
IO输入输出对象可以是 文件,网络,管道等用文件描述符fd表示的
事件Event分为可读事件和可写事件,有两种触发机制(水平触发和边缘触发)
水平触发机制:只要fd关联的内核缓冲区非空/非满,数据就可以一直读/写,就可以一直发出可读/可写信号进行通知
Epoll Api接口介绍
注:下列Api都是在用户态进行调用
epoll_create会创建epoll实例,会创建红黑树结构和双向链表结构的ReadList
epoll_ctl会添加,修改,删除 注册到epoll中的fd对应的事件Event
epoll_wait系统调用阻塞等待事件发生,返回给用户态 回调函数处理后的就绪队列的文件描述符列表
协议栈回调
协议栈解析数据后会触发回调函数进行回调,IP头可以解析出源IP和目的IP以及协议,Tcp头可以解析出源端口和目的端口,一个Socket fd就是由一个五元组表示的,除了传入fd之外还会传入事件
回调函数作用:
(1)通过fd找到对应节点
(2)把节点加入就绪队列
Epoll优点
(1)Epoll使用了事件就绪通知,避免线性轮询所有fd文件描述符,避免不必要开销
(2)可以有效处理大量并发连接
(3)Epoll_wait只返回给用户态就绪的文件描述符列表,减少不必要的事件处理
(4)支持边缘触发机制,减少数据从内核到用户空间的拷贝次数
Epoll在Linux内核中的实现方式
(1)红黑树:存储注册的文件描述符FD,在添加删除查找fd的时候时间复杂度都是O(logn),是一种自平衡的二叉搜索树
(2)就绪队列ReadList:内核维护一个双向链表,包含已就绪的文件描述符,当文件描述符就绪时会被添加到链表中,程序可以通过系统调用获取链表中存储的就绪事件,避免轮询所有文件描述符,采用双向链表的原因是因为双向链表的插入和删除时间复杂度都是O(1)
(3)红黑树的有序性保证了事件触发顺序与节点在红黑树中的顺序一致,当多个时间同时准备就绪时,epoll会按照红黑节点的顺序依次触发事件
Nginx中的Epoll和惊群现象
Nginx采用的Epoll IO多路复用机制,每个Worker进程都会通过epoll_create创建单独的Epoll实例,共享监听套接字,由操作系统来分配每个连接的文件描述符fd注册到worker的Epoll实例中。
Epoll惊群现象为一个新的连接到达监听套接字时,多个Epoll都争抢将连接注册到自己的Epoll,最终只能由一个Epoll能够accept处理,其他的进程就白唤醒了,这就导致了系统资源的浪费
(1)Accept Mutex 锁机制决定哪个 worker 进程接受新的连接,使得只有拿到自旋锁的worker进程可以处理这个连接请求
(2)SO_reuserport采用端口复用的形式,每一个进程都通过监听自己的Socket端口去避免惊群现象
Epoll与poll/Select
Poll每次调用都会将用户态的文件描述符集合拷贝到内核态进行检测,再从内核态拷贝到用户态(用户态管理文件描述符集合)
Epoll会将新的IO事件的fd注册到Epoll红黑树实例中,Epoll_wait阻塞等待,数据到达或其他操作触发协议栈回调的时候才将fd对应的节点放入就绪队列中(内核态维护文件描述集合),Epoll_wait才将文件描述符列表发送给用户
所谓加入就绪队列:就是将节点的前后指针联系到一起,红黑树上的原节点也不会delete掉