Skip to content

说说你知道的几种 I/O 模型 ​

  • 标签:Netty、后端

🏛 业务架构师 ​

四种模型按演进逻辑讲,每一步都在回答"怎么支撑更多连接":

  • BIO(同步阻塞):一连接一线程。连接少没问题,连接一多线程爆炸——C10K 问题的起点;
  • NIO(同步非阻塞 + IO 多路复用):一个 selector 线程监听成千上万个连接的就绪事件,就绪了才处理——线程数与连接数解耦,单机数万连接成为可能;
  • 多路复用的内核实现:select(fd 上限 1024)→ poll(无上限但线性扫描)→ epoll(事件回调,O(1) 获取就绪列表)——Linux 高并发的地基;
  • AIO(异步):内核完成 IO 甚至回调通知,应用只收结果。Linux 上支持不温不火,Netty 曾移除 AIO 支持——工程实践中 Reactor + epoll 已够用。

业务决策视角,IO 模型决定单机连接密度,连接密度决定机器成本:

  • 普通业务 Web(Spring Boot + Tomcat NIO):Tomcat 早就是 NIO 了,业务无感,默认够用;
  • 长连接网关类业务(推送、IM、IoT):必须 Netty 这类 NIO 框架——10 万长连接用 BIO 要 10 万线程(内存与切换成本压垮机器),NIO 十几个线程搞定,单机成本差一个数量级;
  • 文件类高吞吐(日志采集、对象存储传输):sendfile 零拷贝 + 事件驱动。

一句话:IO 模型是"连接数 × 单连接成本"的经济学。业务流量画像(连接数、请求大小、延迟要求)决定该用哪档模型——脱离业务谈模型优劣是空转。

🏗 系统架构师 ​

我按演进序列讲五种 I/O 模型(以 read 为例):

  • 阻塞 I/O(BIO):发起 read 后线程挂起直到数据就绪并拷贝完成。一连接一线程,万连接万线程,内存与切换开销爆炸——传统 Tomcat 每请求一线程模型的局限所在;
  • 非阻塞 I/O(NIO,同步非阻塞):read 立即返回 EWOULDBLOCK,用户轮询。忙等烧 CPU,很少直接用,是多路复用的前置语义;
  • I/O 多路复用:select/poll/epoll 一个线程监听成百上千 fd,就绪才处理。Redis、Nginx、Netty 全基于它,是高并发服务器的骨架;
  • 信号驱动 I/O:内核就绪后发信号回调,应用少,边缘触发难处理;
  • 异步 I/O(AIO):发起后内核完成"等待 + 拷贝"再回调,真正的异步。Linux 原生 AIO 生态弱,io_uring(5.1+)是新方向;Windows IOCP 成熟。

同步/异步的分界:拷贝数据阶段是否需要用户线程参与——前四种都要(就绪后 read 仍阻塞拷贝),AIO 完全不用。Reactor(Netty,基于多路复用) vs Proactor(Windows IOCP) 正对应同步非阻塞与异步两族。工程结论:Linux 上高并发网络服务的答案是 epoll + Reactor,文件异步 IO 在向 io_uring 收敛。

💻 全栈开发专家 ​

五种,我按"阻塞发生在哪两个阶段"讲(等待数据就绪 + 数据拷贝到用户态):

  1. 阻塞 I/O(BIO):两个阶段全阻塞。一连接一线程,万级连接万级线程——内存和切换成本爆炸。传统 Tomcat bio、老 JDBC 都是。
  2. 非阻塞 I/O(NIO 轮询):就绪阶段不阻塞(立刻返回 EWOULDBLOCK),但要自己反复轮询;拷贝阶段仍阻塞。基本没人直接用,是"就绪通知"的前置概念。
  3. I/O 多路复用:select/poll/epoll 把"轮询 N 个 fd"交给内核,线程阻塞在 select 上,任一 fd 就绪就唤醒——一个线程管上万连接。Redis、Nginx、Netty、Tomcat NIO 全是它。
  4. 信号驱动 I/O:内核就绪后发信号,实际用得少。
  5. 异步 I/O(AIO):内核把"就绪 + 拷贝"全干了,完成后回调通知。Windows IOCP 成熟,Linux native Aio 鸡肋(数据库/少数场景用 io_uring 改写了这一层,新趋势)。

面试表达技巧:画一张二维表——横轴"就绪阶段是否阻塞",纵轴"拷贝阶段是否阻塞",多路复用和 AIO 的区别一眼可见:多路复用拷贝仍阻塞,AIO 全异步。

工程注脚:Java 的 NIO 包 = 多路复用(Selector/Channel/Buffer),Linux 上底层是 epoll;Netty 基于此再做了一层工程封装(线程模型、内存池、编解码)。