3. gRPC 分布式事务模型:Saga 与 TCC 模式实现及上下文传播
在微服务架构中,跨多个 gRPC 服务的分布式事务是压测场景中最常见的故障触发点。本章深入剖析两种主流补偿型事务模式——Saga 与 TCC 在 gRPC 中的实现原理,并重点讲解如何通过 gRPC 拦截器实现事务上下文的透明传播。
3.1 Saga 模式在 gRPC 中的实现
Saga 模式将一个分布式事务拆分为一组本地事务(Local Transaction),每个本地事务对应一个 gRPC 服务调用,并配备一个补偿操作(Compensation)。当某个步骤失败时,Saga 协调器按逆序调用补偿操作,回滚已提交的步骤。
3.1.1 实现架构
在 gRPC 生态中,Saga 通常通过一个独立的 Saga Orchestrator 服务实现。该服务使用 gRPC 客户端调用各参与方的业务接口与补偿接口。
3.1.2 gRPC 接口定义示例
// saga.proto
service OrderService {
rpc CreateOrder (CreateOrderRequest) returns (CreateOrderResponse);
rpc CompensateCreateOrder (CompensateCreateOrderRequest) returns (CompensateResponse);
}
service InventoryService {
rpc DeductStock (DeductStockRequest) returns (DeductStockResponse);
rpc CompensateDeductStock (CompensateDeductStockRequest) returns (CompensateResponse);
}
service AccountService {
rpc DeductBalance (DeductBalanceRequest) returns (DeductBalanceResponse);
rpc CompensateDeductBalance (CompensateDeductBalanceRequest) returns (CompensateResponse);
}
3.2 TCC 模式在 gRPC 中的实现
TCC(Try-Confirm-Cancel)模式将每个分布式事务操作显式分为三个阶段:Try(预留资源)、Confirm(确认执行)、Cancel(取消释放)。相比 Saga,TCC 在 Try 阶段即锁定资源,能提供更强的隔离性。
3.2.1 三阶段调用模型
| 阶段 | gRPC 方法 | 业务语义 | 失败处理 |
|---|---|---|---|
| Try | TryDeductBalance |
冻结用户余额,标记为不可用 | 直接返回失败,不执行后续阶段 |
| Confirm | ConfirmDeductBalance |
将冻结余额正式扣除 | 幂等重试,直到成功 |
| Cancel | CancelDeductBalance |
解冻余额,恢复可用额度 | 幂等重试,直到成功 |
3.2.2 TCC 事务管理器与 gRPC 集成
TCC 通常需要一个 Transaction Manager (TM) 来协调全局事务。TM 通过 gRPC 调用各参与方的 Try 接口,根据 Try 结果决定调用 Confirm 或 Cancel。
3.3 gRPC 拦截器与事务上下文传播
无论是 Saga 还是 TCC,分布式事务都需要将全局事务 ID(XID)、分支 ID、事务模式等上下文信息透明地传递到每个 gRPC 调用中。gRPC 拦截器(Interceptor)是实现这一机制的最佳切入点。
3.3.1 上下文传播架构
事务上下文通过 gRPC 的 Metadata 在客户端与服务端之间传递。客户端拦截器在发起调用前将上下文注入 Metadata;服务端拦截器在接收请求时从 Metadata 中提取上下文,并绑定到当前线程或协程。
| 组件 | 职责 | gRPC 拦截器类型 |
|---|---|---|
| 客户端拦截器 | 从当前线程上下文读取 XID,写入 gRPC Metadata | ClientInterceptor |
| 服务端拦截器 | 从 Metadata 读取 XID,设置到当前线程上下文 | ServerInterceptor |
| 传播键 | Metadata 中使用的 Key,如 "xid" |
常量字符串 |
3.3.2 客户端拦截器实现示例(Java)
public class TransactionClientInterceptor implements ClientInterceptor {
@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
MethodDescriptor<ReqT, RespT> method,
CallOptions callOptions,
Channel next) {
return new ForwardingClientCall.SimpleForwardingClientCall<>(next.newCall(method, callOptions)) {
@Override
public void start(Listener<RespT> responseListener, Metadata headers) {
// 从当前线程上下文获取全局事务 ID
String xid = TransactionContextHolder.getXID();
if (xid != null) {
headers.put(Metadata.Key.of("xid", Metadata.ASCII_STRING_MARSHALLER), xid);
}
super.start(responseListener, headers);
}
};
}
}
3.3.3 服务端拦截器实现示例(Java)
public class TransactionServerInterceptor implements ServerInterceptor {
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
ServerCall<ReqT, RespT> call,
Metadata headers,
ServerCallHandler<ReqT, RespT> next) {
// 从 Metadata 中提取全局事务 ID
String xid = headers.get(Metadata.Key.of("xid", Metadata.ASCII_STRING_MARSHALLER));
if (xid != null) {
TransactionContextHolder.setXID(xid);
}
return Contexts.interceptCall(Context.current(), call, headers, next);
}
}
3.4 三种实现方式对比
| 维度 | Saga | TCC | 拦截器上下文传播 |
|---|---|---|---|
| 资源锁定 | 不锁定,最终一致性 | Try 阶段锁定 | 不涉及 |
| 隔离性 | 弱(无隔离) | 强(资源预留) | 不涉及 |
| 实现复杂度 | 中(需补偿逻辑) | 高(三阶段 + 幂等) | 低(拦截器注入) |
| 压测重点 | 补偿链路完整性 | Try/Cancel 幂等性 | 上下文透传正确性 |
| 适用场景 | 长事务、高吞吐 | 短事务、强一致性 | 所有分布式事务 |