protobuf 比 json 快且体积小,但仅适用于高频、大体积数据交换场景;配置文件或简单 api 不推荐替换,否则增加维护成本和调试难度。

Protobuf 比 JSON 快,但不是所有场景都值得换
Go 里用 json.Marshal 和 json.Unmarshal 写惯了,一上来就切 proto.Marshal 很容易掉坑里。Protobuf 确实快——序列化/反序列化耗时通常只有 JSON 的 1/3~1/5,体积小 60% 以上,但前提是:你真在瓶颈路径上做高频、大体积数据交换,比如微服务间 RPC、日志批量上报、实时消息推送。如果只是偶尔配个 config 文件、返回个简单 API 响应,换 Protobuf 反而增加维护成本和调试难度。
常见错误现象:json: unsupported type: map[interface {}]interface{} 这类错误在 JSON 里很常见,但换成 Protobuf 后直接编译报错(字段类型必须显式定义),看似麻烦,其实是提前暴露结构混乱问题。
- 使用场景优先级:gRPC 接口 > 内部服务通信 > 高频埋点上报 > Web API 返回(不推荐)
- Protobuf 要求先写
.proto文件、生成 Go 结构体,JSON 直接 struct tag 就能跑 - 兼容性上,Protobuf v2 和 v3 的
omitempty行为不同,v3 默认不发零值字段,JSON 却依赖json:",omitempty"手动控制
struct tag 和 proto message 字段对不上,是性能杀手
很多人以为只要把 JSON struct 改成 protobuf struct,再套个 proto.Marshal 就完事了。实际一压测发现,比 JSON 还慢。根本原因是字段映射没对齐:比如 JSON struct 里有个 CreatedAt int64 `json:"created_at"`,对应 proto 字段写成 int32 created_at = 1;,反序列化时会触发 runtime 类型转换和边界检查;更糟的是嵌套 map 或 interface{},Protobuf 根本不支持,硬塞会导致 panic 或静默截断。
正确做法是让 proto 定义严格覆盖数据契约,而不是迁就旧代码结构。
立即学习“go语言免费学习笔记(深入)”;
- 避免在 proto 中用
bytes存 JSON 字符串——这等于套娃序列化,CPU 白烧 - 时间字段统一用
google.protobuf.Timestamp,别自己搞int64+ 单位注释 - 枚举值必须用
enum定义,别用字符串或 int 模拟,否则失去类型安全和紧凑编码优势
go-json vs encoding/json,有时候比换 Protobuf 更有效
如果你暂时没法改协议,又卡在 JSON 性能上,先别急着动 protobuf。Go 生态里 go-json(原名 json-iterator)是个低成本高回报的选项:它完全兼容标准库接口,只需替换 import,就能在多数场景下提速 2~4 倍,尤其对深层嵌套、大量 string 字段效果明显。它绕过了反射,用 code generation + unsafe 操作底层字节。
但要注意:它不支持 json.RawMessage 的某些边缘行为,且 panic 错误信息不如标准库友好。
- 启用方式:把
import "encoding/json"换成import json "github.com/goccy/go-json" - 不兼容点:自定义
UnmarshalJSON方法里若用了reflect.Value,可能出错 - 性能敏感服务上线前,建议用 pprof +
benchstat对比json.Marshal和json.Marshal(go-json 版)的真实开销
Protobuf 编码后不能直接 human-readable,调试链路会断
这是最容易被忽略的实际代价。JSON 日志里一眼能看出 "user_id":123,"status":"active",而 Protobuf 是二进制 blob,fmt.Printf("%s", data) 输出一堆乱码。线上查问题时,你得额外加一层 protojson.Marshal 或用 protoc --decode_raw 解码,排查延迟直接翻倍。
更麻烦的是中间件:Nginx、Envoy、Prometheus exporter 默认不理解 Protobuf payload,想打 access log 或做 metrics 聚合,就得写插件或改配置。
- 开发期建议开启
protojson.MarshalOptions{Indent: " "},只在 debug build 中用 - 日志记录关键字段(如 trace_id、status_code)不要依赖整个 payload,单独提取打点
- 如果用 gRPC-Gateway,它自动生成 JSON 接口,但注意
google.api.HttpRule配置不当会导致 body 映射丢失字段











