关键不是“压不压”,而是“什么字符串、多大、在哪一端压、用什么算法压”;grpc中需用proto.size()而非len(string)判断压缩阈值,建议>1024字节再启用gzip,

字符串在 RPC 传输中盲目压缩反而拖慢性能,关键不是“压不压”,而是“什么字符串、多大、在哪一端压、用什么算法压”。
gRPC 中字符串字段是否启用 gzip,取决于 proto.Message.Size() 而不是 len(string)
Protobuf 序列化后的真实字节长度,和原始字符串长度差异很大:空格、重复字段名、嵌套结构都会放大体积;而小字符串(如 "OK"、"user_123")经 proto.Size() 计算后往往不足 100 字节,gzip 压缩后反而增加 18 字节 header,体积变大、CPU 白耗。
- 必须用
proto.Size()预估编码后大小,而非len(s)—— 后者只算 UTF-8 字节数,完全不反映 protobuf 编码开销 - 实测阈值建议:
proto.Size() > 1024再启用grpc.UseCompressor(gzip.Name),低于 512 则强制禁用 - 别在服务端全局注册
grpc.RPCCompressor(gzip.Compressor)就以为万事大吉——客户端不声明,服务端根本不会压缩
HTTP/JSON API 里传字符串,gzip 压缩由 net/http 自动处理,但需避开陷阱
net/http 的 gzip 压缩发生在 ResponseWriter 层,对请求体(request body)不自动压缩,只对响应体(response body)生效。它也不识别你 JSON 里的字符串字段,而是整包压缩整个 HTTP body。
- 服务端启用方式:注册
http.Handler前包装gzip.GzipHandler,或用http.Server{Handler: gzipHandler} - 客户端必须发
Accept-Encoding: gzip,否则服务端不会返回压缩响应 - 禁止对
Content-Type: application/json且状态码为204或304的响应启用压缩——这类响应本就不含 body,强行写入 gzip 流会 panic - 小响应(如
{"id":"abc","status":"success"},约 40 字节)即使开了 gzip,Chrome/Firefox 也常因解压成本过高而跳过实际解压
自定义 RPC Codec 时,字符串压缩要嵌在序列化之后、写入连接之前
如果你用 net/rpc + 自定义 rpc.Codec,压缩逻辑不能放在字符串字段层面,而应作用于整个序列化后的字节流。否则会破坏协议边界,导致读取错位。
- 正确顺序:
json.Marshal(req) → bytes.Buffer → gzip.NewWriter → conn.Write() - 错误做法:遍历 struct 字段,对每个
string单独gzip.CompressString()—— 这会让解码器无法识别字段分隔符 - 务必在
gzip.Writer.Close()后再调用conn.Write(),否则末尾 flush 数据丢失,接收端读到截断数据 - 别复用
*gzip.Writer实例:并发调用下会写冲突,每个请求新建一个更安全
Zstd 和 Snappy 在字符串密集型 RPC 中的取舍
纯文本字符串(如日志、配置、HTML 片段)压缩率高,但不同算法表现差异极大;二进制字符串(如 base64 图片)则几乎不值得压。
-
zstd:对 5–100 KB 的 JSON/XML 字符串,压缩率比gzip高 12–18%,解压速度相当,适合带宽受限内网;但需引入第三方库,iOS Safari 旧版本不兼容 -
snappy:压缩率仅比gzip低 5–7%,但解压速度快 3–5 倍,适合高频短字符串(如指标上报、trace tag),CPU 敏感场景首选 -
lz4:吞吐最高(实测 >1.2 GB/s),但压缩率最低;仅当单次 RPC 携带大量短字符串(如批量 ID 列表)且延迟要求 - 所有算法都应在压缩前做
if len(data) 快速路径判断,避免小数据负优化
真正容易被忽略的是:字符串在 RPC 中从来不是孤立存在的,它总依附于结构体上下文。决定压不压、怎么压,得看整个 message 编码后的体积和业务 SLA——而不是盯着某个 string 字段想当然。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











