2. gRPC 基础回顾:Protocol Buffers、服务定义、一元与流式 RPC
在深入分布式事务压测与契约测试之前,必须牢固掌握 gRPC 的核心基础。gRPC 的高性能、强类型与多语言互操作性,均源于其底层技术栈:Protocol Buffers(protobuf)作为接口定义语言(IDL)与序列化协议,以及基于 HTTP/2 的四种通信模式。本节将系统梳理这些基础构件,为后续的契约测试设计奠定形式化基础。
2.1 Protocol Buffers:契约的基石
Protocol Buffers 是 gRPC 的“契约语言”。它通过 .proto 文件定义数据结构与服务接口,并生成各语言的高效序列化代码。与 JSON/XML 相比,protobuf 具有以下关键优势:
| 特性 | Protocol Buffers | JSON / XML |
|---|---|---|
| 序列化体积 | 小(二进制,无冗余键名) | 大(文本,键名重复) |
| 解析速度 | 快(二进制直接映射) | 慢(文本解析开销) |
| 类型安全 | 强类型(编译期校验) | 弱类型(运行时校验) |
| 向后兼容 | 内置(字段编号 + 可选/默认) | 需手动处理 |
| 契约定义 | 单一 .proto 文件 | 无统一契约 |
一个典型的 .proto 文件结构如下:
syntax = "proto3";
package transaction.v1;
// 定义订单状态枚举
enum OrderStatus {
ORDER_STATUS_UNSPECIFIED = 0;
ORDER_STATUS_PENDING = 1;
ORDER_STATUS_CONFIRMED = 2;
ORDER_STATUS_FAILED = 3;
}
// 定义请求消息
message CreateOrderRequest {
string order_id = 1;
string user_id = 2;
double amount = 3;
repeated string item_ids = 4;
}
// 定义响应消息
message CreateOrderResponse {
string transaction_id = 1;
OrderStatus status = 2;
string message = 3;
}
契约测试要点:在压测场景中,
.proto 文件就是服务提供者与消费者之间的“法律文件”。契约测试的核心任务之一,就是验证客户端发送的请求与服务器返回的响应,是否严格符合 .proto 中定义的字段编号、类型、可选性以及枚举值范围。
2.2 服务定义:从消息到 RPC 方法
在 .proto 文件中,使用 service 关键字将消息组合成 RPC 方法。每个方法指定输入消息类型与输出消息类型,gRPC 编译器据此生成客户端 Stub 与服务器骨架。
service OrderService {
// 一元 RPC:客户端发送一个请求,服务器返回一个响应
rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse);
// 服务器端流式 RPC:客户端发送一个请求,服务器返回一个流
rpc ListOrders(ListOrdersRequest) returns (stream Order);
// 客户端端流式 RPC:客户端发送一个流,服务器返回一个响应
rpc BatchCreateOrder(stream CreateOrderRequest) returns (BatchCreateOrderResponse);
// 双向流式 RPC:双方同时发送和接收流
rpc ProcessOrderStream(stream OrderEvent) returns (stream OrderResult);
}
服务定义中的 rpc 关键字后紧跟方法名,括号内为输入/输出类型。关键字 stream 标记流式通信。这种声明方式使得服务契约在代码生成阶段即可被静态检查,避免了运行时接口不匹配的风险。
2.3 一元 RPC 与流式 RPC 对比
gRPC 基于 HTTP/2 提供了四种通信模式。理解它们的差异,对于设计压测场景与契约测试用例至关重要。
| 模式 | 请求 | 响应 | 典型场景 | 压测关注点 |
|---|---|---|---|---|
| 一元 RPC | 1 条消息 | 1 条消息 | 创建订单、查询用户 | 延迟、吞吐量、错误率 |
| 服务器端流式 | 1 条消息 | 多条消息(流) | 批量查询、日志推送 | 流完成时间、消息间隔 |
| 客户端端流式 | 多条消息(流) | 1 条消息 | 批量上传、数据聚合 | 流发送速率、合并延迟 |
| 双向流式 | 多条消息(流) | 多条消息(流) | 实时聊天、事件驱动 | 全双工吞吐、背压表现 |
契约测试陷阱:流式 RPC 的契约不仅包含消息结构,还包含流结束信号(gRPC 状态码)与消息顺序。例如,服务器端流必须在发送完所有消息后发送
Status.OK;双向流中,双方关闭流的时机必须符合业务语义。契约测试必须覆盖这些流生命周期行为,而不仅仅是单条消息的格式。
2.4 四种 RPC 模式的通信流程
下图展示了四种 RPC 模式在 HTTP/2 流上的交互过程,帮助理解不同模式下的消息边界与流控制。
2.5 gRPC 调用生命周期与状态码
每次 gRPC 调用(无论一元还是流式)都遵循严格的生命周期,最终以状态码结束。契约测试必须验证这些状态码的正确性。
- 连接建立:客户端与服务器通过 HTTP/2 建立连接。
- 元数据交换:可选的初始元数据(如认证令牌)在调用开始时发送。
- 消息传输:根据 RPC 模式,传输一条或多条 protobuf 消息。
- 状态与尾部元数据:服务器发送 gRPC 状态码(
OK、INVALID_ARGUMENT、UNAVAILABLE等)以及可选的尾部元数据。 - 流关闭:双方关闭 HTTP/2 流。
压测场景库映射:在分布式事务压测中,一元 RPC 常用于事务提交与回滚操作;流式 RPC 常用于事件溯源与状态同步。契约测试需要为每种模式编写独立的断言逻辑,例如:验证服务器端流是否在预期时间内发送了正确数量的消息,以及最终状态码是否为
OK。
2.6 小结:基础回顾与契约测试的衔接
本节的回顾为后续章节奠定了三个关键认知:
- 契约即代码:
.proto文件是服务间通信的单一事实来源,契约测试围绕它展开。 - 模式决定测试策略:一元 RPC 的契约测试关注请求/响应对;流式 RPC 的契约测试还需关注流生命周期、消息顺序与背压行为。
- 状态码是契约的一部分:gRPC 状态码(如
UNAVAILABLE表示服务不可用)在压测中直接对应故障注入场景,契约测试必须覆盖正常与异常状态码。