应使用 Protocol Buffers 替代 jsON 作为序列化格式,因其二进制、强类型、字段编号传输,体积减少60%–80%,grpc原生支持;配合grpc-gateway兼顾REST兼容性。

用 Protocol Buffers 替换 json 作为序列化格式
go 微服务间 http 或 gRPC 通信时,默认用 json.Marshal 会导致 CPU 占用高、体积大、解析慢。Protocol Buffers(.proto 定义 + protoc-gen-go 生成代码)是更优选择,尤其在 gRPC 场景下原生支持。
-
json是文本格式,无 schema 校验,字段名重复传输,不压缩;protobuf是二进制、有强类型定义、字段用 tag 编号而非名称,体积通常减少 60%–80% - gRPC 默认使用
protobuf,若用http+json,可改用grpc-gateway暴露 REST 接口,后端仍走 protobuf 编解码 - 注意:不同语言生成的 proto Struct 字段顺序/零值行为可能不一致,务必统一
go_package和option go_package路径,并在 CI 中校验生成代码一致性
service UserService { rpc GetUser(GetUserRequest) returns (GetUserResponse); } message GetUserRequest { int64 user_id = 1; } message GetUserResponse { User user = 1; bool found = 2; }
启用 gRPC 流式传输替代多次 RPC 调用
当服务需要批量获取或实时推送数据(如日志流、监控指标、订单状态变更),反复调用 Unary RPC 会产生显著延迟和连接开销。gRPC 支持 server-streaming、client-streaming 和 bidi-streaming,能复用连接、减少 handshake 开销。
- 避免写循环调用
GetOrderStatus(ctx, &req)100 次;改用StreamOrderStatus(ctx, &StreamReq{Ids: ids}) - 流式响应需注意客户端超时设置:
context.WithTimeout应覆盖整个流生命周期,而非单次Recv() - 服务端流式 handler 中,不要在
Send()前做重载计算;否则会阻塞流,影响其他并发流
共享内存或本地缓存减少跨服务查询
高频读取的只读数据(如配置项、地区列表、汇率)不应每次通过 HTTP/gRPC 调用远程服务获取,应通过本地缓存降低 RT 和下游压力。
- 用
github.com/patrickmn/go-cache或github.com/bluele/gcache实现带 TTL 的内存缓存,避免自己手写锁和过期逻辑 - 缓存 key 建议包含版本号或更新时间戳(如
"region_list_v202405"),便于发布时强制刷新 - 若多个实例需一致性,不要依赖本地缓存同步;改用 redis +
WATCH/MULTI或基于事件驱动的缓存失效(如监听 kafka topicconfig-updated)
避免在传输层做业务逻辑转换
常见反模式:A 服务把原始数据塞进 map[String]Interface{},B 服务再用 json.Unmarshal 解析并映射到结构体——这绕过了编译期类型检查,且无法利用 protobuf 的零拷贝优化。
立即学习“go语言免费学习笔记(深入)”;
- 所有跨服务数据结构必须定义在独立的
api/或proto/仓库中,由 CI 自动生成 Go/java/TS 代码,禁止手动维护 DTO - 禁止在 HTTP handler 中对请求 body 做
json.RawMessage透传;若需兼容旧版,应由网关层完成协议转换,业务服务只对接标准 proto message - 字段新增/删除必须遵守 protobuf 兼容性规则:不重用 field number,可选字段用
optional(proto3),弃用字段加deprecated = true
实际落地中最容易被忽略的是 proto 文件的版本管理和生成代码的 commit 策略——很多人把生成的 *.pb.go 直接提交,导致 diff 失控;正确做法是只提交 .proto,CI 自动检查并生成,失败即阻断发布。