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 四种模式与重试策略的映射关系

gRPC 四种通信模式与重试策略映射 一元 RPC 1请求 → 1响应 重试:高可行性 服务端流式 RPC 1请求 → N条消息 重试:中可行性 客户端流式 RPC N条消息 → 1响应 重试:低可行性 双向流式 RPC 双向N条消息 重试:极低可行性 重试策略建议 • 一元 RPC:直接重试,需保证幂等性(如使用请求 ID 去重) • 服务端流式:重试时携带偏移量/时间戳,避免重复消费 • 客户端流式:重试需重新发送整个流,建议使用批次 ID 做幂等 错误处理流程对比 错误直接返回 客户端立即感知 流中间可能失败 部分消息已消费 服务端状态不确定 部分消息已处理 双向状态混乱 需业务层恢复 核心结论 • 一元 RPC 是重试机制的最佳实践场景,适合自动重试策略 • 流式 RPC 的重试需要结合业务状态,建议在应用层实现自定义恢复逻辑

3.7 模式选择对重试配置的影响

在微服务链路中,选择通信模式时需同步考虑重试策略的复杂度:

  • 一元 RPC: 适合配置自动重试(如 gRPC 内置重试策略),只需关注幂等性。
  • 服务端流式 RPC: 建议在请求中增加 offset 或 sequence_number 参数,支持断点续传。
  • 客户端流式 RPC: 避免自动重试,改为业务层手动重试,并携带全局批次 ID。
  • 双向流式 RPC: 通常不配置重试,而是通过心跳检测和会话恢复机制处理。
实践建议: 在微服务链路中,80% 的调用应使用一元 RPC,以简化重试和错误处理逻辑。流式 RPC 仅在实时推送、批量上传等必要场景中使用,并配套自定义重试方案。