
gRPC 压缩不是默认开启的,必须显式配置两端
很多人以为只要服务端配了 gzip,客户端发过来的数据就会自动被压缩——其实完全不是。gRPC 的压缩是“协商式”的:客户端要主动声明用什么压缩算法,服务端也得注册对应解压器,否则请求照常走,但压根不压缩。
常见错误现象:grpc.UseCompressor("gzip") 只在客户端调用时加了,服务端没注册 gzip.Compressor,结果监控看到传输体积一点没变;或者反过来,服务端注册了但客户端没声明,请求还是明文。
- 服务端需在
grpc.NewServer()里传入grpc.RPCCompressor(gzip.Compressor)和grpc.RPCDecompressor(gzip.Decompressor) - 客户端每次调用 RPC 方法时,必须显式带上
grpc.UseCompressor(gzip.Name)(注意是gzip.Name,不是字符串"gzip") - 更稳妥的做法是全局设置:创建
grpc.Dial()时用grpc.WithDefaultCallOptions(grpc.UseCompressor(gzip.Name)),避免漏掉某次调用
小消息压了反而更慢,得设压缩阈值
压缩本身要消耗 CPU,而小数据(比如几十字节的健康检查请求)经 gzip 处理后,体积可能不变甚至略增,还白耗几个微秒。这不是理论,是真实压测中反复出现的性能倒退点。
gRPC 没内置阈值开关,得自己绕一下:
立即学习“go语言免费学习笔记(深入)”;
- 不要对所有请求无差别启用压缩;建议只对响应体 > 1KB 或请求体 > 512B 的调用启用
grpc.UseCompressor - 可以在客户端封装一层调用函数,先
proto.Size()估算序列化后大小,再决定是否加grpc.UseCompressor选项 - 注意:Protobuf 的
Size()返回的是编码后长度,比原始 struct 字段数估算准得多
net/rpc 加压缩?别硬套,先看要不要换 gRPC
如果你还在用标准库的 net/rpc,并想着给它“打补丁”加 gzip,那得先问一句:业务是否允许协议升级?因为手动实现一个可靠的压缩 codec,远比表面看起来复杂。
容易踩的坑:
-
gzip.Writer必须调用Close()才会写出 trailer 和 CRC,否则服务端gzip.NewReader解压时直接报invalid header - RPC 消息是 length-prefix 格式(gob 默认),压缩后长度字段就失效了——你得重写整个
ServerCodec,不能只压 payload - 两端 gzip 级别、buffer 大小、flush 行为稍有不一致,就会出现粘包或 EOF 错误,调试起来非常隐蔽
现实建议:新项目直接上 gRPC;旧系统若无法迁移,优先考虑用连接层包装(如 gzip.NewWriter(conn) 包整个 TCP 连接),而不是动 codec 层。
压缩算法选 gzip、snappy 还是 zstd?看数据特征,不是看名字
别被“zstd 最先进”带偏。实际效果取决于你的 payload 类型和延迟敏感度。
- 文本类日志、JSON-like Protobuf:gzip 压缩率高,适合带宽受限场景(如边缘节点回传)
- 高频低延迟服务(如实时风控决策)、CPU 资源紧张:snappy 或
zstd.WithEncoderLevel(zstd.SpeedFastest)更合适,压缩/解压快 3–5 倍 - zstd 的平衡性确实好,但它在 Go 生态里需要引入
github.com/klauspost/compress/zstd,且 gRPC 官方不原生支持,得自己注册编解码器
真正关键的不是算法本身,而是两端必须用同一套实现——比如客户端用 github.com/klauspost/compress/gzip,服务端就不能混用标准库 compress/gzip,CRC 计算方式细微差异就会导致解压失败。
压缩这件事,最麻烦的从来不是“怎么写代码”,而是两端行为对齐、大小阈值判断、以及 CPU 和带宽之间的隐性权衡。稍微松一点,就变成白忙活。










