第 18 / 20 篇

附录 B 练习参考答案

  1. 两次发送不一定对应两次接收: TCP 提供字节流,不保留应用调用 send/write 的边界。接收缓冲区、网络传输与读取时机都会影响每次读到的字节数。需要协议定界。

  2. netty-all 不等于单个全量 jar: 现代版本通过 Maven 传递依赖带入多个模块。应使用构建工具解析依赖,而不是只复制聚合 jar。

  3. 客户端结束而服务端继续: 客户端连接 Channel 与监听 Channel 不同。关闭一条连接不会自动关闭监听通道,服务器仍可接受其他连接。

  4. 写 Future 与入库: 写操作成功不能证明业务处理成功。应设计带请求编号的业务应答,明确应答是在事务提交前还是提交后发出,并配置请求超时。

  5. 编码器位置: ctx.writeAndFlush 从当前上下文向前走出站路线。业务处理器之后的编码器可能被跳过,导致字符串无法正确变成网络字节。

  6. 长度字段: 正文 12 字节加 4 字节头,共 16 字节。整帧上限 1024 时,正文最多 1020 字节。前提是长度字段不包含自身、没有其他头尾字段。

  7. 延后使用 ByteBuf: SimpleChannelInboundHandler 默认在回调结束后释放匹配消息。延后使用需要明确 retain 与 release 配对,或复制所需内容;共享视图不自动延长生命。

  8. readComplete 与完整订单: 前者表示一轮读取事件结束,不是业务边界。应由协议解码器产出完整订单消息。

  9. 心跳不能替代请求超时: 心跳只能反映某种连接或协议活动,具体业务仍可能卡死。每个请求需要自己的期限与清理逻辑。

  10. requestId 与重复扣款: requestId 仅用于关联应答,不自动提供幂等。服务端需要稳定的业务幂等键、去重记录或事务约束,并定义重试语义。

  11. 中文测试: 整包输入没覆盖 UTF-8 字符跨读取边界的情况。应在一个多字节字符内部切分输入,再检查最终结果。

  12. 线程池仍可能积压: 进入队列的速度长期高于消费速度时,待处理任务会增长。线程池还可能受数据库连接池和外部服务吞吐限制,需要队列上限与背压。

  13. 写成功计数不等于业务成功: 本地发送、对端读取和业务提交属于不同阶段。指标必须对应真正要衡量的阶段。

  14. CPU 低也可能阻塞: 等待数据库、锁或网络时线程不消耗很多 CPU,却占着 EventLoop,导致其他连接的事件无法及时处理。