跳到正文
Elaine Blog
返回

编解码、粘包拆包与背压

网络与 I/O

TCP 没有业务消息边界。“粘包”只是接收方一次读到多条消息,“拆包”只是一次只读到部分消息,它们都不是 TCP 故障。 正确做法是让协议明确边界,并让解码器支持任意分段。

Table of contents

Open Table of contents

四种常见边界方案

方案优点风险
固定长度解析简单浪费空间,扩展困难
分隔符文本直观转义、超长未闭合消息
长度字段二进制高效长度校验和字节序必须一致
协议自描述表达力强解析器更复杂

长度字段协议可以定义为:魔数、版本、消息类型、请求 ID、正文长度、正文。魔数是固定开头,用于快速拒绝错误协议;请求 ID 用于关联异步响应。

运行长度字段实验

Netty 长度字段样例下载先用四字节整数写入正文长度, 再把 UTF-8 正文写到网络缓冲区;解码器只有收到完整帧才向后传播:

mvn -Dtest=NetworkServerLabTest#framesAndDecodesOneNettyMessage test

真实协议必须限制最大帧长,并在分配正文缓冲区前验证长度非负、不过限。否则攻击者只发一个巨大长度字段,就可能制造内存压力。

编码和序列化不是同一个概念

编码把传输字节转换成消息边界或基础类型;序列化把业务对象转换成可传输表示,例如 JSON、Protobuf。两者常被放在同一 Pipeline,但职责不同。 协议还要定义版本兼容:新增可选字段、未知字段处理、枚举扩展和废弃周期。

不要使用 Java 原生对象序列化作为跨服务协议。它与 Java 类型强耦合,演进困难,并扩大反序列化安全风险。

背压保护内存和下游

背压是下游处理不过来时,主动减慢或拒绝上游,而不是无界缓存。Netty 可根据 Channel 可写状态和写缓冲区高低水位暂停生产;读取侧可以临时关闭自动读取, 处理完积压后再继续读。

完整策略包括:

只调大缓冲区会把故障延迟到内存耗尽。限流决定“允许多少请求进入”,背压决定“下游变慢时怎样反馈”,超时决定“最多等待多久”,三者要一起设计。

下一步

阅读Netty 线程模型与任务调度,避免业务阻塞把整个 EventLoop 拖慢。


分享这篇文章:

上一篇
Netty 快速入门:Channel、EventLoop 与 Pipeline
下一篇
Netty 线程模型与任务调度:不要阻塞 EventLoop