双向流式RPC在Kratos中必须写为rpc BidirectionalStream(stream Request) returns (stream Response),且需正确配置package、go_package及import "google/api/annotations.proto",否则无法生成合规代码。

双向流式 RPC 在 Kratos 中怎么写 proto?
必须用 stream 关键字同时修饰请求和响应类型,否则生成的代码不支持双向流。Kratos 不会自动把普通 RPC 升级成流式,定义错就只能重生成。
常见错误是只写一边:rpc BidirectionalStream(stream DataRequest) returns (DataResponse) —— 这其实是客户端流式,不是双向流。
- 正确写法:
rpc BidirectionalStream(stream DataRequest) returns (stream DataResponse) -
package和go_package选项要与 Kratos 项目结构对齐,比如option go_package = "helloworld/api/helloworld/v1;v1" - 别漏掉
import "google/api/annotations.proto",虽然双向流本身不依赖 HTTP 映射,但 Kratos 的生成工具链默认检查该 import
Kratos 生成双向流服务代码要注意什么?
kratos proto server 命令默认只生成一元 RPC 的骨架,对双向流不会自动补全 Recv/Send 循环逻辑,得手动改。
生成后你会看到 internal/service/greeter.go 里有个空方法体,里面只有 return nil。这不行——双向流必须自己实现收发循环,且要处理连接中断、Recv 返回 !ok 等边界。
- 服务端逻辑必须用
for+select轮询stream.Recv(),不能只调一次 - 每次
stream.Send()前建议加ctx.Err() != nil检查,避免往已断开的流写数据导致 panic - 不要在循环里直接调用阻塞操作(如数据库同步查询),否则会卡住整个流;需用 goroutine 或异步 channel 拆解
客户端怎么稳定消费双向流?别卡死在 Recv()
Python/Go/Java 客户端都容易在这里翻车:Recv() 是阻塞调用,如果服务端迟迟不发响应,客户端就挂住,超时机制还可能被流内部状态干扰。
Kratos 默认没给流式调用配全局上下文超时,得自己传带 deadline 的 context.Context。
- Go 客户端示例:
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second),然后用这个ctx调client.BidirectionalStream(ctx) - 每次
resp, err := stream.Recv()后,必须判断err == io.EOF或status.IsOK(err),不能只看err != nil - Python 客户端用
grpcio时,next()迭代器可能抛StopIteration,不是所有异常都走except grpc.RpcError
流控没做,内存爆了怎么办?
gRPC 内置流控只管 TCP 窗口和 HTTP/2 流量控制,它不管你的业务逻辑塞了多少消息进发送队列。服务端一边狂 Send(),客户端处理不过来,消息就在 client 端内存里堆积。
真实线上问题往往不是连接断,而是 OOM 后被系统 kill。Kratos 本身不提供应用层背压,得自己加。
- 最简方案:客户端每处理完一条,主动发个空
AckRequest回服务端,服务端收到再发下一条(需额外定义一个单向 RPC) - 进阶做法:在
DataRequest里加int32 window_size = 2字段,客户端初始发 10,处理完 3 条就再发个window_size=3更新窗口 - 别依赖
stream.Context().Done()做流控依据——它只反映连接状态,不反映消费能力
双向流看着只是多写了两个 stream 关键字,实际最难的是收发节奏对齐和资源生命周期管理。很多团队卡在“能通”,却栽在“长时间运行就崩”。重点盯住 Recv 的退出条件、Send 的错误传播、以及任何没显式 cancel 的 context。











