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 流上的消息交互时序,帮助理解不同模式下的超时点位置:
3.4 流式模式的核心概念:流生命周期
流式模式(Server/Client/Bidirectional)引入了流生命周期的概念,这是与 Unary 模式最大的区别。一个 gRPC 流从创建到关闭经历以下阶段:
- 流建立:客户端发起 RPC 调用,HTTP/2 流创建。
- 消息传输:一方或双方通过流发送消息(Message)。
- 流空闲:双方都没有消息发送,但流保持打开。
- 流关闭:由客户端或服务端主动关闭,或由于超时/错误被动关闭。
超时排查陷阱: 在流式模式中,流空闲超时(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 章《重试策略与幂等性设计》奠定基础。理解通信模式是正确配置超时和重试的前提。