在分布式事务方案选型时,Saga和TCC是两种最常被拿来比较的模式。不少团队在两者之间反复纠结,最终却因为没搞清楚长事务场景下的核心约束,选错了方案,导致系统上线后频繁出现数据不一致的线上事故。Saga在长事务场景下的表现优于TCC,这并非单纯的经验之谈,而是由两者的隔离级别和资源锁定机制决定的。
Saga和TCC虽然都通过补偿操作实现最终一致性,但它们对业务资源的占用策略截然不同。TCC依赖资源预留,而Saga依赖业务补偿,这一差异直接影响了它们在长事务中的可用性。
下表从四个维度对比两种模式的关键差异:
| 对比维度 | Saga | TCC |
|---|---|---|
| 资源锁定 | 不锁定资源,每个子事务直接提交 | Try阶段预留资源,Confirm/Cancel前持有锁 |
| 隔离级别 | 读已提交,存在脏读和幻读 | 可达到串行化,但以牺牲并发为代价 |
| 事务时长 | 无锁等待,适合长事务 | 锁持有时间等于事务时长,长事务下锁竞争严重 |
| 失败处理 | 执行补偿操作回滚已提交的子事务 | Cancel释放资源,无业务副作用 |
从表中可以看出,TCC需要在Try阶段预先占用资源,直到Confirm或Cancel才释放。这意味着事务执行时间越长,资源被锁定的时间就越长。在长事务场景下,比如一个订单处理流程需要等待人工审核、外部系统回调,整个过程可能持续几分钟甚至几小时,TCC的锁持有期间会阻塞其他并发请求,导致系统吞吐量急剧下降。
Saga在长事务中保持高性能的核心原因在于它采用“先提交,后补偿”的策略。每个子事务独立提交,不会长时间持有数据库行锁或业务资源锁,其他事务可以正常读写中间状态的数据。这种设计虽然牺牲了一定的隔离性,但在多数业务场景下,通过业务层的幂等和空回滚控制,足以保证最终一致性。
以订单服务为例,一个典型的Saga编排如下:
// Saga编排器伪代码示例
func CreateOrderSaga(ctx context.Context, order *Order) error {
// 步骤1:创建订单
if err := orderService.CreateOrder(ctx, order); err != nil {
return err
}
// 步骤2:扣减库存
if err := inventoryService.DeductInventory(ctx, order.Items); err != nil {
// 补偿步骤1:取消订单
orderService.CancelOrder(ctx, order.ID)
return err
}
// 步骤3:调用支付
if err := paymentService.Pay(ctx, order.PaymentInfo); err != nil {
// 补偿步骤2:恢复库存
inventoryService.AddInventory(ctx, order.Items)
// 补偿步骤1
orderService.CancelOrder(ctx, order.ID)
return err
}
return nil
}每一步子事务都是立即提交的,不存在长时间锁持有。如果支付失败,补偿操作会恢复库存并取消订单,保证最终数据一致。而TCC实现同样的流程,需要将库存扣减、订单创建等操作拆分为Try/Confirm/Cancel三个接口,Try阶段会真正锁定库存和订单资源,直到支付完成才释放。
TCC的确在某些场景下不可替代,尤其是对隔离性要求特别高的金融转账操作。例如A账户向B账户转账,必须保证在事务完成前,两个账户的余额变化对其他事务不可见,否则会出现资金凭空消失的幻象。TCC通过Try阶段的资源预留实现了业务层面的串行化隔离,这是Saga难以做到的。
但这类场景通常事务执行时间极短,毫秒级即可完成,资源锁持有时间可以忽略不计。一旦事务时长拉长到秒级以上,TCC的锁竞争就会成为系统瓶颈。据Uber在其技术博客中分享的实践数据,将支付系统从TCC迁移至Saga后,长事务场景下的并发能力提升了约4倍,数据库锁等待超时率从15%降至0.5%以下。
虽然Saga在长事务中优势明显,但它的补偿逻辑设计不当很容易引发数据不一致。有三个核心问题必须解决:
在代码层面,需要在补偿方法中增加这些防御逻辑:
func (s *InventoryService) AddInventory(ctx context.Context, orderID string) error {
// 基于orderID查询补偿记录,确保幂等
if alreadyCompensated(orderID) {
return nil
}
// 检查是否发生了悬挂:如果子事务还未执行,则标记为已回滚,防止后续子事务执行
if !subTxExecuted(orderID) {
markCancelled(orderID)
return nil
}
// 执行真正的库存恢复逻辑
return s.repo.Add(ctx, orderID)
}这种基于状态机的补偿设计,是Saga模式在生产环境中稳定运行的基础。
架构设计的本质是权衡。Saga和TCC各有各的适用场景,但很多团队在选择时容易陷入理论完美的陷阱,忽略了业务实际。一个简单的判断标准是:如果事务的每个步骤耗时超过100毫秒,或者事务链路中存在人工交互、外部系统回调等不确定性等待,那么Saga是更务实的选择。TCC的锁开销在长事务中会被放大,而Saga的补偿复杂性可以通过成熟的框架和中间件(如Seata、DTM)来简化,整体工程成本反而更低。
评论
加载中...