IO模型优化
1. IO事件的通知机制
水平触发和边缘触发
- 水平触发:只要文件描述符可以非阻塞地执行 I/O ,就会触发通知。也就是说,应用程序可以随时检查文件描述符的状态,然后再根据状态,进行 I/O 操作。
- 边缘触发:只有在文件描述符的状态发生改变(也就是 I/O 请求达到)时,才发送一次通知。这时候,应用程序需要尽可能多地执行 I/O,直到无法继续读写,才可以停止。如果 I/O 没执行完,或者因为某种原因没来得及处理,那么这次通知也就丢失了。
2. 常用的非同步IO模型
2.1 select和poll
使用非阻塞 I/O 和水平触发通知。
select 和 poll 需要从文件描述符列表中,找出哪些可以执行 I/O ,然后进行真正的网络 I/O 读写。由于 I/O 是非阻塞的,一个线程中就可以同时监控一批套接字的文件描述符,这样就达到了单线程处理多请求的目的。
select 使用固定长度的位相量,表示文件描述符的集合,因此会有最大描述符数量的限制。比如,在 32 位系统中,默认限制是 1024。并且,在 select 内部,检查套接字状态是用轮询的方法,再加上应用软件使用时的轮询,就变成了一个 O(n^2) 的关系。
oll 改进了 select 的表示方法,换成了一个没有固定长度的数组,这样就没有了最大描述符数量的限制(当然还会受到系统文件描述符限制)。但应用程序在使用 poll 时,同样需要对文件描述符列表进行轮询,这样,处理耗时跟描述符数量就是 O(N) 的关系。
2.2 epoll
使用非阻塞 I/O 和边缘触发通知。
- epoll 使用红黑树,在内核中管理文件描述符的集合,这样,就不需要应用程序在每次操作时都传入、传出这个集合。
- epoll 使用事件驱动的机制,只关注有 I/O 事件发生的文件描述符,不需要轮询扫描整个集合。
2.2 异步IO(AIO)
异步 I/O 也是到了 Linux 2.6 才支持的功能,并且在很长时间里都处于不完善的状态
3.工作模型优化
- 主进程 + 多个 worker 子进程
- 主进程执行 bind() + listen() 后,创建多个子进程;
- 然后,在每个子进程中,都通过 accept() 或 epoll_wait() ,来处理相同的套接字。
- 监听到相同端口的多进程模型
所有的进程都监听相同的接口,并且开启 SO_REUSEPORT 选项,由内核负责将请求负载均衡到这些监听进程中去。