9.2 分布式事务解决方案
面试重要程度:⭐⭐⭐⭐⭐
常见提问方式: "如何保证分布式系统的数据一致性?"
技术深度: 分布式理论、事务模式、框架实现
预计阅读时间:35分钟
🎯 分布式事务基础
什么是分布式事务
分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。
典型场景:
@Service
public class OrderService {
// 场景:用户下单需要操作多个服务
public void createOrder(CreateOrderRequest request) {
// 1. 订单服务:创建订单
Order order = orderRepository.save(new Order(request));
// 2. 库存服务:扣减库存
inventoryService.deductStock(request.getProductId(), request.getQuantity());
// 3. 账户服务:扣减余额
accountService.deductBalance(request.getUserId(), request.getAmount());
// 4. 积分服务:增加积分
pointService.addPoints(request.getUserId(), request.getPoints());
// 问题:如何保证这4个操作要么全部成功,要么全部失败?
}
}
ACID特性的挑战
public class DistributedTransactionChallenges {
/**
* 原子性挑战:多服务调用的一致性
*/
public void atomicityChallenge() {
try {
serviceA.operation(); // 成功
serviceB.operation(); // 失败 - 如何回滚serviceA?
serviceC.operation(); // 未执行
} catch (Exception e) {
// 分布式环境下的回滚复杂性
}
}
/**
* 一致性挑战:状态同步问题
*/
public void consistencyChallenge() {
// 订单状态:已创建
// 库存状态:已扣减
// 支付状态:处理中
// 如何保证最终一致?
}
}
🔄 分布式事务解决方案
1. 两阶段提交(2PC)
原理:
- 准备阶段:协调者询问所有参与者是否可以提交
- 提交阶段:根据参与者响应决定提交或回滚
@Component
public class TwoPhaseCommitCoordinator {
private List<TransactionParticipant> participants;
public boolean executeTransaction(DistributedTransaction transaction) {
String transactionId = UUID.randomUUID().toString();
try {
// 阶段1:准备阶段
boolean canCommit = preparePhase(transactionId, transaction);
if (canCommit) {
// 阶段2:提交阶段
return commitPhase(transactionId);
} else {
rollbackPhase(transactionId);
return false;
}
} catch (Exception e) {
rollbackPhase(transactionId);
return false;
}
}
private boolean preparePhase(String transactionId, DistributedTransaction transaction) {
for (TransactionParticipant participant : participants) {
try {
PrepareResult result = participant.prepare(transactionId, transaction);
if (result != PrepareResult.YES) {
return false;
}
} catch (Exception e) {
return false;
}
}
return true;
}
private boolean commitPhase(String transactionId) {
boolean allCommitted = true;
for (TransactionParticipant participant : participants) {
try {
participant.commit(transactionId);
} catch (Exception e) {
allCommitted = false;
// 2PC问题:无法回滚已提交的参与者
}
}
return allCommitted;
}
}
public interface TransactionParticipant {
PrepareResult prepare(String transactionId, DistributedTransaction transaction);
void commit(String transactionId);
void rollback(String transactionId);
}
2. TCC模式(Try-Confirm-Cancel)
原理:
- Try:尝试执行,预留资源
- Confirm:确认执行,使用预留资源
- Cancel:取消执行,释放预留资源
@Component
public class TccTransactionManager {
@Autowired
private List<TccParticipant> participants;
@Transactional
public boolean executeTransaction(TccTransaction transaction) {
String transactionId = UUID.randomUUID().toString();
List<TccParticipant> succeededParticipants = new ArrayList<>();
// Try阶段
for (TccParticipant participant : participants) {
try {
boolean tryResult = participant.tryExecute(transactionId, transaction);
if (tryResult) {
succeededParticipants.add(participant);
} else {
cancelParticipants(transactionId, succeededParticipants);
return false;
}
} catch (Exception e) {
cancelParticipants(transactionId, succeededParticipants);
return false;
}
}
// Confirm阶段
try {
confirmParticipants(transactionId, succeededParticipants);
return true;
} catch (Exception e) {
// Confirm失败需要重试,不能Cancel
scheduleConfirmRetry(transactionId, succeededParticipants);
return false;
}
}
}
public interface TccParticipant {
boolean tryExecute(String transactionId, TccTransaction transaction);
void confirmExecute(String transactionId);
void cancelExecute(String transactionId);
}
@Component
public class AccountTccParticipant implements TccParticipant {
@Override
public boolean tryExecute(String transactionId, TccTransaction transaction) {
PaymentRequest request = (PaymentRequest) transaction.getData();
// 检查余额
Account account = accountRepository.findById(request.getUserId());
if (account.getBalance().compareTo(request.getAmount()) < 0) {
return false;
}
// 冻结金额
FrozenBalance frozenBalance = new FrozenBalance();
frozenBalance.setTransactionId(transactionId);
frozenBalance.setUserId(request.getUserId());
frozenBalance.setAmount(request.getAmount());
frozenBalanceRepository.save(frozenBalance);
return true;
}
@Override
public void confirmExecute(String transactionId) {
FrozenBalance frozenBalance = frozenBalanceRepository.findByTransactionId(transactionId);
// 扣减账户余额
Account account = accountRepository.findById(frozenBalance.getUserId());
account.setBalance(account.getBalance().subtract(frozenBalance
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
Java面试圣经 文章被收录于专栏
Java面试圣经,带你练透java圣经

查看10道真题和解析