gRPC默认基于HTTP/2实现长连接,无需手动开启;需重点配置服务端keepalive、客户端复用ClientConn及PermitWithoutStream开关,并正确使用--go_out和--go-grpc_out双参数生成代码。

gRPC 默认就是长连接,别再手动“开启”
Go 的 gRPC 客户端和服务端默认基于 HTTP/2,连接复用是协议原生行为,不需要额外配置“启用长连接”。真正要动手的,是防止连接被中间设备或服务端主动断开。
常见错误现象:rpc error: code = Unavailable desc = transport is closing,往往不是代码写错了,而是空闲连接被服务端或 Nginx/Envoy 等代理强制关闭。
- 服务端必须显式配置
grpc.KeepaliveParams,尤其是MaxConnectionIdle(如30 * time.Minute)和Time(如10 * time.Second),否则默认不发 ping 帧 - 若用了反向代理,需同步配置其 keepalive:Nginx 要设
keepalive_timeout,Envoy 要配idle_timeout -
MaxConnectionIdle设为 0 表示禁用空闲超时,但容易导致连接泄漏,不推荐
客户端必须复用 *grpc.ClientConn 实例
每次调用都 grpc.Dial 或 grpc.DialContext,等于每请求建一次 TCP 连接,netstat 里会看到大量 TIME_WAIT,QPS 上不去,TLS 握手开销也白费。
正确做法是全局复用一个 *grpc.ClientConn,并在程序启动时初始化、退出时关闭。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 不要在 handler 里临时 Dial;也不要为每个请求 new 一个 client
- 使用
grpc.WithTransportCredentials(如 TLS)或grpc.WithInsecure()即可,底层连接池自动复用 - 如果需要控制连接建立行为,可用
grpc.WithBlock()阻塞等待连接就绪,但生产环境慎用,避免启动卡死
protoc 生成代码必须同时用 --go_out 和 --go-grpc_out
很多报错源于旧教程残留命令:protoc --grpc_out=. 已废弃,新版本 protoc-gen-go-grpc 不识别该参数,直接报 unrecognized option '--grpc_out' 或生成文件里缺 RegisterXXXServer 函数。
必须两个参数并存,且顺序无关:
-
--go_out=.→ 生成*.pb.go(消息结构体) -
--go-grpc_out=.→ 生成*_grpc.pb.go(服务接口、注册函数、客户端 stub) - 确保
protoc-gen-go-grpc在$PATH中,且版本 ≥ v1.60,与google.golang.org/grpc兼容
TCP 层长连接需单独实现心跳与超时
当不用 gRPC、改用裸 TCP(如自研推送网关、设备直连)时,HTTP/2 的保活机制失效,必须自己处理心跳和连接存活。
核心不是“发 PING”,而是“检测无响应”:
- 用
conn.SetReadDeadline(time.Now().Add(10 * time.Second))控制读超时,每次成功读取消息后重置 deadline - 心跳包由客户端定时发送(如每 5 秒
conn.Write([]byte("PING"))),服务端收到PONG后更新最后活跃时间 - 在读循环中定期检查
time.Since(lastHeartbeat) > 15 * time.Second,超时则主动 close conn - 务必配合
context.Context控制 goroutine 生命周期,避免连接关闭后仍往已关闭 conn 写数据 panic
gRPC 的长连接是“默认开着但容易被掐断”,TCP 长连接是“得亲手焊牢每一段逻辑”。最容易被忽略的,是代理层和客户端实例复用这两头——它们不出错时一切正常,一出错就表现为偶发性连接重置或性能陡降,排查时容易绕远路。

















