grpc默认使用protocol buffers编解码器;虽proto非强制,但go中默认硬编码绑定encoding/proto,可通过实现grpc.codec接口替换,如json需注意字段名、零值处理及性能差异。

gRPC 默认用什么编解码器?proto 不是必须的?
gRPC 在 Go 里默认只认 proto,底层硬编码绑定了 encoding/proto 和 google.golang.org/protobuf。但“默认”不等于“唯一”——只要实现 grpc.Codec 接口,就能换掉它。
常见误解是以为必须写 .proto 文件才能用 gRPC;其实只要客户端和服务端用同一套 Marshal/Unmarshal 规则,连 JSON 都能跑通(当然得自己处理字段映射和类型安全)。
-
grpc.Codec要求实现四个方法:Marshal、Unmarshal、Name、String -
Name()返回的字符串会出现在Content-Type里,比如application/grpc+json,服务端靠它路由到对应 codec - 如果没显式注册 codec,gRPC 会 fallback 到内置的
proto实现,不会报错,但你的自定义逻辑根本不会触发
怎么注册自定义 codec?grpc.RegisterCodec 为什么经常失效?
必须在 grpc.Dial 或 grpc.NewServer 之前调用 grpc.RegisterCodec,否则注册无效——因为 gRPC 初始化时会把已注册的 codec 快照进内部 map,之后再注册就只是往全局变量塞,没人读。
另一个坑:注册时传的 codec 实例,会被多个连接/请求共享。如果你的 Marshal 方法里用了非线程安全的缓存(比如复用 bytes.Buffer 但没重置),就会出现数据错乱或 panic。
立即学习“go语言免费学习笔记(深入)”;
- 推荐在
init()函数里注册,确保早于任何 dial/server 创建 - 别在
Marshal中复用可变对象;如需性能优化,用sync.Pool管理 buffer - 检查是否重复注册:多次调用
RegisterCodec同一个 name 会覆盖,但不会报错,容易误以为生效了
用 jsonpb 替代 proto?小心字段名和空值处理差异
有人想无缝切 JSON,直接拿 github.com/golang/protobuf/jsonpb(旧)或 google.golang.org/protobuf/encoding/protojson(新)包封装成 codec。这可行,但行为和 proto 有本质区别:
-
protojson默认忽略零值字段(如int32: 0、string: ""),而 proto 二进制里这些字段一定存在;客户端若依赖字段存在性判断,会出 bug - JSON 字段名默认是 camelCase,proto 是 snake_case;必须用
protojson.MarshalOptions{UseProtoNames: true}才对齐 -
Duration和Timestamp类型在 JSON 里序列化为字符串(如"10s"),而 proto 是二进制结构;跨语言互通时尤其要注意
性能差很多?别让 codec 成为瓶颈
自定义 codec 的开销主要在两处:序列化本身(比如 JSON 比 proto 慢 3–5 倍),以及反射调用(如果 codec 通用适配任意 struct)。但更隐蔽的瓶颈常在错误处理路径上——比如 Unmarshal 失败时返回了未包装的 fmt.Errorf,gRPC 会把它当普通响应体发回,客户端收到的是无意义的 JSON 错误字符串,而不是标准的 status.Error。
- 务必在
Unmarshal里把解析错误转成status.Error(codes.Internal, ...),否则错误传播链断裂 - 避免在 codec 里做日志、网络请求、锁竞争等重操作;它是在 gRPC 请求关键路径上的同步函数
- 如果只是想支持多种格式(如同时接受 proto 和 JSON),不要在单个 codec 里 if-else 分支,而是注册多个 codec,靠
Content-Type自动分发
真正难的不是写一个能跑的 codec,而是让它的行为在边界条件下(空请求、超大 payload、并发异常)和原生 proto 保持一致。这点很容易被跳过测试。










