必须确保go_package路径与物理目录一致且匹配go.mod前缀,客户端须用status.FromError解包错误码,流式RPC需单独处理非io.EOF错误并启用KeepAlive保活。
能通不等于能用——微服务里gRPC通信卡在连接假死、错误码丢失、流式阻塞上,不是代码写不对,而是关键参数漏配或生成逻辑错位。
根本原因是 go_package 声明和文件物理路径不一致,不是插件没装对,也不是 proto 写错了语法。
option go_package = "github.com/yourorg/user/v1"; → 生成的 user_grpc.pb.go 必须放在项目目录下 github.com/yourorg/user/v1/ 路径中(Go Modules 模式下,该路径需与 go.mod 的 module 名前缀匹配)--go-grpc_opt=paths=source_relative 参数,否则从子目录执行 protoc 时,生成路径会错位go_package 会导致编译冲突,比如 user.proto 和 auth.proto 都写 option go_package = "pb";,Go 会认为是同一个包,但结构体名重复--go_out 不跑 --go-grpc_out,NewXXXClient 就是 undefined;反过来只跑 --go-grpc_out,pb.XXXRequest 类型就找不到默认 grpc.Dial 是阻塞建连 + 零保活机制,网络抖动、LB 超时、NAT 断连后连接状态不可知,表现就是请求发不出、Recv() 一直 hang。
grpc.WithTimeout(5 * time.Second),否则 DNS 解析慢或 TLS 握手失败时卡几十秒grpc.WithKeepaliveParams(keepalive.ClientParameters{Time: 10 * time.Second}),不然空闲连接被中间设备静默断开,客户端还当它活着grpc.WithTransportCredentials(insecure.NewCredentials()),要用 credentials.NewTLS(tlsConfig)
*grpc.ClientConn 是线程安全的,全局复用一个实例,别每次 RPC 都 Dial —— TCP/TLS 开销太大,压测时连接数爆炸gRPC 默认把所有 error 转成 status.Error(codes.Unknown, ...),上游没法区分是用户不存在还是网络超时,重试、降级、监控全失效。
立即学习“go语言免费学习笔记(深入)”;
status.Errorf(codes.NotFound, "user %d not found", id),不能用 fmt.Errorf 包装,否则状态码丢失if st, ok := status.FromError(err); ok { switch st.Code() { case codes.NotFound: ... } }
status.Status,不能 return fmt.Errorf("xxx: %w", err)
Recv() 返回非 io.EOF 的 error 才代表流异常终止,得单独处理,不能当成普通读取结束流式方法签名变了,Server 参数类型不是 context.Context,而是带 Send()/Recv() 的接口,直接在主线程循环发数据会阻塞整个 handler。
stream.Send() 后检查 error:if err := stream.Send(msg); err != nil { return err }
stream.Context().Done(),一旦收到 cancel 或 deadline,立即退出循环,否则 goroutine 泄漏Recv(),发送前可先调用 stream.SetHeader() 传元数据试探,或改用背压协议(如 ACK 流)pb.UnimplementedXXXServer,否则调用未实现的方法直接 panic,不是返回 UNIMPLEMENTED 状态码最常被跳过的其实是 go_package 路径一致性检查和 status.FromError 解包这两步——它们不报编译错误,但上线后查问题要花三倍时间。