gRPC默认最大消息体为4MB,超限即报RESOURCE_EXHAUSTED;根本原因是grpc.MaxRecvMsgSize和grpc.MaxSendMsgSize默认值限制,需显式调大配置以支持大字段传输。

gRPC 默认最大消息体是 4MB,超了就报 RESOURCE_EXHAUSTED
这是 Kratos 项目里最常遇到的传输失败现象之一:服务端日志没报错,客户端却收到 status code: RESOURCE_EXHAUSTED,或者直接断连。根本原因不是业务逻辑问题,而是 gRPC 的默认限制在起作用——grpc.MaxRecvMsgSize 和 grpc.MaxSendMsgSize 默认都是 4MB(即 4 * 1024 * 1024 字节)。
如果你的 .proto 里定义了大字段(比如 bytes data 存原始图片、PDF 或加密密文),又没显式调大限制,就会触发这个错误。
- 服务端需在创建 gRPC Server 时传入选项:
grpc.MaxRecvMsgSize(32 (32MB) - 客户端调用时也得同步加大:
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(32 - Kratos 中通常在
internal/server/grpc.go的NewGRPCServer和cmd/server/main.go的 client 初始化处设置
Protobuf 编码本身不压缩,大数据包要自己处理
很多人以为改大 gRPC 限制就万事大吉,结果发现 32MB 消息依然卡顿、OOM 或超时。这是因为 Protobuf 是二进制编码,但不带压缩;原始数据(尤其是文本类 JSON、XML、日志块)用 bytes 字段直塞进去,体积几乎不缩减。
真实场景中更稳妥的做法是分层控制:
- 业务层判断是否真需要一次传完整数据:比如文件上传,优先走对象存储 + URL 回传,而非塞进 gRPC body
- 必须走 gRPC 时,先在客户端用
gzip压缩bytes字段,服务端解压(注意加Content-Encoding: gzip自定义 header 或协议字段标识) - Protobuf message 内避免嵌套过深或重复字段,用
oneof减少无效字段序列化开销
Kratos + gRPC 的 Wire 注入和中间件里容易漏掉限制配置
在 Kratos 项目中,gRPC Server 往往通过 wire.Build 构建,而 grpc.ServerOption 如果没显式注入到 ProviderSet,就会被忽略——即使你在 NewGRPCServer 函数里写了 grpc.MaxRecvMsgSize,Wire 也可能用默认构造函数覆盖掉。
检查点很具体:
- 确认
ProviderSet中包含自定义的GRPCServer构造函数,且该函数返回值明确传入了 size 选项 - 不要只改
NewGRPCServer函数签名,还要确保wire.Build调用链最终用了它(go generate ./后检查生成的wire_gen.go是否含对应参数) - 如果用了 Kratos 的
transport/http和transport/grpc混合部署,HTTP 侧的 BodySizeLimit(如echo.HTTPServer的MaxRequestBodySize)也得单独配,它和 gRPC 限制互不影响
调试时别只看 error log,要抓 wire logs 和 gRPC metadata
RESOURCE_EXHAUSTED 错误本身不带 payload 大小信息,光看日志很难定位是发超了还是收超了。建议打开 gRPC 的详细日志:
- 启动时加环境变量:
GRPC_GO_LOG_SEVERITY_LEVEL=info GRPC_GO_LOG_VERBOSITY_LEVEL=2 - 在客户端调用前启用
grpc.WithStatsHandler,记录每次 call 的inHeader/inTrailer,观察grpc-encoding和实际 recv size - Kratos 的
transport.GRPCClient可以 wrap 一层拦截器,在UnaryClientInterceptor里打印req的 proto.Size(),快速验证是否真超限
真正麻烦的不是调大数字,而是大小改完后没同步更新 Wire 注入链,或者 HTTP/gRPC 双通道只调了一边——这种问题在线上压测时才暴露,但日志里没有任何提示。











