必须用protoc生成代码,因Go无法编译期校验RPC签名,手写易漏context参数、未实现方法或类型不匹配;需安装protoc-gen-go与protoc-gen-go-grpc插件,正确配置go_package选项并同时指定--go_out和--go-grpc_out参数。

别用 net/rpc 做新项目,直接上 gRPC —— 它才是 Go 生产环境远程调用的事实标准。
为什么必须用 protoc 生成代码,不能手写?
gRPC 的服务接口和消息结构由 .proto 文件定义,Go 运行时无法在编译期检查 RPC 方法签名是否合法。手写 *_grpc.pb.go 几乎必然出错:比如忘记实现 UnimplementedXXXServer 基类、漏掉 context.Context 参数、返回值类型不匹配等。protoc 插件生成的代码自带完整骨架、类型约束和空实现兜底,还能随 proto 变更自动同步。
常见错误现象:cannot use &MyService{} as proto_demo.GreeterServer(类型不满足接口)、missing method SayHello(没实现方法)、context.Context missing(参数位置或类型错)。
实操建议:
- 确保已安装两个插件:
protoc-gen-go和protoc-gen-go-grpc,且都在$PATH中 -
.proto文件头部必须有option go_package = "./;your_pkg_name";,否则生成路径和包名会错乱 - 生成命令必须同时指定
--go_out和--go-grpc_out,缺一不可 - 推荐加
--go_opt=paths=source_relative和--go-grpc_opt=paths=source_relative,避免生成绝对路径导入
客户端 Dial 失败的三个高频原因
rpc.Dial 是旧式 net/rpc 的用法,gRPC 客户端必须用 grpc.Dial,且默认不支持明文 HTTP/1.1。Dial 失败最常卡在这三处:
常见错误现象:connection refused(端口没监听)、transport: Error while dialing dial tcp: operation was canceled(超时)、rpc error: code = Unavailable desc = connection closed before server preface received(HTTP/2 协商失败)。
实操建议:
- 服务端未启用 TLS 时,客户端必须显式禁用加密:
grpc.WithTransportCredentials(insecure.NewCredentials()) - 地址格式必须是
"127.0.0.1:5555",不能带http://或https:// - 连接后立即调用前,建议加简单健康检查:
conn.GetState() == connectivity.Ready,避免在Call时才暴露连接问题 - 不要复用
grpc.ClientConn前不检查是否已关闭;长期运行的服务应配合grpc.WithKeepaliveParams防连接僵死
服务端注册失败:RegisterGreeterServer 无反应?
gRPC 服务端不是靠“注册函数”启动的,而是靠 grpc.Server 实例的 Serve() 启动监听并分发请求。RegisterGreeterServer(s, srv) 只是把实现挂到内部路由表,如果 s.Serve(lis) 没执行或 panic 退出,服务根本不会响应任何请求。
常见错误现象:服务进程运行中但 telnet 127.0.0.1 5555 直接拒绝、grpcurl 报 Failed to dial target host、日志里完全没输出。
实操建议:
- 务必检查
net.Listen是否成功,错误要panic或记录,不能忽略 -
grpc.NewServer()可传入选项,如grpc.UnaryInterceptor用于日志或鉴权,但新手先保持空参 - 服务启动后,用
grpcurl -plaintext localhost:5555 list验证服务是否可发现(需提前装grpcurl) - 若用 Docker,注意端口映射和
127.0.0.1绑定问题:服务端应监听0.0.0.0:5555,而非仅127.0.0.1
Call 调用返回 context.DeadlineExceeded 怎么办?
gRPC 所有 RPC 方法第一个参数都是 context.Context,它控制整个调用生命周期。默认 context 不设超时,但生产环境必须设,否则一个慢请求会拖垮整个连接池。出现 context.DeadlineExceeded 不代表服务挂了,只说明客户端等不及了。
性能影响:不设 timeout 会导致 goroutine 泄露;设太短则误判失败;设太长则阻塞资源。
实操建议:
- 永远不要用
context.Background()直接调用,至少加context.WithTimeout(ctx, 5*time.Second) - 对不同接口分级设超时:查询类 2s,写入类 5s,批量导出类 30s
- 错误处理必须检查
status.Code(err),而不仅是err != nil;codes.DeadlineExceeded应重试或降级,不是直接报错 - 服务端也可用
ctx.Done()主动感知客户端取消,及时释放 DB 连接或文件句柄
真正容易被忽略的是:gRPC 错误不是 Go 原生 error 链,必须用 status.Convert(err) 解包才能拿到真实 code 和 message;很多调试卡在以为自己拿到了错误详情,其实只是 err.Error() 的字符串包装。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











