Go微服务RPC应首选gRPC而非net/rpc;proto3需显式声明json_name并用snake_case命名;服务端方法签名须为func(ctx context.Context, req T) (R, error);客户端连接须全局复用并配置Keepalive;流式接口需每次Send后检查error及ctx.Err()。

Go 微服务里用 RPC,别一上来就写 rpc.Register 或直接开 grpc.Dial。标准库 net/rpc 仅限 Go 内部互通,gRPC 才是生产级选择;但接口设计不当,再快的协议也扛不住字段膨胀、版本断裂和上下文泄漏。
proto3 定义必须显式声明 json_name 且字段全小写下划线
Go 结构体字段名是 UserID,但 proto 默认生成的 JSON key 是 userid(全小写),而 gRPC 的 HTTP/JSON 网关或前端调试工具依赖的是带下划线的规范格式(如 user_id)。不显式指定,就会出现客户端收不到字段、日志查不到值、Swagger 文档错乱等问题。
实操建议:
- 所有 message 字段都用
snake_case命名,并加[json_name = "xxx"],例如:string user_id = 1 [json_name = "user_id"]; - 避免使用
optional(proto3 默认就是 optional),否则生成代码会引入指针,增加 nil 判断负担 -
repeated字段对应 Go 切片,但空切片([]int{})和 nil 切片(nil)序列化行为不同:前者发空数组,后者不发该字段——统一初始化为空切片更可控
服务端方法签名必须严格匹配 func(ctx context.Context, req *T) (*R, error)
gRPC 要求每个 RPC 方法第一个参数是 context.Context,第二个是请求结构体指针,返回值必须是响应结构体指针 + error。漏掉 context 或返回类型错位,会导致 protoc-gen-go-grpc 生成失败,或运行时报 method has wrong number of ins/outs。
立即学习“go语言免费学习笔记(深入)”;
实操建议:
- 不要在 handler 里忽略
ctx—— 超时、取消、鉴权元数据都靠它传递 - 响应结构体字段名要和 proto 一致,且导出(首字母大写),否则 JSON 序列化为空对象
- 错误必须用
status.Error()包装,不能直接 returnerrors.New,否则客户端无法解析成标准 gRPC 错误码
客户端连接必须全局复用,且显式配置 Keepalive
每次调用都 grpc.Dial,等于每秒新建数百个 TCP 连接,触发 TIME_WAIT 暴涨、TLS 握手开销激增、DNS 频繁解析,最终表现为 connection refused 或 transport is closing。
实操建议:
- 在应用初始化阶段创建单例
*grpc.ClientConn,注入到各业务模块,而非函数内临时 Dial - 启用 keepalive:
grpc.WithKeepaliveParams(keepalive.ClientParameters{Time: 30 * time.Second, Timeout: 10 * time.Second}) - 连接失败时不要 panic,应配合重试逻辑(如
grpc.WithConnectParams(grpc.ConnectParams{Backoff: backoff.DefaultConfig})) - 进程退出前务必调用
conn.Close(),否则 goroutine 和 fd 泄漏
流式接口必须检查 ctx.Err() 并逐次判断 Send() 返回值
服务端用 stream.Send() 推送大量数据时,若客户端断连、网络拥塞或消费过慢,gRPC 内部缓冲区会堆积,最终阻塞甚至 panic。错误日志常为 transport: failed to write a frame 或 context canceled,但实际原因被掩盖。
实操建议:
- 每个
stream.Send()后必须检查 error:if err := stream.Send(msg); err != nil { return err } - 循环中持续检查
if ctx.Err() != nil { return ctx.Err() },不能只在开头 check 一次 - 避免无节制推送:可结合
stream.SetHeader()发送批次元数据,或改用双向流,由客户端通过Recv()显式确认就绪
最易被忽略的一点:gRPC 接口不是“定义完就能跑”,而是“定义即契约”——字段增删、类型变更、默认值调整,都会破坏二进制兼容性。哪怕只是把 int32 改成 int64,旧客户端也可能静默丢数据。上线前必须做 wire-level 兼容性验证,不能只靠单元测试。


















