json.Marshal在高并发下易成瓶颈,因其依赖反射和interface{}类型擦除,每次调用需动态解析字段、检查标签、分配临时容器,开销大且无法复用,CPU常卡在reflect.Value.Interface或encoding/json.(*encodeState).marshal。

为什么 json.Marshal 在高并发下容易成瓶颈?
因为 json.Marshal 默认使用反射 + interface{} 类型擦除,每次调用都要动态解析结构体字段、检查标签、分配临时 map/slice,开销大且无法复用。压测时常见 CPU 卡在 reflect.Value.Interface 或 encoding/json.(*encodeState).marshal。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 避免在 hot path(如 HTTP handler 内)直接对大结构体或高频请求调用
json.Marshal - 若结构体字段固定,优先用代码生成替代运行时反射
- 注意
json.Marshal不是线程安全的——它内部复用了sync.Pool的*encodeState,但 pool 本身没问题;真正风险在于你传入含 mutex 或非并发安全字段的 struct(比如未加锁的map)
用 easyjson 或 ffjson 生成静态序列化代码
它们在构建期扫描 struct 标签,生成专用的 MarshalJSON / UnmarshalJSON 方法,绕过反射。性能通常比标准库高 2–5 倍,GC 压力显著下降。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 安装:
go install github.com/mailru/easyjson/...@latest - 为结构体打标签后运行:
easyjson -all user.go,会生成user_easyjson.go - 调用时直接用生成的
User.MarshalJSON(),而非json.Marshal(user) - 注意:生成代码依赖字段顺序和导出性,如果 struct 字段改名或变成 unexported,必须重新生成
jsoniter 能否安全替代标准库?
可以,但需谨慎。jsoniter 是兼容标准库 API 的 drop-in 替代,通过 unsafe + 预编译类型信息加速,无需代码生成,适合快速优化存量项目。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 导入替换:
import json "github.com/json-iterator/go",其余代码不动 - 开启 fast-path(推荐):
json.ConfigCompatibleWithStandardLibrary已默认启用,但若你手动创建jsoniter.API实例,记得调用.RegisterExtension(...)来支持自定义 marshaler - 陷阱:jsoniter 对
interface{}的处理仍走反射,若你大量使用map[string]interface{}或[]interface{},性能提升有限;此时应转为强类型 struct - 注意其
Get()方法返回的是 view 类型,多次调用.ToString()会重复拷贝,应缓存结果
什么时候该自己写 MarshalJSON 方法?
当你有明确的性能热点,且结构体逻辑简单、字段稳定,比如日志事件、监控指标、API 响应体。手写能彻底控制内存分配和字段遍历顺序,避免任何间接调用开销。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
bytes.Buffer或预分配[]byte拼接,避免字符串拼接产生的多次 alloc - 字段值为空时跳过(如
omitempty),但别用reflect判断,直接写死比较(if u.Name != "") - 时间字段用
t.UnixMilli()而非t.Format(...),后者涉及格式解析和字符串分配 - 别忘了实现
UnmarshalJSON—— 如果只序列化不反序列化,HTTP 响应场景下可省略,但测试和调试会受限
最常被忽略的一点:无论用哪种方案,只要结构体里嵌了 map、slice 或指针,就得确认它们的并发安全性。序列化过程不会加锁,但数据可能正在被其他 goroutine 修改——这不是序列化库的问题,而是数据竞争的根源。











