gRPC Stream 是基于 HTTP/2 的语义抽象,非自动长连接;需正确定义 proto 中的 stream 关键字(客户端流、服务端流、双向流),避免语法错误导致生成代码缺失;服务端 Send() 需 err 检查,禁复用 message 实例;客户端 Recv() 必须显式循环调用;Keepalive 与重连需手动配置,生产环境应封装指数退避重连。

gRPC Stream 本身不是“长连接开关”,而是基于 HTTP/2 流的语义抽象;只要定义了 stream 关键字并正确生成代码,底层自动复用 TCP 连接——但连接是否真正“长”、能否自动重连、流是否稳定,全靠配置和实现细节决定。
proto 文件里 stream 怎么写才不报错
写错位置或漏掉 stream,protoc 生成的 Go 代码会缺失关键类型(比如 XXXServer 或 XXXClient 接口),后续编译直接失败。
-
rpc Get(stream Request) returns (Response)→ 客户端流(客户端发多次,服务端回一次) -
rpc Put(Request) returns (stream Response)→ 服务端流(客户端发一次,服务端推多次) -
rpc Chat(stream Message) returns (stream Message)→ 双向流(双方各自独立收发) - 不能写成
rpc Bad(Request) returns (Response)后加注释说“这是流”,gRPC 不认注释;也不能把stream放在 message 字段里,如repeated bytes data,这会导致序列化失控和 OOM
服务端实现时 goroutine 卡死的三个常见原因
看着只是循环调用 Send(),但实际运行中极易 hang 住,表现为 CPU 低、连接数持续上涨、新请求无响应。
-
Send()是同步阻塞调用:客户端断开、网络抖动、接收方未及时Recv(),都会让Send()无限等待。必须对每次Send()做err检查,遇到非nil错误立即return - 别手动传
context.Context参数:服务端 handler 签名只能是func(*Req, XXXServer) error,额外加context.Context会导致生成代码不匹配;要用server.Context()获取上下文 - 别复用同一个 proto message 实例:反复赋值再
Send(),底层 buffer 被覆盖,客户端可能收到上一轮的脏数据;每次Send()前应新建实例或显式清空字段
客户端读取 ServerStream 必须手动循环调用 Recv()
很多人误以为 stream 是 channel,想用 for range stream,但 Go 的 gRPC client stream 不支持这种语法——Recv() 是单次拉取,不调就不会推进。
- 标准模式:
for { res, err := stream.Recv(); if err != nil { break }; handle(res) } -
err == io.EOF表示服务端正常关闭流;err != nil && err != io.EOF(如rpc error: code = Canceled desc = context canceled)表示异常中断,需按业务逻辑处理重连或降级 - 若需节流,用
time.Ticker控制调用频率;不要在Recv()后硬加time.Sleep(),这会拖慢整个流的响应窗口,尤其在双向流中影响对方发送节奏
Keepalive 和重连不是默认开启的
HTTP/2 底层支持多路复用和流复用,但 gRPC 默认不开启保活探测,也不自动重连。服务端重启后,客户端常卡在 transport is closing 或 connection refused,必须主动干预。
- 客户端 dial 时要显式配置
keepalive.ClientParameters,特别是PermitWithoutStream: true,否则空闲时不会发 ping - 遇到
Unavailable或DeadlineExceeded错误,不能简单重试原 stream(已失效),而应重建 client stub 并发起新 stream 调用 - 生产环境建议封装带指数退避的重连逻辑,例如用
backoff.Retry+ 自定义backoff.BackOff,避免雪崩式重连冲击服务端
真正难的不是写出第一个 stream 示例,而是让成百上千个并发 stream 在网络波动、服务滚动重启、客户端频繁上下线的情况下,既不堆积 goroutine,也不丢消息,还不误判连接状态——这些细节藏在 Recv() 的错误分支、Send() 的超时控制、以及 keepalive 参数的毫秒级调优里。


















