一、问题定义与核心概念
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事务中的悬挂和空回滚问题,保障分布式事务的可靠性和数据一致性。