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 流上的映射关系。
2.5 模式选择决策流程
在实际项目中,选择哪种 RPC 模式直接影响日志记录策略和事务边界定义。以下决策流程可帮助快速判断:
- 客户端是否需要发送多个消息?
- 否 → 进入步骤 2
- 是 → 进入步骤 3
- 服务器是否需要返回多个消息?
- 否 → Unary RPC
- 是 → Server Streaming RPC
- 服务器是否需要返回多个消息?
- 否 → 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 的位置决定何时记录开始、结束和中间消息。