Seata TCC 模式实战
TCC 模式原理
三阶段
Try 阶段:
- 检查和预留资源
- 执行业务检查
- 不执行真正的业务操作
Confirm 阶段:
- 执行业务操作
- 使用 Try 阶段预留的资源
- 必须成功,失败需重试
Cancel 阶段:
- 释放 Try 阶段预留的资源
- 回滚业务操作
- 必须成功,失败需重试
工作流程
┌─────────────────────────────────────────────┐
│ Transaction Coordinator │
│ (TC) │
└───────────────────┬─────────────────────────┘
│
┌───────────┼───────────┐
│ │ │
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ 服务 A │ │ 服务 B │ │ 服务 C │
│ Try │ │ Try │ │ Try │
│ Confirm │ │ Confirm │ │ Confirm │
│ Cancel │ │ Cancel │ │ Cancel │
└───────────┘ └───────────┘ └───────────┘
AT vs TCC
| 特性 | AT 模式 | TCC 模式 |
|---|---|---|
| 侵入性 | 无侵入 | 需要实现三个接口 |
| 性能 | 中等 | 高 |
| 隔离性 | 有全局锁 | 无锁,资源预留 |
| 适用场景 | 一般场景 | 高性能要求场景 |
| 实现复杂度 | 低 | 高 |
快速开始
1. 添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
2. 配置 Seata
seata:
enabled: true
tx-service-group: default_tx_group
service:
vgroup-mapping:
default_tx_group: default
grouplist:
default: 127.0.0.1:8091
3. 实现 TCC 接口
public interface TccTransactionService {
/**
* Try 阶段
*/
@TCCBusinessActionName("tccTry")
boolean tryMethod(TccParam param);
/**
* Confirm 阶段
*/
@TCCBusinessActionName("tccConfirm")
boolean confirmMethod(TccParam param);
/**
* Cancel 阶段
*/
@TCCBusinessActionName("tccCancel")
boolean cancelMethod(TccParam param);
}
4. 使用 @LocalTCC 注解
@LocalTCC
public interface AccountTccService {
/**
* Try 阶段:检查和预留资金
*/
@TCCBusinessActionName("accountTry")
boolean tryMethod(@BusinessActionContextParameter(paramName = "userId") Long userId,
@BusinessActionContextParameter(paramName = "amount") BigDecimal amount);
/**
* Confirm 阶段:扣减资金
*/
@TCCBusinessActionName("accountConfirm")
boolean confirmMethod(@BusinessActionContextParameter(paramName = "userId") Long userId,
@BusinessActionContextParameter(paramName = "amount") BigDecimal amount);
/**
* Cancel 阶段:释放预留资金
*/
@TCCBusinessActionName("accountCancel")
boolean cancelMethod(@BusinessActionContextParameter(paramName = "userId") Long userId,
@BusinessActionContextParameter(paramName = "amount") BigDecimal amount);
}
5. 实现 TCC 服务
@Service
public class AccountTccServiceImpl implements AccountTccService {
@Autowired
private AccountMapper accountMapper;
@Override
public boolean tryMethod(Long userId, BigDecimal amount) {
// 1. 检查账户是否存在
Account account = accountMapper.selectById(userId);
if (account == null) {
throw new BusinessException("账户不存在");
}
// 2. 检查余额是否充足
if (account.getMoney().compareTo(amount) < 0) {
throw new BusinessException("余额不足");
}
// 3. 预留资金(冻结)
accountMapper.freezeMoney(userId, amount);
log.info("TCC Try - 预留资金成功,userId={}, amount={}", userId, amount);
return true;
}
@Override
public boolean confirmMethod(Long userId, BigDecimal amount) {
// 1. 扣减冻结资金
accountMapper.deductFrozenMoney(userId, amount);
log.info("TCC Confirm - 扣减资金成功,userId={}, amount={}", userId, amount);
return true;
}
@Override
public boolean cancelMethod(Long userId, BigDecimal amount) {
// 1. 释放冻结资金
accountMapper.unfreezeMoney(userId, amount);
log.info("TCC Cancel - 释放资金成功,userId={}, amount={}", userId, amount);
return true;
}
}
6. 使用全局事务
@Service
public class OrderTccService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private AccountTccService accountTccService;
@Autowired
private StorageTccService storageTccService;
@GlobalTransactional
public void createOrder(Order order) {
// 1. 创建订单
orderMapper.insert(order);
// 2. TCC 扣减账户
accountTccService.tryMethod(order.getUserId(), order.getAmount());
// 3. TCC 扣减库存
storageTccService.tryMethod(order.getProductId(), order.getCount());
// 4. 模拟异常
if (order.getAmount() > 1000) {
throw new BusinessException("订单金额过大");
}
}
}
进阶实现
1. 防悬挂控制
问题:Cancel 比 Try 先执行
解决方案:
@Service
public class StorageTccServiceImpl implements StorageTccService {
@Autowired
private StorageMapper storageMapper;
@Autowired
private TccFenceMapper tccFenceMapper;
@Override
public boolean tryMethod(Long productId, Integer count) {
// 检查是否已悬挂
String xid = RootContext.getXID();
Long branchId = BranchSessionManager.getBranchId();
if (tccFenceMapper.selectByXidAndBranchId(xid, branchId) != null) {
log.warn("TCC 已悬挂,xid={}, branchId={}", xid, branchId);
return true;
}
// 检查库存
Storage storage = storageMapper.selectById(productId);
if (storage == null || storage.getCount() < count) {
throw new BusinessException("库存不足");
}
// 预留库存
storageMapper.freezeStock(productId, count);
// 记录 TCC 事务
tccFenceMapper.insert(new TccFence(xid, branchId, "try", Status.TRYED));
return true;
}
@Override
public boolean confirmMethod(Long productId, Integer count) {
String xid = RootContext.getXID();
Long branchId = BranchSessionManager.getBranchId();
// 检查是否已执行
TccFence fence = tccFenceMapper.selectByXidAndBranchId(xid, branchId);
if (fence == null) {
// Cancel 先执行,悬挂
log.warn("TCC 悬挂,xid={}, branchId={}", xid, branchId);
return true;
}
if (fence.getStatus() == Status.CONFIRMED) {
log.warn("TCC 已确认,xid={}, branchId={}", xid, branchId);
return true;
}
// 扣减库存
storageMapper.deductFrozenStock(productId, count);
// 更新状态
tccFenceMapper.updateStatus(xid, branchId, Status.CONFIRMED);
return true;
}
@Override
public boolean cancelMethod(Long productId, Integer count) {
String xid = RootContext.getXID();
Long branchId = BranchSessionManager.getBranchId();
// 检查是否已执行
TccFence fence = tccFenceMapper.selectByXidAndBranchId(xid, branchId);
if (fence == null) {
// Try 未执行,记录悬挂
tccFenceMapper.insert(new TccFence(xid, branchId, "cancel", Status.SUSPENDED));
log.warn("TCC 悬挂,xid={}, branchId={}", xid, branchId);
return true;
}
if (fence.getStatus() == Status.CANCELLED) {
log.warn("TCC 已取消,xid={}, branchId={}", xid, branchId);
return true;
}
// 释放库存
storageMapper.unfreezeStock(productId, count);
// 更新状态
tccFenceMapper.updateStatus(xid, branchId, Status.CANCELLED);
return true;
}
}
2. 空回滚控制
问题:Try 未执行,直接回滚
解决方案:
@Override
public boolean cancelMethod(Long productId, Integer count) {
String xid = RootContext.getXID();
Long branchId = BranchSessionManager.getBranchId();
// 检查 Try 是否执行
TccFence fence = tccFenceMapper.selectByXidAndBranchId(xid, branchId);
if (fence == null) {
// Try 未执行,空回滚
log.warn("TCC 空回滚,xid={}, branchId={}", xid, branchId);
tccFenceMapper.insert(new TccFence(xid, branchId, "cancel", Status.ROLLEDBACK));
return true;
}
// 正常回滚逻辑
storageMapper.unfreezeStock(productId, count);
tccFenceMapper.updateStatus(xid, branchId, Status.CANCELLED);
return true;
}
3. 幂等控制
问题:Confirm/Cancel 重复执行
解决方案:
@Override
public boolean confirmMethod(Long productId, Integer count) {
String xid = RootContext.getXID();
Long branchId = BranchSessionManager.getBranchId();
// 检查是否已确认
TccFence fence = tccFenceMapper.selectByXidAndBranchId(xid, branchId);
if (fence != null && fence.getStatus() == Status.CONFIRMED) {
log.warn("TCC 重复确认,xid={}, branchId={}", xid, branchId);
return true; // 幂等返回
}
// 执行确认逻辑
storageMapper.deductFrozenStock(productId, count);
tccFenceMapper.updateStatus(xid, branchId, Status.CONFIRMED);
return true;
}
4. 异常处理
@Override
public boolean tryMethod(Long productId, Integer count) {
try {
// 业务逻辑
storageMapper.freezeStock(productId, count);
return true;
} catch (BusinessException e) {
// 业务异常,直接抛出
log.error("TCC Try 业务异常", e);
throw e;
} catch (Exception e) {
// 系统异常,记录日志
log.error("TCC Try 系统异常", e);
throw new SystemException("TCC Try 失败", e);
}
}
性能优化
1. 资源预留优化
@Override
public boolean tryMethod(Long productId, Integer count) {
// 使用 Redis 预扣减,提高性能
String key = "stock:freeze:" + productId;
Long frozen = redisTemplate.opsForValue().increment(key, count);
// 检查是否超卖
if (frozen > getStockCount(productId)) {
redisTemplate.opsForValue().decrement(key, count);
throw new BusinessException("库存不足");
}
// 异步同步到数据库
asyncSyncToDatabase(productId, count);
return true;
}
2. 批量确认
@GlobalTransactional
public void batchConfirm(List<Order> orders) {
for (Order order : orders) {
// 批量确认
accountTccService.confirmMethod(order.getUserId(), order.getAmount());
storageTccService.confirmMethod(order.getProductId(), order.getCount());
}
}
3. 异步 Confirm
@GlobalTransactional(asyncCommit = true)
public void createOrder(Order order) {
// Try 阶段
accountTccService.tryMethod(order.getUserId(), order.getAmount());
storageTccService.tryMethod(order.getProductId(), order.getCount());
// Confirm 阶段异步执行
}
监控与排查
1. TCC 事务日志
@RestController
@RequestMapping("/tcc")
public class TccController {
@Autowired
private TccFenceMapper tccFenceMapper;
@GetMapping("/fences")
public List<TccFence> listFences(@RequestParam String xid) {
return tccFenceMapper.selectList(
new QueryWrapper<TccFence>().eq("xid", xid)
);
}
@GetMapping("/fences/stats")
public Map<String, Object> getStats() {
Map<String, Object> stats = new HashMap<>();
stats.put("tryed", countByStatus(Status.TRYED));
stats.put("confirmed", countByStatus(Status.CONFIRMED));
stats.put("cancelled", countByStatus(Status.CANCELLED));
stats.put("suspended", countByStatus(Status.SUSPENDED));
return stats;
}
}
2. 事务状态监控
@Component
public class TccTransactionMonitor {
@Autowired
private MeterRegistry meterRegistry;
@Scheduled(fixedRate = 60000)
public void monitor() {
// 统计各状态事务数
long tryedCount = countByStatus(Status.TRYED);
long confirmedCount = countByStatus(Status.CONFIRMED);
long cancelledCount = countByStatus(Status.CANCELLED);
meterRegistry.gauge("seata.tcc.tryed", tryedCount);
meterRegistry.gauge("seata.tcc.confirmed", confirmedCount);
meterRegistry.gauge("seata.tcc.cancelled", cancelledCount);
}
}
3. 异常告警
@Component
public class TccAlertHandler {
@Autowired
private AlertService alertService;
@Scheduled(fixedRate = 60000)
public void checkSuspendedTransactions() {
long suspendedCount = countByStatus(Status.SUSPENDED);
if (suspendedCount > 10) {
alertService.sendAlert(
String.format("TCC 悬挂事务过多:%d 个", suspendedCount)
);
}
}
}
最佳实践
1. 接口设计
- Try 接口:只检查和预留,不执行真正业务
- Confirm 接口:必须成功,失败需重试
- Cancel 接口:必须成功,失败需重试
- 幂等性:三个接口都要实现幂等
2. 资源管理
- 及时释放:Cancel 必须释放资源
- 超时控制:设置合理的超时时间
- 资源清理:定期清理过期预留资源
3. 异常处理
- 业务异常:Try 阶段抛出,触发回滚
- 系统异常:记录日志,允许重试
- 网络异常:实现重试机制
4. 性能优化
- 减少 Try 耗时:Try 阶段尽量轻量
- 异步 Confirm: Confirm 阶段可异步
- 批量操作:支持批量 Confirm/Cancel
常见问题
1. 空回滚
问题:Try 未执行,直接 Cancel
解决方案:
- 实现防悬挂控制
- 记录 TCC 事务状态
- Cancel 检查 Try 是否执行
2. 幂等性
问题:Confirm/Cancel 重复执行
解决方案:
- 记录事务状态
- 检查是否已执行
- 实现幂等逻辑
3. 数据不一致
问题:TCC 执行后数据不一致
解决方案:
- 检查网络稳定性
- 查看 TCC 事务日志
- 实现补偿机制
总结
Seata TCC 模式是一种高性能的分布式事务解决方案,通过 Try-Confirm-Cancel 三个接口实现两阶段提交。
相比 AT 模式,TCC 模式性能更高,但实现复杂度也更高,需要处理空回滚、防悬挂、幂等性问题。
在生产环境中,建议建立完善的监控告警机制,并实现补偿机制保证数据一致性。