bigywater.tech
gRPC · 23

压缩:gzip 编码与协商

gRPC 支持按消息压缩吗?怎么协商?压缩多少、花多少 CPU?这篇用本机回环测了 gzip 的体积变化和耗时变化,并看了线上的压缩标志位。

0.5% / 9.6% / 100.0%
100 KiB 的重复片段、类 JSON 文本、随机字节经 gzip 后线上字节占原来的比例
1.6 ms → 8.9 ms
回环上往返 1 MiB 类 JSON 文本,不压缩与 gzip 的耗时
Unimplemented
服务端不认识客户端指定的压缩算法时的错误码

怎么协商

实测 · 本机执行输出
  重复的 JSON 片段 客户端→服务端 不压缩 102672 字节 / gzip 510 字节(0.5%);服务端→客户端 不压缩 102677 / gzip 515 字节(0.5%);gzip 时请求的压缩标志位 1、grpc-encoding=gzip;响应的压缩标志位 1、grpc-encoding=gzip
  随机字节       客户端→服务端 不压缩 102638 字节 / gzip 102593 字节(100.0%);服务端→客户端 不压缩 102643 / gzip 102598 字节(100.0%);gzip 时请求的压缩标志位 1、grpc-encoding=gzip;响应的压缩标志位 1、grpc-encoding=gzip
  类 JSON 文本  客户端→服务端 不压缩 102562 字节 / gzip 9874 字节(9.6%);服务端→客户端 不压缩 102567 / gzip 9879 字节(9.6%);gzip 时请求的压缩标志位 1、grpc-encoding=gzip;响应的压缩标志位 1、grpc-encoding=gzip

客户端每个请求带 grpc-accept-encoding 声明能解的算法;用 UseCompressor 压缩请求时带 grpc-encoding,每条消息的 5 字节前缀里压缩标志位为 1。服务端默认用请求相同的算法回复,也可以在 handler 里用 SetSendCompressor 选一个客户端声明过的算法。

收益取决于数据

实测 · 本机执行输出
  客户端 gzip,服务端不设置                          请求标志位 1、响应标志位 1;请求头 grpc-accept-encoding=gzip;响应头 grpc-encoding=gzip;请求字节 628、响应字节 573;SetSendCompressor 返回 <nil>
  客户端不压缩,服务端 SetSendCompressor(gzip)       请求标志位 0、响应标志位 1;请求头 grpc-accept-encoding=gzip;响应头 grpc-encoding=gzip;请求字节 102562、响应字节 610;SetSendCompressor 返回 <nil>

重复多的数据压到几十分之一,已经压缩的数据(随机字节)压不动,还要花 CPU。

代价

实测 · 本机执行输出
  重复的 JSON 片段 不压缩 1.9 ms;gzip 3.5 ms(1.8 倍)
  类 JSON 文本  不压缩 1.6 ms;gzip 8.9 ms(5.7 倍)
  随机字节       不压缩 1.5 ms;gzip 2.8 ms(1.9 倍)

在带宽几乎无限的回环上,压缩只增加耗时;在带宽受限的网络上才可能划算,需要在真实网络上测。

级别

实测 · 本机执行输出
  级别  1:请求 124969 字节(原始 1048576);往返 8.9 ms
  级别 -1:请求 95455 字节(原始 1048576);往返 9.5 ms
  级别  9:请求 95449 字节(原始 1048576);往返 31.7 ms

gzip.SetLevel 改的是全局设置。最高级别耗时数倍,体积几乎没有变化。

不认识的算法

实测 · 本机执行输出
  服务端不认识 fake:code=Unimplemented message="grpc: Decompressor is not installed for grpc-encoding \"fake\"";响应头里 grpc-accept-encoding=无
  同一个连接上之后不压缩的调用:err=<nil>

服务端不认识就返回 Unimplemented,客户端不会自动退回到不压缩。

分步演示

压缩
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Go 1.27.1 / grpc-go v1.84.0 / protobuf-go v1.36.12 / golang.org/x/net v0.57.0 / protoc 36.2(Homebrew);服务端和客户端都是本机起的 Go 进程(127.0.0.1,端口 9800 以上,不访问任何外部服务);需要看线上字节时用我自己写的 TCP 抓包代理加 golang.org/x/net/http2 的 Framer 解析 HTTP/2 帧;计时用 time.Now 或 perf_counter,数字每次运行会有小幅抖动。线上字节与压缩标志位来自抓包代理(统计两个方向的全部字节,含 HTTP/2 帧开销);耗时是本机回环上的中位数,压缩没有带宽收益,只显示 CPU 成本;“fake”压缩器只在客户端进程注册,服务端放在另一个子进程里;可压缩数据是重复的 JSON 片段,类 JSON 文本是我用代码生成的,真实数据的压缩比会不同
    • 没有验证:snappy、zstd 等其它算法(需要额外的包);真实网络(跨机房、移动网)下压缩能否缩短总时间;流式 RPC 里每条消息分别压缩的开销;WithCompressor 等已废弃的选项;服务端 CPU 占用;压缩与 TLS 结合时的安全问题(例如压缩侧信道,没有测)
    京ICP备2026067502号-1京公网安备11010502063487号