1. 分布式事务中的TCC模式核心挑战
第一次接触TCC事务是在处理电商平台的积分兑换系统时。当时我们的系统经常出现"积分扣了但商品没发"的情况,排查后发现是网络抖动导致Confirm阶段失败。这让我意识到,TCC这种看似简单的"预留-确认"机制,在实际分布式环境中会遇到各种边界场景。
TCC(Try-Confirm-Cancel)作为补偿型事务的典型实现,其核心思想是将业务操作拆分为三个阶段:
- Try:预留资源(如冻结积分)
- Confirm:确认执行(如扣减冻结的积分)
- Cancel:取消释放(如解冻积分)
但在实际生产环境中,网络分区、服务宕机、消息重试等分布式系统固有特性,会导致两个经典问题:
- 空回滚(Empty Rollback):Cancel接口被调用时,对应的Try操作还未执行
- 悬挂(Hanging):Try操作在Cancel之后才到达服务端
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 空回滚问题的产生与防御
去年双十一大促期间,我们的订单系统就遭遇过空回滚的雪崩效应。当时由于支付服务响应延迟,大量订单直接触发了Cancel流程,但实际这些订单的Try请求还在MQ中堆积等待消费。
2.1 空回滚发生条件
当以下两个条件同时满足时发生:
- Try请求由于网络延迟或服务不可用未能到达参与者
- 事务协调器因超时触发回滚,Cancel请求先于Try到达
java复制// 典型错误实现示例
@Transactional
public boolean cancel(Long xid) {
// 直接执行业务回滚逻辑
accountService.unfreeze(xid); // 如果Try未执行,会导致数据不一致
return true;
}
2.2 解决方案:事务控制表
我们在每个参与者服务中建立了tcc_transaction_control表:
| 字段 | 类型 | 说明 |
|---|---|---|
| xid | varchar(64) | 全局事务ID |
| status | tinyint | 1-TRY_SUCCESS, 2-CONFIRMED, 3-CANCELLED |
| create_time | datetime | 记录创建时间 |
| update_time | datetime | 最后更新时间 |
改进后的Cancel逻辑:
java复制public boolean cancel(Long xid) {
// 查询事务记录
TccTransaction record = transactionDao.selectByXid(xid);
if (record == null) {
// 插入空回滚记录
transactionDao.insertEmptyRollback(xid);
return true;
}
if (record.getStatus() == TRY_SUCCESS) {
// 正常回滚流程
accountService.unfreeze(xid);
transactionDao.updateStatus
