--- tags: [grpc, rpc-patterns, streaming, bidirectional] create time: 2026-05-07 16:00 --- # RPC 模式详解 ## 概述 gRPC 支持 **四种 RPC 调用模式**,每种对应不同的客户端/服务端消息交互时序。理解它们不仅仅是记住名词,更要掌握「什么时候该用哪种」以及「各种模式的坑在哪里」。 > [!question] 先看一个实际场景 > > 你需要实现一个订单列表功能:用户在前端点击按钮 → 后端查询数据库 → 一次返回 100 条订单记录。 > > 你会选择哪种 RPC 模式?如果用 Unary 一行行发回来会怎样? Unary 模式下这 100 条记录会变成 100 次独立的 HTTP/2 Stream,虽然 HTTP/2 支持多路复用,但这依然浪费了宝贵的 Stream 标识符空间。更好的方式是用 **Server Streaming** 在一个 Stream 内批量发送。 ## Unary —— 普通请求/响应 最常见的调用方式,一问一答: ```go // client side resp, err := client.GetOrder(ctx, &pb.GetOrderRequest{Id: "ORD-001"}) if err != nil { // handle error } // server side func (s *orderServer) GetOrder(ctx context.Context, req *pb.GetOrderRequest) (*pb.Order, error) { return s.store.Get(req.GetId()) } ``` 适用场景:CRUD 常规操作、单次计算任务、短平快的查询。这是占 gRPC 流量 **90%+** 的模式。 ## Server Streaming —— 服务端流式 服务端一次性返回多个结果,适合批量查询或推送: ```mermaid sequenceDiagram participant C as Client participant S as Server C->>S: ListOrders(request) S-->>C: Order[1] S-->>C: Order[2] S-->>C: Order[3] S-->>C: EOF Note right of C: 客户端收到完整列表 ``` ```go func (s *orderServer) ListOrders(req *pb.ListOrdersRequest, stream pb.OrderService_ListOrdersServer) error { orders := s.store.ListAll() for _, o := range orders { if err := stream.Send(o); err != nil { return err } } return nil } ``` > [!warning] 服务端必须主动关闭流 > > 如果服务端 `Send` 循环中没有遇到错误或主动返回 `nil`,Stream 永远不会结束,客户端将永远阻塞在 `Recv()`。务必确保所有路径都会退出循环或返回错误。 ## Client Streaming —— 客户端流式 客户端连续发送多条消息,服务端最后返回聚合结果。适合大数据上传: ```mermaid sequenceDiagram participant C as Client participant S as Server C->>S: BatchRecord[1] C->>S: BatchRecord[2] C->>S: BatchRecord[3] C->>S: CloseSend() Note right of S: 收集所有数据 S-->>C: BatchResult{count: 3} ``` ```go func (s *orderServer) UploadLogs(stream pb.OrderService_UploadLogsServer) error { var count int32 for { log, err := stream.Recv() if err == io.EOF { break } if err != nil { return err } s.store.Log(log.Message) count++ } return stream.SendAndClose(&pb.UploadResult{Count: count}) } ``` 适用场景:批量写入日志、文件分块上传、批量创建记录。注意要在客户端控制流速,避免一次性塞爆缓冲区。 ## Bidi Streaming —— 双向流式 双方各自独立发送流,完全异步。**最适合实时场景**(聊天、行情推送、协同编辑): ```mermaid sequenceDiagram participant CS as Client Stream participant SS as Server Stream CS->>SS: Msg[1]: "Hello" SS->>CS: Reply[1]: "Hi!" CS->>SS: Msg[2]: "What's up?" SS->>CS: Reply[2]: "Not much" CS->>SS: Msg[3]... SS->>CS: Reply[3]... CS--)SS: Cancel SS--)CS: Cancel ``` ```go // 实战示例:WebSocket-like 的实时通知推送 func (s *server) SubscribeNotifications( req *pb.SubscribeRequest, stream pb.NotificationService_SubscribeServer, ) error { // 注册订阅 notifier.Register(stream.Context().Done(), func() { stream.Send(&pb.Notification{}) }) <-stream.Context().Done() return stream.Context().Err() } ``` > [!question] 选型思考 > > 假设你要实现一个实时订单状态推送功能(前端监听订单从"待支付"→"已发货"→"已完成"的变化),你会选哪种流式模式?为什么不用 WebSocket? gRPC 双向流是**强类型 + 全双工**的替代方案,不需要额外建 WebSocket 通道,Proto 定义即契约,代码自动生成即客户端 SDK。对于纯内部服务链路,gRPC 双向流通常优于 WebSocket。 ### Bidi Streaming 的生命周期管理 ```mermaid flowchart TD Start["建立双向连接"] --> Idle["空闲等待"] Idle --> SendMsg["客户端 send"] Idle --> SendReply["服务端 send"] SendMsg --> Idle SendReply --> Idle SendMsg --> ClientCancel{"client cancel?"} SendReply --> ServerCancel{"server cancel?"} ClientCancel -->|yes| Cleanup["释放资源"] ServerCancel -->|yes| Cleanup Cleanup --> End["断开连接"] style Start fill:#e3f2fd style End fill:#ffcdd2 style Cleanup fill:#fff3e0 ``` > [!keypoint] 黄金法则 > > 双向流的**任何一方都可以随时单方面关闭发送端**(`CloseSend` 或 `context.Done`)。另一方应检测到 EOF 并及时清理资源,而不是无限等待。 ## 四种模式速查 | 模式 | 客户端消息数 | 服务端消息数 | 典型场景 | |------|------------|------------|---------| | Unary | 1 | 1 | CRUD 常规操作 | | Server Stream | 1 | N | 列表查询、日志流拉取 | | Client Stream | N | 1 | 批量写入、大文件分块上传 | | Bidi Stream | N | M | 聊天室、实时协作、行情推送 | > [!note] 组合使用 > > 同一个 Service 中可以混合使用四种模式。比如 `OrderService` 里: > - `CreateOrder` 用 Unary(创建后立即返回结果) > - `ListOrders` 用 Server Stream(大量数据分批返回) > - `ImportRecords` 用 Client Stream(批量导入) > - `NotifyOrderStatus` 用 Bidi Stream(实时状态变更推送) ## 关联笔记 - [[01-协议与架构]] — 流式建立在 HTTP/2 Stream 的多路复用之上 - [[04-拦截器]] — 流式调用同样可以通过 StreamInterceptor 进行切面处理 - [[05-错误处理]] — 流式调用的错误处理与普通模式有差异 - [[06-连接管理]] — 长时间存活的流式连接需要 Keepalive 保活