主题
Netty 生产检查清单
适用 Netty 4.x。每条都可勾选,括号内是默认值或判断依据;版本差异以官方文档为准。 只列「不做会出事」的动作,机制解释见各篇正文。
上线前
- [ ] 堆外内存上限显式设置:
-XX:MaxDirectMemorySize必须配(默认等于-Xmx,对长连接服务通常不够),并同时配-Dio.netty.leakDetection.level=SIMPLE(默认);预发用PARANOID(性能开销随负载变化,不按固定比例估算)。 - [ ] Pipeline 顺序已验证:
LengthFieldBasedFrameDecoder在最前,业务解码器其次,Encoder 在业务 Handler 之后;用一个「半包 + 粘包 + 连续三帧」的单元测试确认业务 Handler 收到的是完整消息。 - [ ] 帧边界与协议头已定:
maxFrameLength按业务设 64KB–1MB(RPC 常见区间),不用Integer.MAX_VALUE;协议头带 Magic 与 Version 并约定未知版本处理方式;文件传输走 Chunked,不复用 FrameDecoder。 - [ ] 心跳超时按 RTT 定:
IdleStateHandler(readerIdleTime, writerIdleTime, 0)中readerIdleTime取心跳间隔 × 3,客户端writerIdleTime取心跳间隔;30–60s 心跳对应 60–180s 判死,不要用 10s。 - [ ] 重连有指数退避与抖动:退避
min(60, 2^attempt)秒封顶,叠加 0–1000ms jitter;只做退避不加 jitter,客户端仍会在封顶时刻同步重连。 - [ ] 背压与连接上限已配置:
WriteBufferWaterMark(32KB, 64KB),并在 accept 后按channelGroup.size()拒绝超限连接;WorkerGroup线程数取 CPU 核数 × 2,Boss 线程通常每个监听端口 1 个(多 ServerChannel 才需多个,多配不能给同一端口容错)。
运行中
- [ ] 堆外内存:
io.netty.allocator.usedDirectMemory超过MaxDirectMemorySize的 80% 告警;同时看进程 RSS 是否只涨不降。 - [ ] 堆内与 GC:
io.netty.allocator.usedHeapMemory超过-Xmx的 80%,或 Full GC 频率持续上升,先怀疑消息积压或 ByteBuf 未释放。 - [ ] EventLoop 积压:
io.netty.eventLoop.pendingTasks持续 5 分钟 > 1000,说明有任务在 IO 线程上阻塞。 - [ ] 连接与流量:活跃连接数突降 30% 或突增 200%、写字节速率突然掉零,分别对应客户端批量掉线或下游写不出去;指标名以实际注册的为准,正文里的
io.netty.*需先确认已导出。
故障时
- [ ] 堆外内存持续增长直至
OutOfDirectMemoryError→ 看 RSS 与usedDirectMemory曲线 → 预发开-Dio.netty.leakDetection.level=PARANOID复现,按LEAK:分配栈定位忘记release()或异步队列漏retain()的 Handler。 - [ ] P99 飙升但 CPU 只有 30% 左右 → 看
pendingTasks与单 EventLoop 的 Channel 数 → 判定某个 Handler 做了同步 DB/RPC 调用,把耗时逻辑移出 IO 线程,处理完再切回ctx.executor()写回。 - [ ] 客户端频繁断连重连但网络正常 → 对比
IdleStateHandler阈值与实测 RTT P99 → 阈值低于 RTT × 3 就调大,并确认allIdleTime没有误开。 - [ ] 服务端重启后瞬时 CPU/内存打满、大量 connect 超时 → 看客户端重连时间分布 → 没有 jitter 就补随机抖动,并把退避封顶从固定值改为指数退避。
- [ ] 业务侧收到半条消息或解码异常 → 查 FrameDecoder 的
lengthFieldOffset/lengthFieldLength/lengthAdjustment与实际协议是否一致 → 用抓包字节逐帧核对,不要先怀疑业务序列化。