前言
I/O 模型的演进,可以参考 Linux IO 模型。重中之重的则是 I/O 多路复用,也是 Redis、Nginx 和 Netty 等实现高性能 I/O 的核心原理。
I/O 多路复用
通过一种机制,同时监控多个文件描述符。将用户态对文件描述的遍历,转移到内核态,从而提高 I/O 性能。
为什么不能用多线程?最大连接数会受到系统资源限制。
目前实现的方式主要有 select、poll 和 epoll 三种。
select/poll
如上图所示,select/poll 存在 2 次拷贝和 2 次遍历,当文件描述符过多会出现性能问题。
select 采用定长数据结构存储描述符集合,默认为 1024,相比之下 poll 则去掉了该限制。
小结,select/poll 存在三个弊端;
- 每次 select 都需要全量拷贝描述符集合;
- 内核采用遍历检查描述符就绪状态;
- select 仅返回就绪个数,程序仍需遍历整个集合;
epoll
epoll 主要由保存描述符集合的红黑树,和保存就绪描述符的链表组成。
针对 select/poll 三个弊端,epoll 给出了解决方案;
- 基于红黑树 O(logn) 管理描述符集合,避免每次全量拷贝;
- 内核通过事件驱动记录就绪描述符,而不是遍历整个描述符集合;
- 增加已就绪的描述符链表,应用无需再进行遍历;
事件触发模式
- 水平触发(LT)
level-triggered,当内核缓冲区存在未读取的数据是,服务器会被不断触发事件; - 边缘触发(ET):
edge-triggered,当描述符状态改变,服务器会触发一次事件,无论数据是否被处理完;
ET 可以减少内核事件触发的次数,提高性能。select/poll 只有 LT 模式,epoll 默认是 LT,也能支持 ET 模式。