微服务拆分后,跨库跨服务的数据一致性成为绕不开的问题,单体时代的本地事务不再适用。Seata提供AT、TCC、Saga、XA四种模式,其中AT与TCC在业务项目中使用最广。本文对比两种模式的使用方式与适用边界,给出可落地的接入示例。
AT模式:无侵入的自动补偿方案
AT模式对业务代码几乎零侵入:在数据源上包一层代理,分支事务执行前后自动记录undo log快照,回滚时反向补偿。典型用法:
// 引入 seata-spring-boot-starter 并配置数据源代理
@GlobalTransactional(timeoutMills = 30000)
public void createOrder(OrderDTO dto) {
orderMapper.insert(dto); // 本地事务
stockService.deduct(dto.getSkuId(), 1); // 远程调用
couponService.use(dto.getCouponId());
}
@GlobalTransactional标注的入口方法即全局事务起点,分支事务由TC协调二阶段提交。业务方法里不需要写任何补偿逻辑,回滚由undo_log自动完成。
AT模式的代价与适用边界
AT模式适合同一事务内数据一致性要求高、并发冲突概率低的场景。两点限制要先确认:
- 全局锁:二阶段提交期间对冲突数据加锁,热点商品并发扣减可能放大延迟;
- 隔离级别:默认读未提交,需要自定义隔离时要在查询上加 SELECT FOR UPDATE 或标记 @GlobalLock。
订单创建、账户积分、库存扣减这类业务,AT模式的成本明显低于手写补偿。
TCC模式:手工补偿控制业务流程
当业务流程中间态必须对外可见、分支事务需要长期持有时,用TCC把每个操作拆成Try、Confirm、Cancel三个阶段:
@TwoPhaseBusinessAction(name = "deductStock",
commitMethod = "confirm", rollbackMethod = "cancel")
public void deduct(BusinessActionContext ctx, Long skuId, int num) {
// Try:冻结库存,不直接扣减
stockMapper.freezeStock(skuId, num);
}
public boolean confirm(BusinessActionContext ctx) {
// Confirm:冻结转实际扣减,最终提交
return stockMapper.confirmFreeze(skuId, num);
}
public boolean cancel(BusinessActionContext ctx) {
// Cancel:解冻恢复
return stockMapper.releaseFreeze(skuId, num);
}
TCC需要业务方自己保证幂等与资源释放,实现成本比AT高,但适合预占资金、扣减积分、订座等强一致性场景。
两种模式的选型对比
| 维度 | AT模式 | TCC模式 |
| 侵入性 | 低,代理数据源即可 | 高,需拆三个阶段 |
| 一致性 | 最终一致 | 强一致(资源冻结) |
| 典型场景 | 订单、库存、积分 | 资金预占、行程订座 |
| 失败恢复 | undo自动回滚 | 人工确认补偿 |
另一条思路是能不用就不用:事务边界跨越的服务,优先通过幂等接口加本地消息表实现最终一致性,把分布式事务的数量压到极少数,Seata只用于真正强一致的短链路。
生产落地注意事项
Seata服务端(TC)需独立部署并持久化事务日志;客户端事务分组要与服务端配置一致。全局事务超时按最慢链路加余量设置,避免慢接口拖死全局锁。上线前做一次故障演练:kill掉一个分支实例,观察二阶段协调与undo日志是否正常工作。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/fen-bu-shi-shi-wu-shi-zhan-seataat-mo-shi-yu-tcc-mo-shi/