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

一、问题定义与核心概念

1.1 问题定义

悬挂问题(Hanging)

text

复制

下载

复制代码
正常流程:Try → Confirm/Cancel
悬挂场景:Try执行成功 → 网络超时 → 事务管理器调用Cancel → Cancel未执行
结果:预留的资源无法释放,形成悬挂事务

空回滚问题(Empty Rollback)

text

复制

下载

复制代码
正常流程:Try失败 → 调用Cancel
空回滚场景:Try未执行(网络超时) → 事务管理器超时触发Cancel → Cancel无Try记录可回滚
结果:执行了无效的Cancel操作

二、悬挂问题解决方案

2.1 状态标记法

java

复制

下载

复制代码
@Component
public class HangPreventionService {
    
    @Autowired
    private TransactionStateStore stateStore;
    
    /**
     * Try阶段:记录事务状态
     */
    @Transactional
    public void tryPhase(String xid, String businessId) {
        // 1. 检查是否存在未完成的同一业务事务
        TransactionState existing = stateStore.getUnfinishedByBusinessId(businessId);
        if (existing != null && !isExpired(existing)) {
            throw new TransactionHangingException("存在未完成事务,请稍后重试");
        }
        
        // 2. 插入事务记录
        TransactionState state = new TransactionState();
        state.setXid(xid);
        state.setBusinessId(businessId);
        state.setStatus(TransactionStatus.TRYING);
        state.setCreateTime(new Date());
        state.setExpireTime(DateUtils.addMinutes(new Date(), 30));
        stateStore.save(state);
        
        // 3. 执行业务Try逻辑
        businessService.tryOperation(businessId);
    }
    
    /**
     * Cancel阶段:检查并处理悬挂
     */
    @Transactional
    public void cancelPhase(String xid) {
        // 1. 查询事务状态
        TransactionState state = stateStore.getByXid(xid);
        
        if (state == null) {
            // 未找到记录,可能是空回滚或数据不一致
            log.warn("未找到事务记录,可能已超时清理: xid={}", xid);
            return;
        }
        
        // 2. 状态验证
        if (state.getStatus() == TransactionStatus.CANCELLED) {
            log.info("事务已取消,幂等返回: xid={}", xid);
            return;
        }
        
        if (state.getStatus() != TransactionStatus.TRYING) {
            throw new IllegalStateException("事务状态异常,无法取消");
        }
        
        // 3. 检查是否悬挂(Try完成但未收到Confirm)
        if (isHanging(state)) {
            log.warn("检测到悬挂事务,执行补偿: xid={}", xid);
            compensateHanging(state);
        } else {
            // 4. 正常取消
            businessService.cancelOperation(state.getBusinessId());
        }
        
        // 5. 更新状态
        state.setStatus(TransactionStatus.CANCELLED);
        state.setUpdateTime(new Date());
        stateStore.update(state);
    }
}

2.2 防悬挂锁机制

java

复制

下载

复制代码
public class HangPreventionLock {
    
    private final RedissonClient redisson;
    private final long lockTimeout = 30; // 锁超时时间(秒)
    
    /**
     * 获取防悬挂锁
     */
    public boolean acquireHangLock(String xid, String businessId) {
        String lockKey = String.format("tcc:hanging:lock:%s:%s", xid, businessId);
        RLock lock = redisson.getLock(lockKey);
        
        try {
            // 尝试获取锁,最多等待5秒
            return lock.tryLock(5, lockTimeout, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }
    
    /**
     * Try前置检查
     */
    public boolean preCheckTry(String xid, String businessId) {
        // 1. 检查是否正在取消中
        String cancelingKey = String.format("tcc:canceling:%s", xid);
        if (redisson.getBucket(cancelingKey).get() != null) {
            throw new TransactionCancellingException("事务正在取消中");
        }
        
        // 2. 获取防悬挂锁
        if (!acquireHangLock(xid, businessId)) {
            throw new TransactionLockException("获取防悬挂锁失败");
        }
        
        return true;
    }
}

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

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

2.3 定时清理悬挂事务

java

复制

下载

复制代码
@Component
public class HangTransactionCleaner {
    
    @Autowired
    private TransactionStateStore stateStore;
    
    @Autowired
    private Compensator compensator;
    
    /**
     * 定时清理悬挂事务(每分钟执行一次)
     */
    @Scheduled(fixedDelay = 60000)
    public void cleanHangingTransactions() {
        // 1. 查询超时未完成的事务
        List<TransactionState> hangingTransactions = 
            stateStore.findHangingTransactions(5); // 超过5分钟
        
        // 2. 批量处理
        hangingTransactions.forEach(this::processHangingTransaction);
    }
    
    private void processHangingTransaction(TransactionState transaction) {
        try {
            log.info("开始处理悬挂事务: xid={}, status={}", 
                transaction.getXid(), transaction.getStatus());
            
            switch (transaction.getStatus()) {
                case TRYING:
                    // Try成功但未收到Confirm/Cancel
                    handleHangingTry(transaction);
                    break;
                    
                case CONFIRMING:
                    // Confirm执行中卡住
                    handleHangingConfirm(transaction);
                    break;
                    
                case CANCELLING:
                    // Cancel执行中卡住
                    handleHangingCancel(transaction);
                    break;
            }
            
            // 3. 标记为已处理
            transaction.setStatus(TransactionStatus.COMPENSATED);
            transaction.setUpdateTime(new Date());
            stateStore.update(transaction);
            
        } catch (Exception e) {
            log.error("处理悬挂事务失败: xid={}", transaction.getXid(), e);
            // 发送告警,需要人工干预
            alertService.sendAlert("悬挂事务处理失败", transaction);
        }
    }
}

三、空回滚问题解决方案

3.1 Try状态检查法

java

复制

下载

复制代码
@Component
public class EmptyRollbackPreventer {
    
    @Autowired
    private TransactionLogService logService;
    
    /**
     * Cancel接口实现(防空回滚)
     */
    @Transactional
    public void cancelWithCheck(String xid, String businessId) {
        // 1. 查询Try执行记录
        TryRecord tryRecord = logService.getTryRecord(xid);
        
        if (tryRecord == null) {
            // 2. Try未执行,属于空回滚
            logEmptyRollback(xid, businessId);
            
            // 3. 插入空回滚记录,防止重复处理
            logService.saveEmptyRollbackRecord(xid, businessId);
            
            // 4. 直接返回成功,保证幂等性
            return;
        }
        
        // 5. 幂等检查:是否已取消过
        if (tryRecord.getStatus() == TryStatus.CANCELLED) {
            log.info("Cancel已执行过,幂等返回: xid={}", xid);
            return;
        }
        
        // 6. 验证状态:必须TRY_SUCCESS才能Cancel
        if (tryRecord.getStatus() != TryStatus.TRY_SUCCESS) {
            throw new IllegalStateException(
                String.format("事务状态[%s]不允许Cancel", tryRecord.getStatus())
            );
        }
        
        // 7. 执行实际的Cancel逻辑
        executeCancel(businessId);
        
        // 8. 更新状态
        tryRecord.setStatus(TryStatus.CANCELLED);
        logService.updateTryRecord(tryRecord);
    }
    
    /**
     * Try接口:必须记录状态
     */
    @Transactional
    public void tryWithRecord(String xid, String businessId) {
        // 1. 插入Try记录(初始状态)
        TryRecord record = new TryRecord();
        record.setXid(xid);
        record.setBusinessId(businessId);
        record.setStatus(TryStatus.TRYING);
        record.setCreateTime(new Date());
        logService.saveTryRecord(record);
        
        try {
            // 2. 执行业务Try操作
            businessService.doTry(businessId);
            
            // 3. 更新为成功状态
            record.setStatus(TryStatus.TRY_SUCCESS);
            logService.updateTryRecord(record);
            
        } catch (Exception e) {
            // 4. 更新为失败状态
            record.setStatus(TryStatus.TRY_FAILED);
            record.setErrorMsg(e.getMessage());
            logService.updateTryRecord(record);
            
            throw e;
        }
    }
}

3.2 预写日志方案

java

复制

下载

复制代码
public class PreWriteLogSolution {
    
    private final TransactionLogDAO logDAO;
    
    /**
     * 两阶段防空回滚方案
     */
    public void processTransaction(String xid, String businessId) {
        // 第一阶段:预写日志
        preWriteTransactionLog(xid, businessId);
        
        try {
            // 第二阶段:执行业务
            executeBusiness(xid, businessId);
            
            // 第三阶段:确认日志
            confirmTransactionLog(xid);
            
        } catch (Exception e) {
            // 第四阶段:回滚检查
            rollbackWithCheck(xid, businessId, e);
        }
    }
    
    private void preWriteTransactionLog(String xid, String businessId) {
        TransactionLog log = new TransactionLog();
        log.setXid(xid);
        log.setBusinessId(businessId);
        log.setStatus(TransactionLogStatus.PREPARED);
        log.setCreateTime(System.currentTimeMillis());
        log.setExpireTime(System.currentTimeMillis() + 300000); // 5分钟过期
        
        // 插入预写日志
        if (logDAO.insert(log) <= 0) {
            throw new TransactionException("预写事务日志失败");
        }
    }
    
    private void rollbackWithCheck(String xid, String businessId, Exception cause) {
        // 1. 检查预写日志是否存在
        TransactionLog preLog = logDAO.selectByXid(xid);
        
        if (preLog == null || preLog.getStatus() != TransactionLogStatus.PREPARED) {
            // 没有预写日志,属于空回滚或重复回滚
            logEmptyRollback(xid, businessId, cause);
            
            // 插入空回滚记录
            logDAO.insertEmptyRollback(xid, businessId, cause.getMessage());
            return;
        }
        
        // 2. 执行实际的回滚逻辑
        try {
            businessService.cancel(businessId);
            
            // 3. 更新日志状态
            preLog.setStatus(TransactionLogStatus.ROLLBACKED);
            preLog.setUpdateTime(System.currentTimeMillis());
            logDAO.update(preLog);
            
        } catch (Exception e) {
            log.error("回滚业务失败", e);
            // 标记为回滚失败,需要补偿
            preLog.setStatus(TransactionLogStatus.ROLLBACK_FAILED);
            logDAO.update(preLog);
            
            throw e;
        }
    }
}

四、综合解决方案

4.1 完整TCC事务管理器

java

复制

下载

复制代码
@Slf4j
@Component
public class EnhancedTccTransactionManager {
    
    @Autowired
    private TransactionStateService stateService;
    
    @Autowired
    private BusinessExecutor businessExecutor;
    
    @Autowired
    private HangPreventionLock hangLock;
    
    /**
     * 增强版Try阶段
     */
    public void enhancedTry(String xid, String businessId, TryAction tryAction) {
        // 1. 防悬挂检查
        checkHangingRisk(businessId);
        
        // 2. 获取分布式锁
        boolean locked = hangLock.acquireHangLock(xid, businessId);
        if (!locked) {
            throw new TransactionException("获取事务锁失败");
        }
        
        try {
            // 3. 记录开始状态
            stateService.recordTryStart(xid, businessId);
            
            // 4. 执行业务Try
            tryAction.execute();
            
            // 5. 记录Try成功
            stateService.recordTrySuccess(xid);
            
        } catch (Exception e) {
            // 6. 记录Try失败
            stateService.recordTryFailure(xid, e.getMessage());
            throw e;
            
        } finally {
            // 7. 释放锁(设置较短过期时间,Confirm/Cancel会重新获取)
            hangLock.releaseLockWithDelay(xid, businessId, 10);
        }
    }
    
    /**
     * 增强版Cancel阶段
     */
    public void enhancedCancel(String xid, String businessId, CancelAction cancelAction) {
        // 1. 获取Cancel锁(防止并发Cancel)
        boolean cancelLocked = hangLock.acquireCancelLock(xid);
        
        try {
            // 2. 检查是否为空回滚
            TransactionState state = stateService.getState(xid);
            
            if (state == null || state.getStatus() == TransactionStatus.INIT) {
                // 空回滚处理
                handleEmptyRollback(xid, businessId);
                return;
            }
            
            // 3. 幂等检查
            if (state.getStatus() == TransactionStatus.CANCELLED 
                || state.getStatus() == TransactionStatus.CANCELLED_EMPTY) {
                log.info("Cancel已执行,幂等返回: xid={}", xid);
                return;
            }
            
            // 4. 状态验证
            if (state.getStatus() != TransactionStatus.TRY_SUCCESS) {
                throw new IllegalStateException(
                    String.format("事务状态[%s]不允许Cancel", state.getStatus())
                );
            }
            
            // 5. 执行业务Cancel
            cancelAction.execute();
            
            // 6. 更新状态
            stateService.markAsCancelled(xid);
            
        } finally {
            if (cancelLocked) {
                hangLock.releaseCancelLock(xid);
            }
        }
    }
    
    /**
     * 防悬挂风险检查
     */
    private void checkHangingRisk(String businessId) {
        List<TransactionState> unfinished = 
            stateService.getUnfinishedByBusinessId(businessId, 10); // 最近10分钟
        
        if (!unfinished.isEmpty()) {
            throw new TransactionHangingRiskException(
                String.format("业务[%s]存在未完成事务", businessId)
            );
        }
    }
    
    /**
     * 空回滚处理
     */
    private void handleEmptyRollback(String xid, String businessId) {
        log.warn("检测到空回滚: xid={}, businessId={}", xid, businessId);
        
        // 1. 记录空回滚日志
        stateService.recordEmptyRollback(xid, businessId);
        
        // 2. 监控上报
        monitorService.reportEmptyRollback(xid, businessId);
        
        // 3. 返回成功(保证幂等性)
    }
}

4.2 状态机设计

java

复制

下载

复制代码
public enum TransactionStatus {
    
    // 初始状态
    INIT("初始状态"),
    
    // Try阶段状态
    TRYING("Try执行中"),
    TRY_SUCCESS("Try执行成功"),
    TRY_FAILED("Try执行失败"),
    
    // Confirm阶段状态
    CONFIRMING("Confirm执行中"),
    CONFIRMED("Confirm执行成功"),
    CONFIRM_FAILED("Confirm执行失败"),
    
    // Cancel阶段状态
    CANCELLING("Cancel执行中"),
    CANCELLED("Cancel执行成功"),
    CANCELLED_EMPTY("空回滚"),
    CANCEL_FAILED("Cancel执行失败"),
    
    // 悬挂处理状态
    HANGING_DETECTED("检测到悬挂"),
    COMPENSATING("补偿中"),
    COMPENSATED("已补偿"),
    COMPENSATE_FAILED("补偿失败");
    
    private final String description;
    
    TransactionStatus(String description) {
        this.description = description;
    }
    
    // 状态转移检查
    public boolean canTransferTo(TransactionStatus target) {
        // 定义合法的状态转移规则
        Map<TransactionStatus, Set<TransactionStatus>> transferRules = new HashMap<>();
        
        transferRules.put(INIT, Set.of(TRYING));
        transferRules.put(TRYING, Set.of(TRY_SUCCESS, TRY_FAILED, HANGING_DETECTED));
        transferRules.put(TRY_SUCCESS, Set.of(CONFIRMING, CANCELLING, HANGING_DETECTED));
        transferRules.put(TRY_FAILED, Set.of(CANCELLING));
        // ... 其他转移规则
        
        Set<TransactionStatus> allowedTargets = transferRules.get(this);
        return allowedTargets != null && allowedTargets.contains(target);
    }
}

五、数据库表设计

5.1 事务状态表

sql

复制

下载

复制代码
CREATE TABLE tcc_transaction_state (
    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
    xid VARCHAR(64) NOT NULL COMMENT '全局事务ID',
    business_id VARCHAR(64) NOT NULL COMMENT '业务ID',
    business_type VARCHAR(32) NOT NULL COMMENT '业务类型',
    
    -- 状态信息
    status VARCHAR(32) NOT NULL COMMENT '事务状态',
    retry_count INT DEFAULT 0 COMMENT '重试次数',
    max_retry INT DEFAULT 3 COMMENT '最大重试次数',
    error_msg TEXT COMMENT '错误信息',
    
    -- 时间信息
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    expire_time DATETIME NOT NULL COMMENT '过期时间',
    finish_time DATETIME COMMENT '完成时间',
    
    -- 控制字段
    version INT DEFAULT 1 COMMENT '版本号(乐观锁)',
    is_deleted TINYINT DEFAULT 0 COMMENT '是否删除',
    
    -- 索引
    UNIQUE KEY uk_xid (xid),
    KEY idx_business_id (business_id),
    KEY idx_status_expire (status, expire_time),
    KEY idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='TCC事务状态表';

5.2 防悬挂锁表

sql

复制

下载

复制代码
CREATE TABLE tcc_hang_lock (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    lock_key VARCHAR(128) NOT NULL COMMENT '锁Key',
    lock_value VARCHAR(64) NOT NULL COMMENT '锁值',
    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='防悬挂锁表';

5.3 空回滚记录表

sql

复制

下载

复制代码
CREATE TABLE tcc_empty_rollback_log (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    xid VARCHAR(64) NOT NULL COMMENT '事务ID',
    business_id VARCHAR(64) NOT NULL COMMENT '业务ID',
    reason VARCHAR(256) COMMENT '空回滚原因',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    
    UNIQUE KEY uk_xid_business (xid, business_id),
    KEY idx_create_time (create_time)
) COMMENT='空回滚记录表';

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

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

六、监控与告警

6.1 监控指标

java

复制

下载

复制代码
@Component
public class TccMonitor {
    
    // 悬挂事务监控
    @Meter("tcc.hanging.count")
    private Meter hangingCount;
    
    // 空回滚监控
    @Meter("tcc.empty_rollback.count")
    private Meter emptyRollbackCount;
    
    // 事务成功率监控
    @Timer("tcc.transaction.duration")
    private Timer transactionDuration;
    
    // 记录悬挂事务
    public void recordHanging(String xid, String businessId) {
        hangingCount.increment();
        
        // 记录详细日志
        log.warn("检测到悬挂事务: xid={}, businessId={}", xid, businessId);
        
        // 发送监控事件
        eventPublisher.publishEvent(new HangingTransactionEvent(xid, businessId));
    }
    
    // 记录空回滚
    public void recordEmptyRollback(String xid, String businessId) {
        emptyRollbackCount.increment();
        
        log.info("记录空回滚: xid={}, businessId={}", xid, businessId);
        
        // 上报到监控系统
        monitorClient.reportEmptyRollback(xid, businessId);
    }
}

6.2 告警规则配置

yaml

复制

下载

复制代码
# alert-rules.yml
tcc_monitoring:
  rules:
    # 悬挂事务告警
    - alert: HangingTransactionDetected
      expr: |
        increase(tcc_hanging_count_total[5m]) > 10
      for: 2m
      labels:
        severity: warning
      annotations:
        summary: "检测到悬挂事务"
        description: "5分钟内新增悬挂事务 {{ $value }} 个"
    
    # 空回滚率过高告警
    - alert: HighEmptyRollbackRate
      expr: |
        rate(tcc_empty_rollback_count_total[10m]) / 
        rate(tcc_transaction_total[10m]) > 0.01
      for: 5m
      labels:
        severity: warning
      annotations:
        summary: "空回滚率过高"
        description: "空回滚率达到 {{ $value | humanizePercentage }}"
    
    # 事务成功率下降告警
    - alert: TransactionSuccessRateDrop
      expr: |
        (rate(tcc_success_count_total[10m]) / 
         rate(tcc_transaction_total[10m])) < 0.95
      for: 3m
      labels:
        severity: critical
      annotations:
        summary: "事务成功率下降"
        description: "事务成功率下降至 {{ $value | humanizePercentage }}"

七、最佳实践总结

7.1 悬挂问题防护要点

text

复制

下载

复制代码
✅ Try前检查:检查同一业务是否存在未完成事务
✅ 资源锁:使用分布式锁控制并发
✅ 超时清理:定时任务清理超时事务
✅ 状态验证:Cancel前验证事务状态

7.2 空回滚问题防护要点

text

复制

下载

复制代码
✅ Try记录:必须记录Try执行状态
✅ Cancel检查:Cancel前检查Try是否执行
✅ 幂等设计:Cancel操作必须幂等
✅ 空回滚日志:记录空回滚用于分析

7.3 实施建议

java

复制

下载

复制代码
// 分阶段实施路线图
public class TccSolutionRoadmap {
    
    // Phase 1: 基础防护(1-2周)
    // - 实现Try状态记录
    // - Cancel幂等性保证
    // - 基本状态检查
    
    // Phase 2: 悬挂防护(2-3周)
    // - 引入防悬挂锁
    // - 实现超时清理
    // - 添加监控告警
    
    // Phase 3: 优化完善(3-4周)
    // - 空回滚自动处理
    // - 性能优化
    // - 可视化监控
    
    // Phase 4: 高级特性(4-6周)
    // - 机器学习预测
    // - 自适应调整
    // - 多级降级策略
}

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

相关推荐
短剑重铸之日14 小时前
《SpringBoot4.0初识》第五篇:实战代码
java·后端·spring·springboot4.0
heartbeat..14 小时前
Spring MVC 全面详解(Java 主流 Web 开发框架)
java·网络·spring·mvc·web
jump_jump14 小时前
SaaS 时代已死,SaaS 时代已来
前端·后端·架构
-西门吹雪14 小时前
c++线程之std::async浅析
java·jvm·c++
a努力。14 小时前
国家电网Java面试被问:最小生成树的Kruskal和Prim算法
java·后端·算法·postgresql·面试·linq
知行合一。。。14 小时前
Python--03--函数入门
android·数据库·python
竹君子14 小时前
AIDC知识库(3)英伟达Rubin 架构对未来AIDC方案的影响初探
人工智能
朝九晚五ฺ14 小时前
从零到实战:鲲鹏平台 HPC 技术栈与并行计算
java·开发语言
Geminit14 小时前
无人机培训,蚂蚁智飞在线训练,AI赋能新培训/学习模式
职场和发展