大疆Java面试被问:TCC事务的悬挂、空回滚问题解决方案

一、问题定义与成因分析

1. 悬挂问题(Hanging)

定义 :Try阶段预留的资源迟迟无法释放,导致资源长时间被占用
发生场景

text

复制

下载

复制代码
正常流程:Try → Confirm/Cancel
悬挂流程:Try → 网络超时 → 触发Cancel → Cancel未执行 → 资源锁定

根本原因

  • Try阶段执行成功,但网络超时导致事务管理器未收到响应

  • 事务管理器触发全局回滚,调用Cancel

  • Cancel调用失败或未执行,预留资源无法释放

2. 空回滚问题(Empty Rollback)

定义 :Cancel接口被调用时,对应的Try操作并未执行
发生场景

text

复制

下载

复制代码
正常流程:Try失败 → 调用Cancel
空回滚:Try未执行(网络超时) → 事务管理器超时 → 调用Cancel

根本原因

  • Try阶段因网络问题未到达服务方

  • 事务管理器超时触发全局回滚

  • Cancel接口被调用,但无Try记录可回滚

二、悬挂问题解决方案

方案一:状态标记 + 过期清理

java

复制

下载

复制代码
@Component
public class TccHangingResolver {
    
    @Autowired
    private TransactionLogService logService;
    
    /**
     * 1. Try阶段:记录事务状态
     */
    @Transactional(rollbackFor = Exception.class)
    public void tryPhase(String xid, String bizId) {
        // 检查是否已有未完成的事务
        TransactionLog existing = logService.getUnfinished(xid);
        if (existing != null) {
            // 已有未完成事务,拒绝新请求
            throw new TccHangingException("存在悬挂事务,请等待处理");
        }
        
        // 插入事务记录
        TransactionLog log = new TransactionLog();
        log.setXid(xid);
        log.setBizId(bizId);
        log.setStatus(TccStatus.TRYING);
        log.setExpireTime(LocalDateTime.now().plusMinutes(30)); // 30分钟过期
        log.setCreatedTime(LocalDateTime.now());
        logService.save(log);
        
        // 执行业务Try逻辑
        // ...
    }
    
    /**
     * 2. Confirm阶段:更新状态
     */
    public void confirmPhase(String xid) {
        TransactionLog log = logService.getByXid(xid);
        if (log == null || log.getStatus() != TccStatus.TRYING) {
            throw new IllegalStateException("事务状态异常");
        }
        
        // 更新为完成状态
        log.setStatus(TccStatus.CONFIRMED);
        log.setFinishedTime(LocalDateTime.now());
        logService.update(log);
        
        // 执行业务Confirm逻辑
        // ...
    }
    
    /**
     * 3. 定时任务:清理悬挂事务
     */
    @Scheduled(fixedDelay = 60000) // 每分钟执行一次
    public void cleanHangingTransactions() {
        List<TransactionLog> hangingLogs = logService.findHangingTransactions();
        
        for (TransactionLog log : hangingLogs) {
            if (isExpired(log)) {
                // 执行补偿逻辑
                compensate(log);
                
                // 标记为已处理
                log.setStatus(TccStatus.COMPENSATED);
                logService.update(log);
            }
        }
    }
    
    /**
     * 4. 补偿策略
     */
    private void compensate(TransactionLog log) {
        try {
            // 根据业务类型执行不同的补偿逻辑
            switch (log.getBizType()) {
                case "ORDER":
                    orderService.compensateOrder(log.getBizId());
                    break;
                case "INVENTORY":
                    inventoryService.releaseInventory(log.getBizId());
                    break;
                // ... 其他业务补偿
            }
        } catch (Exception e) {
            // 记录异常,人工干预
            log.error("补偿执行失败: {}", log.getXid(), e);
            alertService.sendAlert("TCC补偿失败", log);
        }
    }
}

方案二:预检查 + 防悬挂锁

java

复制

下载

复制代码
public class HangingPreventionLock {
    
    private final RedissonClient redisson;
    
    /**
     * 获取防悬挂锁
     */
    public boolean tryAcquireHangingLock(String xid, long timeoutSeconds) {
        String lockKey = "tcc:hanging:lock:" + xid;
        RLock lock = redisson.getLock(lockKey);
        
        try {
            return lock.tryLock(timeoutSeconds, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }
    
    /**
     * Try阶段前置检查
     */
    public boolean preCheck(String xid) {
        // 1. 检查是否已有Confirm/Cancel记录
        if (hasFinished(xid)) {
            return false; // 已结束,拒绝Try
        }
        
        // 2. 检查是否在取消中
        if (isCancelling(xid)) {
            return false; // 正在取消,拒绝Try
        }
        
        // 3. 获取锁,防止并发问题
        if (!tryAcquireHangingLock(xid, 10)) {
            throw new TccLockException("获取防悬挂锁失败");
        }
        
        return true;
    }
}

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案
【点击此处即可/免费获取】

三、空回滚问题解决方案

方案一:Try状态检查 + 幂等处理

java

复制

下载

复制代码
@Component
public class EmptyRollbackPreventer {
    
    @Autowired
    private TransactionStateStorage stateStorage;
    
    /**
     * Cancel接口实现
     */
    @Transactional
    public void cancel(String xid, String bizId) {
        // 1. 检查Try是否执行过
        TccState state = stateStorage.getState(xid);
        
        if (state == null || state.getStatus() == TccStatus.INIT) {
            // Try未执行,属于空回滚
            log.warn("检测到空回滚: xid={}", xid);
            
            // 记录空回滚日志
            recordEmptyRollback(xid);
            
            // 返回成功,保证幂等性
            stateStorage.saveState(xid, TccStatus.CANCELLED_EMPTY);
            return;
        }
        
        if (state.getStatus() == TccStatus.CANCELLED 
            || state.getStatus() == TccStatus.CANCELLED_EMPTY) {
            // 已取消过,幂等返回
            log.info("Cancel已执行过,幂等返回: xid={}", xid);
            return;
        }
        
        if (state.getStatus() != TccStatus.TRY_SUCCESS) {
            throw new IllegalStateException("事务状态异常,无法Cancel");
        }
        
        // 2. 执行实际的Cancel逻辑
        try {
            businessCancel(bizId);
            
            // 3. 更新状态
            stateStorage.saveState(xid, TccStatus.CANCELLED);
            
        } catch (Exception e) {
            // 记录失败,需要重试
            stateStorage.saveState(xid, TccStatus.CANCEL_FAILED);
            throw e;
        }
    }
    
    /**
     * Try接口:必须记录状态
     */
    @Transactional
    public void tryBusiness(String xid, String bizId) {
        // 1. 先记录状态
        stateStorage.saveState(xid, TccStatus.TRYING);
        
        try {
            // 2. 执行业务逻辑
            doTryBusiness(bizId);
            
            // 3. 标记Try成功
            stateStorage.saveState(xid, TccStatus.TRY_SUCCESS);
            
        } catch (Exception e) {
            // 标记Try失败
            stateStorage.saveState(xid, TccStatus.TRY_FAILED);
            throw e;
        }
    }
}

方案二:预写日志 + 状态确认

java

复制

下载

复制代码
public class PreWriteLogSolution {
    
    private final TransactionLogDAO logDAO;
    
    /**
     * 方案:两阶段状态确认
     */
    public void processTransaction(String xid) {
        // 第一阶段:预写状态日志
        preWriteLog(xid);
        
        try {
            // 第二阶段:执行业务
            executeBusiness(xid);
            
            // 第三阶段:确认日志
            confirmLog(xid);
            
        } catch (Exception e) {
            // 第四阶段:回滚检查
            rollbackWithCheck(xid);
        }
    }
    
    private void preWriteLog(String xid) {
        TransactionLog log = new TransactionLog();
        log.setXid(xid);
        log.setStatus(TccStatus.PREPARED);
        log.setCreateTime(System.currentTimeMillis());
        log.setExpireTime(System.currentTimeMillis() + 300000); // 5分钟过期
        
        // 插入预写日志
        logDAO.insert(log);
    }
    
    private void rollbackWithCheck(String xid) {
        // 检查预写日志是否存在
        TransactionLog preLog = logDAO.selectByXid(xid);
        
        if (preLog == null || preLog.getStatus() != TccStatus.PREPARED) {
            // 没有预写日志,属于空回滚
            logDAO.insertEmptyRollbackLog(xid);
            return;
        }
        
        // 执行实际的回滚逻辑
        doRollback(xid);
        
        // 更新日志状态
        preLog.setStatus(TccStatus.ROLLBACKED);
        logDAO.update(preLog);
    }
}

四、综合解决方案:防悬挂空回滚框架

完整架构设计

java

复制

下载

复制代码
@Slf4j
@Component
public class TccProblemSolver {
    
    @Autowired
    private TransactionStateService stateService;
    @Autowired
    private Compensator compensator;
    @Autowired
    private ScheduleExecutor scheduler;
    
    /**
     * TCC Try阶段 - 增强版
     */
    public void enhancedTry(String xid, String bizId, TccAction tryAction) {
        // 1. 防悬挂检查
        if (stateService.hasUnfinishedTransaction(bizId)) {
            throw new TccHangingRiskException("业务ID存在未完成事务");
        }
        
        // 2. 获取分布式锁
        DistributedLock lock = lockManager.acquireLock(bizId);
        try {
            // 3. 记录开始状态
            stateService.recordTryStart(xid, bizId);
            
            // 4. 执行业务Try
            tryAction.execute();
            
            // 5. 记录Try成功
            stateService.recordTrySuccess(xid);
            
        } catch (Exception e) {
            // 6. 记录Try失败
            stateService.recordTryFailure(xid, e.getMessage());
            throw e;
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * TCC Cancel阶段 - 增强版
     */
    public void enhancedCancel(String xid, String bizId, TccAction cancelAction) {
        // 1. 检查是否为空回滚
        TransactionState state = stateService.getState(xid);
        
        if (state == null || state.getStatus() == Status.INITIAL) {
            // 空回滚处理
            logEmptyRollback(xid);
            stateService.markAsEmptyRollback(xid);
            return;
        }
        
        // 2. 幂等检查
        if (state.getStatus() == Status.CANCELLED 
            || state.getStatus() == Status.CANCELLED_EMPTY) {
            log.info("Cancel已执行,幂等返回: xid={}", xid);
            return;
        }
        
        // 3. 状态验证
        if (state.getStatus() != Status.TRY_SUCCESS) {
            throw new IllegalStateException("事务状态不允许Cancel: " + state.getStatus());
        }
        
        // 4. 执行业务Cancel
        try {
            cancelAction.execute();
            stateService.markAsCancelled(xid);
        } catch (Exception e) {
            stateService.markAsCancelFailed(xid);
            throw e;
        }
    }
    
    /**
     * 悬挂事务检测与处理
     */
    @Scheduled(fixedDelay = 30000)
    public void detectAndHandleHanging() {
        // 1. 查询过期未完成的事务
        List<Transaction> hangingTransactions = 
            stateService.findHangingTransactions(5); // 超过5分钟
        
        for (Transaction tx : hangingTransactions) {
            try {
                // 2. 根据业务类型执行补偿
                CompensationResult result = compensator.compensate(tx);
                
                if (result.isSuccess()) {
                    // 3. 标记为已补偿
                    stateService.markAsCompensated(tx.getXid());
                    log.info("悬挂事务补偿成功: {}", tx.getXid());
                } else {
                    // 4. 补偿失败,升级处理
                    handleCompensationFailure(tx, result);
                }
            } catch (Exception e) {
                log.error("悬挂事务处理异常: {}", tx.getXid(), e);
            }
        }
    }
    
    /**
     * 状态机设计
     */
    public enum Status {
        INITIAL,          // 初始状态
        TRY_STARTED,      // Try开始
        TRY_SUCCESS,      // Try成功
        TRY_FAILED,       // Try失败
        CONFIRMING,       // Confirm中
        CONFIRMED,        // Confirm成功
        CANCELLING,       // Cancel中
        CANCELLED,        // Cancel成功
        CANCELLED_EMPTY,  // 空回滚
        HANGING,          // 悬挂中
        COMPENSATED       // 已补偿
    }
}

五、数据库表设计

事务状态表

sql

复制

下载

复制代码
CREATE TABLE tcc_transaction (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    xid VARCHAR(64) NOT NULL COMMENT '全局事务ID',
    biz_id VARCHAR(64) NOT NULL COMMENT '业务ID',
    biz_type VARCHAR(32) NOT NULL COMMENT '业务类型',
    status VARCHAR(32) NOT NULL COMMENT '事务状态',
    
    -- 时间字段
    create_time DATETIME NOT NULL COMMENT '创建时间',
    update_time DATETIME NOT NULL COMMENT '更新时间',
    expire_time DATETIME NOT NULL COMMENT '过期时间',
    finish_time DATETIME COMMENT '完成时间',
    
    -- 控制字段
    retry_count INT DEFAULT 0 COMMENT '重试次数',
    max_retry INT DEFAULT 3 COMMENT '最大重试次数',
    version INT DEFAULT 1 COMMENT '版本号',
    
    -- 索引
    UNIQUE KEY uk_xid (xid),
    KEY idx_biz_id (biz_id),
    KEY idx_status_expire (status, expire_time),
    KEY idx_create_time (create_time)
) COMMENT 'TCC事务状态表';

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案
【点击此处即可/免费获取】

防悬挂锁表

sql

复制

下载

复制代码
CREATE TABLE tcc_hanging_lock (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    lock_key VARCHAR(128) NOT NULL COMMENT '锁Key',
    lock_holder VARCHAR(64) NOT NULL COMMENT '锁持有者',
    expire_time DATETIME NOT NULL COMMENT '锁过期时间',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    
    UNIQUE KEY uk_lock_key (lock_key),
    KEY idx_expire_time (expire_time)
) COMMENT '防悬挂锁表';

六、最佳实践总结

1. 悬挂问题防护要点

  • ✅ Try前检查是否存在未完成事务

  • ✅ 设置合理的资源预留过期时间

  • ✅ 实现补偿任务定期清理悬挂资源

  • ✅ 使用分布式锁防止并发悬挂

2. 空回滚问题防护要点

  • ✅ Try阶段必须记录状态日志

  • ✅ Cancel前检查Try是否执行成功

  • ✅ 实现幂等Cancel接口

  • ✅ 记录空回滚日志用于监控

3. 监控与告警

java

复制

下载

复制代码
// 关键监控指标
public class TccMetrics {
    // 悬挂事务数量
    @Meter("tcc.hanging.count")
    private Meter hangingCount;
    
    // 空回滚比例
    @Gauge("tcc.empty_rollback.ratio")
    private Double emptyRollbackRatio;
    
    // 补偿成功率
    @Gauge("tcc.compensation.success_rate")
    private Double compensationSuccessRate;
    
    // 事务平均耗时
    @Timer("tcc.transaction.duration")
    private Timer transactionDuration;
}

4. 配置建议

yaml

复制

下载

复制代码
tcc:
  hanging:
    check-interval: 30000    # 悬挂检查间隔30秒
    expire-time: 300000      # 资源过期时间5分钟
    max-retry: 3             # 最大重试次数
    
  empty-rollback:
    enabled: true            # 启用空回滚防护
    log-path: /logs/tcc/empty-rollback.log
    
  compensation:
    enabled: true            # 启用自动补偿
    thread-pool-size: 10     # 补偿线程池大小
    batch-size: 100          # 批量处理大小

通过以上综合解决方案,可以有效预防和解决TCC事务中的悬挂和空回滚问题,确保分布式事务的可靠性和数据一致性。

相关推荐
自可乐17 小时前
n8n全面学习教程:从入门到精通的自动化工作流引擎实践指南
运维·人工智能·学习·自动化
king of code porter17 小时前
百宝箱企业版搭建智能体应用-创建应用
人工智能·大模型·智能体
HDO清风17 小时前
CASIA-HWDB2.x 数据集DGRL文件解析(python)
开发语言·人工智能·pytorch·python·目标检测·计算机视觉·restful
2201_7569890917 小时前
C++中的事件驱动编程
开发语言·c++·算法
策知道17 小时前
依托政府工作报告准备省考【经验贴】
大数据·数据库·人工智能·搜索引擎·政务
多米Domi01117 小时前
0x3f 第48天 面向实习的八股背诵第五天 + 堆一题 背了JUC的题,java.util.Concurrency
开发语言·数据结构·python·算法·leetcode·面试
2301_8223776517 小时前
模板元编程调试方法
开发语言·c++·算法
csbysj202017 小时前
Python 循环嵌套
开发语言
工程师老罗18 小时前
Pytorch如何加载和读取VOC数据集用来做目标检测?
人工智能·pytorch·目标检测
测试_AI_一辰18 小时前
Agent & RAG 测试工程05:把 RAG 的检索过程跑清楚:chunk 是什么、怎么来的、怎么被命中的
开发语言·人工智能·功能测试·自动化·ai编程