主题
概念卡片:Netty线程模型与高性能
一句话机制:Netty 高性能 = I/O 模型(NIO 多路复用)+ 线程模型(Reactor 主从)+ 内存效率(零拷贝 + 内存池)+ 可靠性(粘包解码/心跳/Selector bug 修复);其中线程模型的核心是 bossGroup 只收连接、workerGroup 管读写,业务在单线程串行执行避免锁竞争。
BIO / NIO / AIO 区别(必考)
| 维度 | BIO | NIO | AIO |
|---|---|---|---|
| 模型 | 一连接一线程 | 一请求一线程(多路复用) | 一有效请求一线程 |
| 阻塞 | 阻塞(等数据) | 非阻塞(立即返回状态) | 非阻塞(内核完成后回调) |
| 面向 | 流(Stream 单向) | 缓冲区(Channel 双向) | 缓冲区 |
| 适用 | 连接少、低并发 | 高并发、连接多 | 连接数多且长耗时 |
- 伪异步 IO:BIO + 线程池,请求入队,缓解线程开销但仍受线程池限制。
- NIO 三组件:Channel(通道,双向)/ Buffer(缓冲区,读写中转)/ Selector(选择器,单线程管多 Channel)。
- Buffer 关键方法:
flip()写→读切换(position 给 limit,position 置 0);clear()读→写(position 置 0,limit=capacity);rewind()重读。 - Selector 监听事件:读 / 写 / 连接 / accept;Linux 实现是
EPollSelectorImpl(epoll_ctl 注册,fdToKey 维护 fd↔SelectionKey 映射)。
Reactor 模型三变种
Reactor = while(true) { selector.select(); 分发事件 } 的反应堆线程,两要素:Reactor(监听分发)+ Handler(实际处理)。
| 变种 | 组成 | 局限 |
|---|---|---|
| 单 Reactor 单线程 | 一个线程干所有(accept/read/decode/process/encode/send) | 高负载不行;线程死循环整个服务挂 |
| 单 Reactor 多线程 | Reactor 收连接,线程池处理读写 + 业务 | 百万连接时一个 Acceptor 成瓶颈 |
| 主从 Reactor 多线程 | MainReactor 收连接(认证/握手),SubReactor 池处理读写,业务线程池处理非 IO | Netty 采用此模型的变体 |
Netty 线程模型落地(boss / worker)
BossGroup(MainReactor)→ accept 事件 → 封装 NioSocketChannel → 注册到 WorkerGroup
WorkerGroup(SubReactor + Worker 同池)→ read/write 事件 → Handler 处理- bossGroup:只在 bind 后拿一个线程当 MainReactor,专收 accept(每个端口对应一个 boss 线程)。
- workerGroup:被各 SubReactor 和 worker 线程复用;默认线程数 CPU 核数 × 2(
Math.max(1, cpu*2)),bind 完才启动。 - 注意:Netty 虽借用主从 Reactor 结构,但实际 SubReactor 与 Worker 在同一个线程池里(不是分离的两个池)。
- 每个 NioEventLoop 循环 3 步:轮询事件 → 处理 I/O 事件(processSelectedKeys)→ 处理任务队列(runAllTasks,每 64 个任务检查耗时防阻塞 I/O)。
粘包/拆包
原因:TCP 是流式协议——写入 > 发送缓冲区 → 拆包;多次小写入合并 → 粘包;MSS 分段 / MTU(1500B) IP 分片。
四种内置解码器:
| 解码器 | 原理 |
|---|---|
FixedLengthFrameDecoder | 定长消息 |
LineBasedFrameDecoder | 换行符分隔 |
DelimiterBasedFrameDecoder | 自定义分隔符 |
LengthFieldBasedFrameDecoder | 消息头长度字段(最通用) |
零拷贝(四层面)
- Direct Buffer:堆外直接内存读写 Socket,免去「堆内存 → 直接内存」一次拷贝。
- CompositeByteBuf:多个 ByteBuf 逻辑合并,避免物理拷贝拼大 Buffer。
- FileRegion + transferTo:文件直接发到目标 Channel,避免循环 write 的内存拷贝。
- wrap():byte[] / ByteBuffer 包装成 ByteBuf 零拷贝。
Selector 空轮询 Bug(epoll bug)
- 现象:select 轮询结果为空仍持续空转,CPU 100%;JDK 6u18 声明修复但 1.7 仍存在。
- Netty 解法:
selectCnt记录空轮询次数,连续空转达到阈值(默认 512)→rebuildSelector():新建 Selector → cancel 旧 key → Channel 重新注册 → 关闭旧 Selector。
心跳机制
- TCP 自带
SO_KEEPALIVE但灵活性不够(只探测死链,不灵活),实际在应用层做自定义心跳。 - Netty 用
IdleStateHandler:readerIdleTime(读超时)/writerIdleTime(写超时)/allIdleTime(总超时);空闲发 PING-PONG 保活。 - 服务端用:定时清除闲置会话;客户端用:检测断线/网络延迟。
不变量(必须成立的约束)
- 1 个 EventLoop = 1 线程 + 1 Selector;一个 Channel 只绑定一个 EventLoop,保证线程安全。
- boss 只收连接不读数据,worker 只读写不收连接。
- 业务 Handler 中禁止长时间阻塞——会卡死所属 EventLoop 线程(该线程所有 Channel 一起遭殃)。
常见误解
- 以为零拷贝是"完全不用拷贝" → 是减少用户态↔内核态拷贝次数(Direct Buffer / transferTo 把数据留在内核态直传)。
- 以为 Netty 的 worker 是"每连接一线程" → 一个 worker 线程管成百上千个 Channel。
- 以为粘包是 Netty 的问题 → 是 TCP 流式协议的天然现象,Netty 只是提供了现成解码器。
关联
- 总览:Netty技术栈总览
- 面试:面试题蒸馏卡:Netty
- 通信底座:[概念卡片:Dubbo RPC框架](./概念卡片:Dubbo RPC框架) · 概念卡片:Dubbo集群容错与负载均衡