json序列化慢因默认使用反射+接口检查,动态解析标签和类型,嵌套深或含interface{}时开销大;messagepack推荐vmihailenco/msgpack库,需显式msgpack标签,混用时须统一数值类型并加双标签。

JSON序列化为什么慢?json.Marshal 的实际开销在哪
Go 的 json.Marshal 默认走反射+接口类型检查,每次调用都要动态解析结构体字段标签、类型映射、空值逻辑。尤其当结构体嵌套深、字段多、含指针或 interface{} 时,性能抖动明显。
常见错误现象:cpu profile 显示 encoding/json.(*encodeState).marshal 占比超 40%,HTTP 接口 P95 延迟突增但 QPS 不高;用 go tool trace 能看到大量 GC 前后短时停顿。
- 避免在 hot path(如 RPC handler 内层循环)反复调用
json.Marshal,尤其别对同一结构体反复序列化 - 字段名含下划线或大小写混用时,
json:"user_id,string"这类标签会触发额外字符串转换,能不用stringtag 就不用 - 如果结构体字段固定且无动态字段,用
easyjson或ffjson生成静态 marshaler,可减少 60%+ 反射开销
MessagePack 在 Go 里怎么选库?msgpack vs go-msgpack vs codec
Go 生态主流是 github.com/vmihailenco/msgpack/v5(简称 vmihailenco/msgpack),不是老版 go-msgpack(已归档),也不是 ugorji/go/codec(维护节奏慢、API 复杂)。前者默认开启紧凑编码、支持 time.Time 原生序列化、有 UseCompactEncoding(true) 控制字节长度。
使用场景:微服务间二进制通信、Kafka 消息体、Redis 缓存 value —— 只要两端都可控,优先上 MessagePack。
立即学习“go语言免费学习笔记(深入)”;
-
vmihailenco/msgpack默认不兼容 JSON 字段名(比如json:"user_id"不自动映射),需显式加msgpack:"user_id"标签,否则字段被忽略 - 若结构体含
map[string]interface{}或[]interface{},vmihailenco/msgpack序列化后体积比 JSON 小但反序列化更慢,此时应改用具体类型(如map[string]string) - 注意
msgpack.NilMapAsEmpty和msgpack.NilSliceAsEmpty这两个解码选项,不设的话,nil map/slice 会被解成nil,而 JSON 默认解为空对象/数组,跨协议时容易出 bug
JSON 和 MessagePack 混用时的兼容陷阱
不能假设 json.Marshal(v) 和 msgpack.Marshal(v) 输出的字节流能互相 decode —— 类型系统不同:JSON 没有 int8/int16,所有数字都是 float64 或 int64;MessagePack 区分 uint8、int32 等,且 bool 是独立 type。
典型错误:前端发来 JSON {"count": 1},后端用 msgpack.Unmarshal 解到 struct,count 字段变成 0;或者反过来,MessagePack 发 uint8(255),JSON 解成 255.0 导致类型断言失败。
- 跨协议传输必须约定 schema,不要依赖“看起来一样”。推荐用
struct字段加双标签:json:"count" msgpack:"count" - 数值字段统一用
int64或float64,避免int(平台相关)和uint(JSON 无原生支持) - 如果必须动态解析,用
json.RawMessage或msgpack.Raw延迟解码,别急着转成 interface{}
序列化性能实测的关键控制点
别只看 “xxx 库快 3 倍” 这种 benchmark,真实瓶颈常在内存分配和 GC。比如 json.Marshal 返回 []byte,每次调用都 new 一块内存;msgpack.Marshal 同样如此,但它的 byte slice 平均小 30%~50%,间接降低 GC 压力。
实测建议直接跑 go test -bench=.,但要注意:
- 用
b.ReportAllocs()看每操作分配多少 bytes,比纯 ns/op 更反映线上表现 - 测试数据必须贴近真实:字段数、嵌套深度、字符串平均长度、是否有空字段 —— 用生产日志抽样构造 fixture
- 禁用
GOGC=off测试,否则 GC 假象会掩盖内存增长问题;也别用runtime.GC()强制回收,它本身就有开销
真正难的是字段语义一致性:JSON 里 "null" 和空字符串、零值字段,在 MessagePack 里可能全变成缺失字段。这种差异不会报错,但会让下游逻辑静默失效。










