bigywater.tech
gRPC · 24

消息大小限制:4 MiB 默认值与相关错误

gRPC 单条消息默认最大多少?超了是什么错?在哪一端检查?压缩能不能绕过?这篇用二分查出精确的临界点,并看了拒绝之前线上传了多少数据。

4194304 字节
默认接收上限,比的是序列化后的整条消息;payload 最多 4194299 字节
0.1%
服务端拒绝 64 MiB 请求之前,线上只传了消息的这个比例(65670 字节)
201 KiB → 100 MiB
gzip 线上只有 201 KiB,但解压后超限,仍被拒绝

默认值

实测 · 本机执行输出
  最大能通过的 payload 4194299 字节 → 序列化后的消息 4194304 字节;再多 1 字节的 payload 4194300 → 消息 4194305 字节(4 MiB = 4194304)
  再多一字节时的错误:code=ResourceExhausted message="grpc: received message larger than max (4194305 vs. 4194304)"

服务端和客户端接收的默认上限都是 4 MiB;发送的默认上限是 MaxInt32(基本不限)。比较的是序列化后的整条消息。

错误

实测 · 本机执行输出
  服务端收 5 MiB 的请求:code=ResourceExhausted message="grpc: received message larger than max (5242885 vs. 4194304)"
  客户端收 5 MiB 的响应:code=ResourceExhausted message="grpc: received message larger than max (5242885 vs. 4194304)"
  客户端 MaxCallSendMsgSize(1 MiB) 发 2 MiB:code=ResourceExhausted message="trying to send message larger than max (2097157 vs. 1048576)";线上客户端发出的 HEADERS 1 个

超限一律是 ResourceExhausted。服务端拒绝请求、客户端拒绝响应、客户端本地拒绝发送,消息文字不同。

拒绝得很早

实测 · 本机执行输出
  64 MiB 的请求:code=ResourceExhausted message="grpc: received message larger than max (67108869 vs. 4194304)";耗时 14.7 ms;客户端→服务端的线上字节 65670(0.06 MiB,占消息的 0.1%)

接收方读到 5 字节的消息前缀就知道长度,超限立刻结束这个流,不会等整条消息传完。

按消息计

实测 · 本机执行输出
  10 条 3 MiB:Send 错误 <nil>;服务端数到 10 条;最终 成功(总共 30 MiB)
  先 1 MiB 再 5 MiB:code=ResourceExhausted message="grpc: received message larger than max (5242885 vs. 4194304)"

限制是单条消息的,流里可以发很多条,总量不受限制。

压缩

实测 · 本机执行输出
  默认上限 4 MiB              :code=ResourceExhausted message="grpc: received message after decompression larger than max 4194304";线上客户端→服务端 205887 字节(201.1 KiB)
  MaxRecvMsgSize(128 MiB) :成功;线上客户端→服务端 205917 字节(201.1 KiB)

压缩后的大小不算,解压后的大小才算,压缩不能绕过限制。

调大

实测 · 本机执行输出
  发 32 MiB 请求:成功,耗时 29.4 ms;收 32 MiB 响应:成功,耗时 21.8 ms

服务端 MaxRecvMsgSize/MaxSendMsgSize,客户端 MaxCallRecvMsgSize/MaxCallSendMsgSize,要两头都设。内存占用随之上升。

分步演示

消息大小限制
本步的对象

    本文用到的工具与边界

    • 本机 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,数字每次运行会有小幅抖动。临界点用二分得到,线上字节来自抓包代理,耗时是本机回环;默认值读自 clientconn.go、server.go;“拒绝前传了 65670 字节与流控窗口接近”是推断
    • 没有验证:服务端发送上限 MaxSendMsgSize 超限时的错误;WithDefaultCallOptions 与单次 CallOption 的优先级;大消息对内存和 GC 的影响(第 32 篇);经过网关或代理(Envoy、nginx)时它们自己的大小限制;流式 RPC 里服务端发送过大消息时客户端的表现
    京ICP备2026067502号-1京公网安备11010502063487号