⚡ 网络 IO 模型
五大 IO 模型 · select/poll/epoll · Reactor 模型 · Netty 原理
1. 五大 IO 模型?同步/异步与阻塞/非阻塞的维度?
- 阻塞 IO(BIO):read 一直等数据到达。一连接一线程
- 非阻塞 IO(NIO 轮询):read 立即返回"没数据",应用反复轮询(CPU 浪费)
- IO 多路复用:select/poll/epoll 一次等多个 fd,有事件才处理(NIO 的核心)
- 信号驱动 IO:数据就绪发 SIGIO 信号通知(用得少)
- 异步 IO(AIO):内核把数据拷贝到用户缓冲区完成后才通知(真正的异步)
两个维度:
- 阻塞 vs 非阻塞:发起 IO 调用后,是"等"还是"立刻返回"(应用视角)
- 同步 vs 异步:数据拷贝完成前,应用要不要参与等待(1~4 都是同步,拷贝由应用完成;AIO 内核帮拷完)
🎯 面试要点
- Java 的 NIO 是"多路复用"不是"异步"——命名易误解,要讲清楚
- Linux 上 Java AIO 底层用 epoll 模拟(不是真异步),性能优势不明显,所以主流是 NIO(Netty)
- Linux 真异步是 io_uring(新内核)——提一句加分
2. select、poll、epoll 的区别?
| 对比 | select | poll | epoll |
|---|---|---|---|
| 数据结构 | fd 位图(1024 上限) | fd 数组(无上限) | 红黑树 + 就绪链表 |
| 遍历 | 每次调用全量拷贝+遍历 O(n) | 同 select O(n) | 只返回就绪 fd(回调挂链表)O(就绪数) |
| 用户态/内核态拷贝 | 每次全量拷贝 | 每次全量拷贝 | 只拷贝就绪事件(epoll_wait) |
epoll 三大操作:epoll_create(建表)、epoll_ctl(增删改监听 fd,回调机制注册)、epoll_wait(等就绪)。水平触发 LT vs 边缘触发 ET:LT 有数据就反复通知,ET 只在状态变化通知一次(配非阻塞读循环)。
🎯 面试要点
- epoll 适合"连接多、活跃少"(高并发服务器);select 适合连接少
- Netty 可用 epoll 原生传输(epoll 模式)。注意:JDK 的 Selector 在 Linux 上底层也是 epoll,不是 select;Netty native epoll 的优势在于边缘触发、多线程 accept 与更少的数据结构开销
- ET 模式性能更高但要小心漏读(while 读到 EAGAIN)
3. Reactor 模型?Netty 如何实现?
Reactor(反应器):事件驱动——一个线程等所有连接的事件(多路复用),事件到达分发给 Handler。Netty 的基石。三种形态:
- 单 Reactor 单线程:一个线程既 select 又处理。只适合简单场景(Redis 类似模型)
- 单 Reactor 多线程:select 线程只分发,业务处理丢线程池
- 主从 Reactor 多线程(Netty 采用):Boss Group 只负责 accept(建立连接),Worker Group 负责每个连接的读写 IO;业务逻辑丢业务线程池
Netty 核心组件
EventLoopGroup bossGroup = new NioEventLoopGroup(1); // accept 线程
EventLoopGroup workerGroup = new NioEventLoopGroup(); // IO 线程(默认核数×2)
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override protected void initChannel(SocketChannel ch) {
ch.pipeline()
.addLast(new LengthFieldBasedFrameDecoder(...)) // 粘包/拆包解决
.addLast(new StringDecoder()) // 解码器(责任链)
.addLast(new MyHandler()); // 业务处理器
}
});
🎯 面试要点
- Netty 四大核心:EventLoop(线程模型)、Channel/Pipeline(责任链处理)、ByteBuf(零拷贝)、Future/Promise(异步回调)
- 零拷贝:Netty 的 FileRegion(sendfile)、DirectBuffer 直接内存(见 JVM 模块)
- 粘包/拆包解决:长度前缀(LengthFieldBasedFrameDecoder)、分隔符、固定长度——Netty 面试必问