3. gRPC 通信模式:Unary、Server Streaming、Client Streaming、Bidirectional Streaming

gRPC 基于 HTTP/2 协议,定义了四种核心通信模式。在微服务链路中,不同的业务场景(如查询详情、订阅事件、批量上传、实时聊天)对应不同的通信模式,而超时与重试策略的配置也因模式而异。理解这四种模式是后续排查链路超时和配置重试策略的基础。

3.1 四种通信模式概述

gRPC 的通信模式由客户端与服务端之间的消息流方向决定。下表从消息流方向、典型场景和超时敏感性三个维度进行对比:

通信模式 消息流方向 典型微服务场景 超时/重试特点
Unary(一元) 客户端发送 1 个请求 → 服务端返回 1 个响应 用户信息查询、订单详情、配置获取 单次 RPC 超时;重试逻辑简单,幂等性易保证
Server Streaming(服务端流) 客户端发送 1 个请求 → 服务端返回 N 个响应 日志推送、监控指标订阅、大结果集分页 首包超时 + 流空闲超时;重试需考虑已消费消息
Client Streaming(客户端流) 客户端发送 N 个请求 → 服务端返回 1 个响应 批量数据上传、文件分片上传、传感器数据聚合 整体超时 + 流空闲超时;重试需重新发送全部消息
Bidirectional Streaming(双向流) 客户端发送 N 个请求 → 服务端返回 N 个响应 实时聊天、游戏状态同步、协同编辑 全双工超时控制复杂;重试可能导致消息乱序
实践提示: 在微服务链路中,Unary 模式占比最高(约 70%),也是超时重试配置的默认场景。流式模式(尤其是双向流)的超时排查往往比 Unary 更复杂,需要关注流生命周期而非单次消息。

3.2 四种模式详解与 Proto 定义

3.2.1 Unary(一元模式)

最基础的 RPC 模式,客户端发起一次调用,服务端返回一次响应。在微服务中,绝大多数同步查询接口采用此模式。

// user_service.proto
service UserService {
  rpc GetUser (GetUserRequest) returns (GetUserResponse);
}

message GetUserRequest {
  int64 user_id = 1;
}

message GetUserResponse {
  string name = 1;
  string email = 2;
}

3.2.2 Server Streaming(服务端流模式)

客户端发送一个请求,服务端通过流多次返回数据。适用于服务端主动推送或大数据量分页场景。

// log_service.proto
service LogService {
  rpc SubscribeLogs (LogFilter) returns (stream LogEntry);
}

message LogFilter {
  string service_name = 1;
  string level = 2;
}

message LogEntry {
  int64 timestamp = 1;
  string message = 2;
}

3.2.3 Client Streaming(客户端流模式)

客户端通过流发送多个请求,服务端在接收完毕后返回一个响应。适用于批量提交或数据聚合。

// upload_service.proto
service UploadService {
  rpc UploadFile (stream FileChunk) returns (UploadStatus);
}

message FileChunk {
  bytes data = 1;
  int32 chunk_index = 2;
}

message UploadStatus {
  bool success = 1;
  string file_id = 2;
}

3.2.4 Bidirectional Streaming(双向流模式)

客户端和服务端各自独立发送流,双方可以同时读写。适用于实时交互场景。

// chat_service.proto
service ChatService {
  rpc Chat (stream ChatMessage) returns (stream ChatMessage);
}

message ChatMessage {
  string user_id = 1;
  string content = 2;
  int64 timestamp = 3;
}

3.3 四种模式的通信流程对比(SVG)

下图展示了四种模式在 HTTP/2 流上的消息交互时序,帮助理解不同模式下的超时点位置:

gRPC 四种通信模式消息流对比 gRPC 四种通信模式消息流对比 Unary 一元模式 Client Server Req Resp Server Streaming Client Server Req Resp1 Resp2 RespN Client Streaming Client Server Req1 Req2 ReqN Resp Bidirectional Client Server Req1 Resp1 Req2 Resp2 超时关键点说明 Unary:单次 RPC 超时;Server Streaming:首包超时 + 流空闲超时; Client Streaming:整体超时 + 流空闲超时;Bidirectional:全双工,需分别控制读写超时

3.4 流式模式的核心概念:流生命周期

流式模式(Server/Client/Bidirectional)引入了流生命周期的概念,这是与 Unary 模式最大的区别。一个 gRPC 流从创建到关闭经历以下阶段:

  1. 流建立:客户端发起 RPC 调用,HTTP/2 流创建。
  2. 消息传输:一方或双方通过流发送消息(Message)。
  3. 流空闲:双方都没有消息发送,但流保持打开。
  4. 流关闭:由客户端或服务端主动关闭,或由于超时/错误被动关闭。
超时排查陷阱: 在流式模式中,流空闲超时(idle timeout)是常见的排查盲区。例如,Server Streaming 场景下,服务端每 30 秒推送一次心跳,如果客户端设置的超时时间小于 30 秒,就会误报超时错误。配置时需区分 timeout(单次 RPC 总超时)和 idle_timeout(流空闲超时)。

3.5 不同模式下的超时配置策略

下表总结了四种模式在 gRPC 客户端(以 Java 为例)中的超时配置要点:

通信模式 超时配置项 推荐配置方式 注意事项
Unary deadline 每次调用设置 withDeadlineAfter(5, TimeUnit.SECONDS) 重试时需重置 deadline
Server Streaming deadline + idleTimeout 流级别设置 withDeadlineAfter(60, TimeUnit.SECONDS),配合 idleTimeout(10, TimeUnit.SECONDS) 首包超时需单独处理
Client Streaming deadline + idleTimeout 整体调用设置 withDeadlineAfter(120, TimeUnit.SECONDS) 重试需重新发送所有消息
Bidirectional Streaming deadline + 读写独立超时 使用 Context.current().withDeadline() 控制整体,应用层心跳保活 避免读写锁死导致超时

3.6 模式选择对重试策略的影响

重试策略在不同通信模式下的实现难度差异很大:

  • Unary:重试最简单。只需保证接口幂等性,直接重试整个 RPC 调用。
  • Server Streaming:重试需谨慎。客户端可能已经消费了部分响应消息,重试会导致重复消费。建议在应用层记录已消费的偏移量。
  • Client Streaming:重试成本高。客户端需要重新发送所有消息,适合在客户端缓存完整数据后重试。
  • Bidirectional Streaming:重试最复杂。消息顺序和状态一致性难以保证,通常建议在应用层实现会话恢复机制,而非简单重试。
课程关联: 本章节为后续第 5 章《gRPC 超时配置最佳实践》和第 7 章《重试策略与幂等性设计》奠定基础。理解通信模式是正确配置超时和重试的前提。