微服务架构下,一次业务操作跨多个服务写库,本地事务就不够用了,分布式事务成为后端开发的必修课。常见的落地手段有Seata AT模式、TCC、本地消息表、事务消息(RocketMQ)。本文对比这几种方案的使用场景,重点拆解Seata AT和TCC的配置与注意事项,给出选型结论和代码示例。
一、分布式事务的几种方案对比
- Seata AT模式:对业务代码侵入最小,框架自动生成undo_log,适合大多数增删改场景。
- Seata TCC:需要业务实现Try/Confirm/Cancel三方法,适合资金、库存等强一致性场景。
- 本地消息表+定时任务:最终一致,简单可靠,适合订单与积分这类异步场景。
- RocketMQ事务消息:事务与消息同生命周期,替代本地消息表的主流方案。
选型结论:链路不长、一致性要求高用AT;涉及资金、需要资源预留用TCC;可以接受异步最终一致用事务消息。不要为了分布式事务把架构复杂化,能用本地事务就不要拆分。
二、Seata AT模式部署与接入
Seata Server(TC)独立部署,业务服务接入RM和TM。AT模式靠全局锁+undo_log实现。以Spring Boot 3为例:
# application.yml
seata:
enabled: true
application-id: order-service
tx-service-group: my_test_tx_group
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
业务侧:
@GlobalTransactional(name = "create_order", timeoutMills = 30000)
public void createOrder(OrderDTO dto) {
orderMapper.insert(dto.toOrder());
stockService.deduct(dto.getSkuId(), dto.getCount());
accountService.debit(dto.getUserId(), dto.getAmount());
}
AT模式注意事项:
- 业务表必须有主键,undo_log表(每库一张)由seata-server初始化。
- 不支持DDL和部分函数,SELECT for update要慎用。
- 跨库跨事务组时,全局锁等待超时会造成热点行阻塞,配置defaultGlobalLockTimeout。
三、TCC模式落地示例
TCC把一笔事务拆成Try(预留资源)、Confirm(确认)、Cancel(回滚)。以扣库存为例:
public class InventoryServiceImpl implements InventoryTccService {
@TwoPhaseBusinessAction(name = "deductStock", commitMethod = "confirm", rollbackMethod = "cancel")
public boolean tryDeduct(BusinessActionContext ctx, Long skuId, int count) {
// Try:冻结库存,不真正扣减
return inventoryMapper.freeze(skuId, count) > 0;
}
public boolean confirm(BusinessActionContext ctx) {
// Confirm:把冻结库存转成实际扣减
return inventoryMapper.freeze2Deduct(ctx.getBusinessKey()) > 0;
}
public boolean cancel(BusinessActionContext ctx) {
// Cancel:解冻
return inventoryMapper.unfreeze(ctx.getBusinessKey()) > 0;
}
}
TCC的难点在幂等和悬挂:Confirm/Cancel可能重复执行,必须做幂等;Cancel在Try失败或超时时也要能补偿,预留的冻结资源要能正确释放。
四、事务消息:异步最终一致的替代方案
不追求实时强一致的下单、积分、短信场景,用RocketMQ事务消息更简单:
TransactionMQProducer producer = new TransactionMQProducer("tx_producer");
producer.setTransactionListener(new TransactionListener() {
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
// 本地事务:写订单表
return orderMapper.insert((Order) arg) > 0
? LocalTransactionState.COMMIT_MESSAGE
: LocalTransactionState.ROLLBACK_MESSAGE;
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
// 回查:查订单是否存在
return orderMapper.existsByMsgId(msg.getKeys())
? LocalTransactionState.COMMIT_MESSAGE
: LocalTransactionState.ROLLBACK_MESSAGE;
}
});
事务消息把“本地事务提交”和“消息发送”绑定成原子操作,避免了本地消息表多一张表的心智负担。
五、选型落地建议
分布式事务没有银弹:AT模式适合从单体拆微服务的过渡期,改造成本低;TCC适合资金类和库存强一致性;事务消息适合能容忍秒级延迟的场景。性能上AT和TCC都会放大数据库压力,压测时要把Seata的事务上下文和锁等待时间算进去。上线前一定要做故障演练:模拟下游超时、重复调用、Cancel重试,验证整个链路不回滚错数据。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/fen-bu-shi-shi-wu-shi-zhan-seataat-mo-shi-yu-tcc-fang-an/