第 14 章 遇到问题时这样排查
14.1 先确认问题发生在哪一层
遵循从外到内的顺序:进程是否启动 → 是否监听正确地址和端口 → 客户端是否连通 → 字节是否到达 → 帧是否完整 → 解码是否正确 → 业务是否执行 → 出站编码与发送是否完成。
每一步都要收集证据。比如“客户端没有输出”可能是没连上,也可能是忘记换行、没有 flush,或响应被错误的 Pipeline 顺序挡住。直接增加线程数通常解决不了这些问题。
14.2 常见现象速查
| 现象 | 优先检查 | 操作方向 |
|---|---|---|
Address already in use |
端口被占用 | 停止旧进程或双方改端口 |
Connection refused |
服务未启动或端口不对 | 看 LISTENING 与客户端参数 |
| 连接一直超时 | 地址、路由、防火墙 | 先在本机测试,再逐步跨机器 |
| 连上但不回复 | 行分隔符、处理器顺序 | 确认发送实际换行字节 |
| 中文乱码 | 双方字符集、切帧顺序 | 统一 UTF-8,先切帧后转字符串 |
TooLongFrameException |
超过协议限制 | 检查发送内容,合理设置上限 |
IllegalReferenceCountException |
重复释放、释放后使用 | 逐条跟踪所有权 |
| 内存持续上升 | 泄漏、队列积压、大消息 | 区分 ByteBuf 泄漏与活跃对象堆积 |
| 少量请求很慢 | EventLoop 阻塞 | 检查数据库、sleep、文件 I/O |
| 客户端迟迟不结束 | 等不到服务器关闭 | 确认发送 quit 且带换行 |
14.3 一个“没有响应”的完整推理
假设服务器显示已经监听,客户端也连接成功,但输入 hello 后没有回复。先确认发送的是 hello\n 对应的实际换行,而不是仅仅 hello。行解码器会等待边界,因此没有收到完整帧时不回复是正确行为。
若换行已发送,再检查是否把 StringEncoder 放到了业务处理器之后;若业务使用 ctx.writeAndFlush,该编码器可能没有经过。再检查异常日志,确认不是不支持的出站消息类型。
最后检查对端是否持续读取。写成功、读取成功和业务处理成功要分别观察,不能用某一个日志替代所有层面的证据。
小练习: 当所有连接一起变慢,而 CPU 很低时,为什么仍然要检查 EventLoop 中的阻塞调用?