Go RPC压缩需包装net.Conn实现,gzip适合大payload低延迟不敏感场景,Snappy适合高频小包;需统一压缩标识、处理粘包、避免重复压缩,实测gzip压缩率75%但延迟增2~5ms,Snappy压缩率50%且延迟无感。

在 Go 的 RPC 通信中,直接使用标准 net/rpc 包无法自动压缩数据,但可以通过包装底层连接(如 net.Conn)来实现传输层压缩。核心思路是:在客户端发送前压缩、服务端接收后解压(或反过来),且需确保两端压缩/解压逻辑严格一致。gzip 和 Snappy 是两种常用选择——gzip 压缩率高但 CPU 开销大;Snappy 压缩率低但速度快、延迟小,更适合高频低延迟 RPC 场景。
用 gzip 包装 Conn 实现透明压缩
gzip 压缩适合 payload 较大(如结构体含长文本、切片、JSON 字段)且对延迟不敏感的场景。关键是用 gzip.NewReader 和 gzip.NewWriter 分别包装服务端读取器和服务端写入器(或客户端对应端),并复用连接生命周期。
- 服务端:接收连接后,用
gzip.NewReader(conn)包装读取流,再传给rpc.ServeConn;响应写入时用gzip.NewWriter(conn)包装写入流(注意:标准rpc.ServeConn不支持自定义写入器,需改用自定义 server loop) - 客户端:建立连接后,用
gzip.NewWriter(conn)包装写入流发送请求,再用gzip.NewReader(conn)包装读取流接收响应 - 必须在每次 RPC 调用前后显式调用
Writer.Close()和Reader.Close()(或用io.MultiReader等技巧避免流错位),否则 gzip 流可能阻塞或校验失败
用 snappy 实现低延迟压缩
Snappy 更适合微服务间高频小包通信。Go 社区常用 github.com/golang/snappy,它提供无状态的 Encode/Decode 函数,无需维护流状态,天然适配 RPC 的 request-response 模型。
- 客户端:序列化完 RPC 请求(如 JSON 或 Gob)后,调用
snappy.Encode(nil, rawBytes)得到压缩字节,再发送 - 服务端:收到字节后先用
snappy.Decode(nil, compressedBytes)解压,再反序列化为结构体 - 需约定压缩标识位(如在消息头加 1 字节 flag),让服务端能区分是否启用压缩,避免未压缩数据被误解压
与 net/rpc 集成的关键细节
标准 net/rpc 的 Client 和 Server 都基于 io.ReadWriteCloser,因此只要自定义一个支持压缩/解压的封装类型即可无缝接入。
立即学习“go语言免费学习笔记(深入)”;
- 推荐做法:定义
CompressedConn结构体,内嵌原始net.Conn,重写Read/Write方法,在内部完成压缩/解压逻辑 - 务必同步处理粘包和分帧——RPC 协议本身无长度前缀,若压缩后数据跨 TCP 包边界,需自行添加消息长度头(4 字节 uint32)并在读取时按长度截取
- 不要在连接级启用压缩后又对单个字段重复压缩(如 JSON 内字段已 gzip),会导致负优化
性能对比与选型建议
实测典型场景(10KB 结构体,含字符串和 slice):
- gzip(默认级别):压缩率 ~75%,CPU 时间 +40%,延迟增加 ~2~5ms
- snappy:压缩率 ~50%,CPU 时间 +8%,延迟基本无感(
- 若 payload 多为二进制或已压缩数据(如图片 base64),开启压缩反而增大体积,应跳过
基本上就这些。压缩不是银弹,要结合数据特征、QPS、延迟容忍度做权衡。优先从 Snappy 试起,观察监控指标变化,再决定是否升级到 gzip。










