2. gRPC 基础回顾:gRPC 通信模型与四种 RPC 模式

在深入结构化日志与事务边界之前,必须牢固掌握 gRPC 的通信模型。gRPC 基于 HTTP/2 协议,利用 Protocol Buffers(protobuf)作为接口定义语言(IDL)和序列化工具。其核心通信模型围绕 Service、RPC 方法 和 Stream 展开。

2.1 gRPC 通信模型核心要素

要素 说明 关键特性
Service 在 .proto 文件中定义的逻辑接口,包含一组 RPC 方法。 跨语言、强类型
Stub 客户端代理,负责将本地调用序列化为 protobuf 消息并通过 HTTP/2 发送。 阻塞式/非阻塞式
Channel 客户端到服务器的连接抽象,管理 HTTP/2 连接池、负载均衡和重试。 可配置拦截器、压缩
Stream HTTP/2 流,支持全双工通信,是四种 RPC 模式的基础。 低延迟、多路复用
Context 携带截止时间、取消信号和元数据(metadata)的上下文对象。 贯穿请求生命周期
关键理解: gRPC 的通信模型本质上是将 HTTP/2 的流(Stream)抽象为 RPC 的请求/响应通道。每个 gRPC 调用都对应一个或多个 HTTP/2 Stream,这使得多路复用和流式控制成为可能。

2.2 四种 RPC 模式详解

gRPC 定义了四种通信模式,覆盖从简单请求-响应到复杂流式处理的全部场景。理解这些模式是设计日志和事务边界的前提。

2.2.1 Unary RPC(一元模式)

客户端发送一个请求,服务器返回一个响应。这是最基础的 RPC 模式,行为类似传统 HTTP API。

  • 适用场景: 查询、创建、更新等简单操作。
  • 日志关注点: 请求/响应完整记录,事务边界清晰(开始-结束)。

2.2.2 Server Streaming RPC(服务端流模式)

客户端发送一个请求,服务器返回一个响应流(多个消息)。

  • 适用场景: 批量数据导出、实时事件推送、大文件分块传输。
  • 日志关注点: 流开始、流中每个消息、流结束/错误。事务边界跨越多个消息。

2.2.3 Client Streaming RPC(客户端流模式)

客户端发送一个请求流(多个消息),服务器返回一个响应。

  • 适用场景: 批量数据上传、传感器数据聚合、长时任务提交。
  • 日志关注点: 每个客户端消息的到达顺序、完整性校验、最终响应。

2.2.4 Bidirectional Streaming RPC(双向流模式)

客户端和服务器可以同时发送和接收消息流,两者独立且全双工。

  • 适用场景: 实时聊天、协同编辑、游戏状态同步、实时音视频信令。
  • 日志关注点: 消息顺序、并发写入、流生命周期管理、背压处理。
实践要点: 在双向流中,事务边界不再以单个请求-响应为单位,而是以“逻辑会话”为单位。日志系统必须能够关联同一会话内的所有消息,并处理乱序和重传。

2.3 四种模式对比与选择

模式 请求 响应 典型延迟 事务边界复杂度
Unary 1 1 低 低(单次调用)
Server Streaming 1 N 中 中(流生命周期)
Client Streaming N 1 中 中(消息聚合)
Bidirectional Streaming N N 高(全双工) 高(会话管理)

2.4 gRPC 通信模型与流式架构图

下图展示了 gRPC 通信模型的整体架构,以及四种 RPC 模式在 HTTP/2 流上的映射关系。

gRPC 通信模型与四种 RPC 模式架构 gRPC 客户端 Stub / Channel gRPC 服务端 Service / Handler HTTP/2 传输层 Unary RPC 请求 (1) → 响应 (1) Server Streaming RPC 请求 (1) → 响应流 (N) Client Streaming RPC 请求流 (N) → 响应 (1) Bidirectional Streaming RPC 请求流 (N) ↔ 响应流 (N) 四种模式均基于 HTTP/2 Stream,支持多路复用和流控

2.5 模式选择决策流程

在实际项目中,选择哪种 RPC 模式直接影响日志记录策略和事务边界定义。以下决策流程可帮助快速判断:

  1. 客户端是否需要发送多个消息?
    • 否 → 进入步骤 2
    • 是 → 进入步骤 3
  2. 服务器是否需要返回多个消息?
    • 否 → Unary RPC
    • 是 → Server Streaming RPC
  3. 服务器是否需要返回多个消息?
    • 否 → Client Streaming RPC
    • 是 → Bidirectional Streaming RPC
常见误区: 不要因为“未来可能需要流式”而将 Unary 模式设计成流式。过度设计会增加日志和事务管理的复杂度。始终从当前需求出发,选择最简模式。

2.6 代码示例:四种模式的 .proto 定义

以下 protobuf 定义展示了四种 RPC 模式的声明方式,这是后续实现日志拦截器和事务管理的基础。

syntax = "proto3";

package demo;

service OrderService {
  // Unary RPC
  rpc CreateOrder (CreateOrderRequest) returns (OrderResponse);

  // Server Streaming RPC
  rpc ListOrders (ListOrdersRequest) returns (stream OrderResponse);

  // Client Streaming RPC
  rpc UploadOrders (stream UploadOrderRequest) returns (UploadSummary);

  // Bidirectional Streaming RPC
  rpc ProcessOrders (stream OrderAction) returns (stream OrderEvent);
}

message CreateOrderRequest {
  string user_id = 1;
  repeated string item_ids = 2;
}

message OrderResponse {
  string order_id = 1;
  string status = 2;
  int64 timestamp = 3;
}

message ListOrdersRequest {
  string user_id = 1;
  int32 page_size = 2;
}

message UploadOrderRequest {
  string batch_id = 1;
  bytes order_data = 2;
}

message UploadSummary {
  int32 total_orders = 1;
  int32 success_count = 2;
  int32 fail_count = 3;
}

message OrderAction {
  string order_id = 1;
  string action_type = 2;
  map<string, string> params = 3;
}

message OrderEvent {
  string event_id = 1;
  string event_type = 2;
  string payload = 3;
}
日志设计启示: 注意 stream 关键字的位置。在服务端流中,stream 修饰响应;在客户端流中,stream 修饰请求;在双向流中,两者都带 stream。日志拦截器需要根据 stream 的位置决定何时记录开始、结束和中间消息。

2.7 本章小结