3. gRPC 通信模式:一元RPC、服务端流式RPC、客户端流式RPC、双向流式RPC
gRPC 基于 HTTP/2 协议,定义了四种核心通信模式。理解这些模式是设计重试策略与错误处理配置的前提——不同的流式语义决定了重试的可行性、幂等性要求以及错误传播路径。
3.1 四种通信模式概述
| 模式名称 | 请求/响应数量 | 典型场景 | 重试可行性 |
|---|---|---|---|
| 一元 RPC | 1 请求 → 1 响应 | 查询用户信息、创建订单 | 高(幂等时可直接重试) |
| 服务端流式 RPC | 1 请求 → N 条消息流 | 实时推送、日志订阅 | 中(需考虑已消费消息) |
| 客户端流式 RPC | N 条消息流 → 1 响应 | 批量上传、数据聚合 | 低(需重新发送全部流) |
| 双向流式 RPC | 双向 N 条消息流 | 实时聊天、协同编辑 | 极低(状态复杂) |
关键原则: 一元 RPC 是重试机制的最佳候选;流式 RPC 的重试通常需要结合业务层面的状态恢复或断点续传逻辑。
3.2 一元 RPC(Unary RPC)
客户端发送单个请求,服务端返回单个响应。这是最基础的通信模式,也是微服务链路中最常见的调用方式。
- 调用流程: 客户端发起请求 → 服务端处理 → 返回响应 → 连接关闭。
- 错误处理: 所有 gRPC 状态码(如
UNAVAILABLE、DEADLINE_EXCEEDED)均可直接返回。 - 重试策略: 若请求幂等,可直接重试;若非幂等,需在业务层做去重(如使用请求 ID)。
// Proto 定义示例
service UserService {
rpc GetUser (GetUserRequest) returns (GetUserResponse);
}
// 客户端调用(Java 示例)
UserServiceGrpc.UserServiceBlockingStub stub = UserServiceGrpc.newBlockingStub(channel);
GetUserResponse response = stub.getUser(GetUserRequest.newBuilder().setUserId(123).build());
3.3 服务端流式 RPC(Server Streaming RPC)
客户端发送一个请求,服务端返回一个消息流(多条消息)。适用于需要持续推送数据的场景。
- 调用流程: 客户端发起请求 → 服务端持续发送消息 → 客户端逐条读取 → 服务端发送完成信号 → 连接关闭。
- 错误处理: 错误可能在流中间发生(如第 5 条消息后失败),客户端需处理部分消费状态。
- 重试策略: 重试时需重新发起请求,已消费的消息通常需要丢弃或做幂等处理。
// Proto 定义示例
service LogService {
rpc SubscribeLogs (LogRequest) returns (stream LogEntry);
}
// 客户端读取流(Java 示例)
Iterator<LogEntry> logs = stub.subscribeLogs(LogRequest.newBuilder().setLevel("ERROR").build());
while (logs.hasNext()) {
LogEntry entry = logs.next();
System.out.println(entry.getMessage());
}
避坑提示: 服务端流式 RPC 的重试不能简单“断点续传”。如果业务需要从第 N 条消息继续,必须在请求中携带偏移量或时间戳参数。
3.4 客户端流式 RPC(Client Streaming RPC)
客户端发送一个消息流,服务端返回单个响应。适用于批量提交或数据聚合场景。
- 调用流程: 客户端持续发送消息 → 服务端接收并处理 → 客户端发送完成信号 → 服务端返回响应 → 连接关闭。
- 错误处理: 若在流中间失败,服务端可能已处理部分消息,客户端无法确认哪些已成功。
- 重试策略: 重试成本极高,通常需要客户端重新发送整个消息流。建议在业务层做批量幂等(如使用批次 ID)。
// Proto 定义示例
service UploadService {
rpc UploadFile (stream Chunk) returns (UploadResponse);
}
// 客户端发送流(Java 示例)
StreamObserver<UploadResponse> responseObserver = new StreamObserver<>() { /* 回调处理 */ };
StreamObserver<Chunk> requestObserver = stub.uploadFile(responseObserver);
requestObserver.onNext(Chunk.newBuilder().setData("part1").build());
requestObserver.onNext(Chunk.newBuilder().setData("part2").build());
requestObserver.onCompleted();
3.5 双向流式 RPC(Bidirectional Streaming RPC)
客户端和服务端各自独立发送消息流,双方可以同时读写。适用于实时交互场景。
- 调用流程: 双方建立连接 → 独立发送消息 → 各自处理对方消息 → 任一方发送完成信号 → 连接关闭。
- 错误处理: 错误可能发生在任意方向,消息顺序和状态管理复杂。
- 重试策略: 几乎不适用自动重试。通常需要业务层实现会话恢复或状态同步机制。
// Proto 定义示例
service ChatService {
rpc Chat (stream ChatMessage) returns (stream ChatMessage);
}
// 客户端双向流(Java 示例)
StreamObserver<ChatMessage> responseObserver = new StreamObserver<>() { /* 处理服务端消息 */ };
StreamObserver<ChatMessage> requestObserver = stub.chat(responseObserver);
requestObserver.onNext(ChatMessage.newBuilder().setText("Hello").build());
// 服务端可能同时发来消息
3.6 四种模式与重试策略的映射关系
3.7 模式选择对重试配置的影响
在微服务链路中,选择通信模式时需同步考虑重试策略的复杂度:
- 一元 RPC: 适合配置自动重试(如 gRPC 内置重试策略),只需关注幂等性。
- 服务端流式 RPC: 建议在请求中增加
offset或sequence_number参数,支持断点续传。 - 客户端流式 RPC: 避免自动重试,改为业务层手动重试,并携带全局批次 ID。
- 双向流式 RPC: 通常不配置重试,而是通过心跳检测和会话恢复机制处理。
实践建议: 在微服务链路中,80% 的调用应使用一元 RPC,以简化重试和错误处理逻辑。流式 RPC 仅在实时推送、批量上传等必要场景中使用,并配套自定义重试方案。