分布式事务:2PC 与 3PC 深度解析
文章目录
一、分布式事务概述
❓ 为什么单机事务无法满足分布式场景?
传统单机事务的局限性:
// 单机事务示例 - 在单体应用中工作良好
@Transactional
public void transferMoney(Long fromAccount, Long toAccount, BigDecimal amount) {
// 1. 扣减源账户余额
accountService.debit(fromAccount, amount);
// 2. 增加目标账户余额
accountService.credit(toAccount, amount);
// 3. 记录交易流水
transactionService.recordTransaction(fromAccount, toAccount, amount);
}
分布式架构下的挑战:
分布式事务的复杂性:
- 网络分区:服务间网络中断导致通信失败
- 节点故障:某个参与节点宕机
- 数据不一致:部分节点成功,部分节点失败
- 性能瓶颈:跨服务协调带来的延迟
分布式事务的挑战分析
CAP 定理在分布式事务中的体现:
| 挑战维度 | 具体表现 | 影响程度 |
|---|---|---|
| 一致性(Consistency) | 多个节点的数据存在延迟或不一致,如主从数据不同步 | ⭐⭐⭐⭐⭐ |
| 可用性(Availability) | 某些节点宕机时系统整体不可用或部分请求失败 | ⭐⭐⭐⭐ |
| 分区容错性(Partition Tolerance) | 网络分区导致节点间无法通信,引发协调失败 | ⭐⭐⭐⭐⭐ |
| 性能(Performance) | 跨节点协调、网络交互带来高延迟 | ⭐⭐⭐ |
⚡ 二、两阶段提交(2PC)
协议流程详解
2PC 核心角色:
- 协调者:事务管理器,负责决策
- 参与者:资源管理器,执行具体操作
2PC 时序图:
2PC Java 实现示例
协调者实现:
@Component
public class TwoPhaseCoordinator {
private final List<Participant> participants = new ArrayList<>();
private final Map<String, Transaction> transactions = new ConcurrentHashMap<>();
/**
* 开始分布式事务
*/
public String beginTransaction() {
String transactionId = UUID.randomUUID().toString();
transactions.put(transactionId, new Transaction(transactionId));
return transactionId;
}
/**
* 注册事务参与者
*/
public void registerParticipant(String transactionId, Participant participant) {
Transaction transaction = transactions.get(transactionId);
if (transaction != null) {
transaction.addParticipant(participant);
}
}
/**
* 执行两阶段提交
*/
public boolean commitTransaction(String transactionId) {
Transaction transaction = transactions.get(transactionId);
if (transaction == null) {
return false;
}
// 第一阶段:准备阶段
List<Boolean> prepareResults = new ArrayList<>();
for (Participant participant : transaction.getParticipants()) {
try {
boolean prepared = participant.prepare();
prepareResults.add(prepared);
} catch (Exception e) {
prepareResults.add(false);
}
}
// 检查所有参与者是否准备成功
boolean allPrepared = prepareResults.stream().allMatch(Boolean::booleanValue);
// 第二阶段:提交/回滚
if (allPrepared) {
// 所有参与者准备成功,执行提交
for (Participant participant : transaction.getParticipants()) {
participant.commit();
}
transactions.remove(transactionId);
return true;
} else {
// 有参与者准备失败,执行回滚
for (Participant participant : transaction.getParticipants()) {
participant.rollback();
}
transactions.remove(transactionId);
return false;
}
}
}
参与者实现:
@Service
public class AccountParticipant implements Participant {
private final AccountService accountService;
private final Map<String, TransactionContext> contexts = new ConcurrentHashMap<>();
@Override
public boolean prepare() {
// 准备阶段:预扣款,锁定资源
TransactionContext context = getCurrentContext();
try {
// 检查账户状态,预扣款
boolean success = accountService.preDebit(context.getAccountId(), context.getAmount());
if (success) {
context.setStatus(TransactionStatus.PREPARED);
return true;
}
} catch (Exception e) {
context.setStatus(TransactionStatus.FAILED);
}
return false;
}
@Override
public void commit() {
// 提交阶段:实际扣款
TransactionContext context = getCurrentContext();
if (context.getStatus() == TransactionStatus.PREPARED) {
accountService.confirmDebit(context.getAccountId(), context.getAmount());
context.setStatus(TransactionStatus.COMMITTED);
}
}
@Override
public void rollback() {
// 回滚阶段:释放锁定资源
TransactionContext context = getCurrentContext();
if (context.getStatus() == TransactionStatus.PREPARED) {
accountService.cancelDebit(context.getAccountId(), context.getAmount());
context.setStatus(TransactionStatus.ROLLBACKED);
}
}
}
⚠️ 2PC 的优缺点分析
优点 ✅:
- 强一致性:保证所有节点数据一致
- 简单易懂:协议逻辑清晰,易于实现
- 广泛支持:多数数据库和中间件都支持
缺点 ❌:
- 同步阻塞:参与者需要等待协调者决策
- 单点故障:协调者宕机导致事务阻塞
- 数据不一致风险:网络分区时可能产生不一致
2PC 故障场景分析:
三、三阶段提交(3PC)
协议流程优化
3PC 改进点:
- 引入超时机制:解决 2PC 的同步阻塞问题
- 增加预提交阶段:减少数据不一致风险
3PC 时序图:
3PC Java 实现示例
协调者实现:
@Component
public class ThreePhaseCoordinator {
private final ScheduledExecutorService timeoutExecutor =
Executors.newScheduledThreadPool(10);
/**
* 三阶段提交执行
*/
public boolean commitTransaction(String transactionId) {
Transaction transaction = transactions.get(transactionId);
// 第一阶段:CanCommit
if (!canCommitPhase(transaction)) {
abortTransaction(transaction);
return false;
}
// 第二阶段:PreCommit
if (!preCommitPhase(transaction)) {
abortTransaction(transaction);
return false;
}
// 第三阶段:DoCommit
return doCommitPhase(transaction);
}
/**
* CanCommit 阶段:检查参与者是否可提交
*/
private boolean canCommitPhase(Transaction transaction) {
List<CompletableFuture<Boolean>> futures = new ArrayList<>();
for (Participant participant : transaction.getParticipants()) {
CompletableFuture<Boolean> future = CompletableFuture.supplyAsync(() -> {
try {
return participant.canCommit();
} catch (Exception e) {
return false;
}
});
futures.add(future);
}
// 设置超时
CompletableFuture<Void> allFutures = CompletableFuture.allOf(
futures.toArray(new CompletableFuture[0])
).orTimeout(30, TimeUnit.SECONDS);
try {
allFutures.get();
return futures.stream().allMatch(f -> {
try {
return f.get();
} catch (Exception e) {
return false;
}
});
} catch (TimeoutException e) {
log.warn("CanCommit阶段超时");
return false;
} catch (Exception e) {
return false;
}
}
/**
* PreCommit 阶段:预提交,锁定资源
*/
private boolean preCommitPhase(Transaction transaction) {
// 类似CanCommit阶段,但执行预提交操作
for (Participant participant : transaction.getParticipants()) {
if (!participant.preCommit()) {
return false;
}
}
return true;
}
/**
* DoCommit 阶段:最终提交
*/
private boolean doCommitPhase(Transaction transaction) {
for (Participant participant : transaction.getParticipants()) {
participant.doCommit();
}
transactions.remove(transaction.getId());
return true;
}
}
参与者超时处理:
@Service
public class TimeoutAwareParticipant implements Participant {
private final Map<String, Phase> currentPhases = new ConcurrentHashMap<>();
@Override
public boolean canCommit() {
String transactionId = getCurrentTransactionId();
currentPhases.put(transactionId, Phase.CAN_COMMIT);
// 设置超时监控
scheduleTimeoutCheck(transactionId, Phase.CAN_COMMIT);
// 检查业务规则,判断是否可以提交
return checkBusinessRules();
}
@Override
public boolean preCommit() {
String transactionId = getCurrentTransactionId();
currentPhases.put(transactionId, Phase.PRE_COMMIT);
scheduleTimeoutCheck(transactionId, Phase.PRE_COMMIT);
// 预提交:锁定资源但不提交
return prepareResources();
}
/**
* 超时处理:自动中止事务
*/
private void scheduleTimeoutCheck(String transactionId, Phase phase) {
timeoutExecutor.schedule(() -> {
if (currentPhases.get(transactionId) == phase) {
log.warn("事务{}在{}阶段超时,自动中止", transactionId, phase);
rollback(); // 自动回滚
}
}, 30, TimeUnit.SECONDS);
}
}
⚖️ 3PC 与 2PC 的对比分析
协议对比矩阵:
| 特性维度 | 2PC(Two Phase Commit) | 3PC(Three Phase Commit) | 改进效果 |
|---|---|---|---|
| 同步阻塞 | 严重阻塞,参与者需等待协调者通知 | 部分解决,引入超时机制 | ⭐⭐⭐ |
| 单点故障 | 协调者宕机会导致系统阻塞 | 通过超时 + 预提交阶段可自动恢复 | ⭐⭐⭐⭐ |
| 数据一致性 | 网络分区或协调者异常可能导致不一致 | 预提交阶段降低不一致风险 | ⭐⭐⭐ |
| 性能开销 | 较低(两次网络交互) | 较高(多一次交互阶段) | ⭐⭐ |
| 实现复杂度 | 相对简单 | 较为复杂(状态管理更多) | ⭐ |
故障恢复能力对比:
⚖️ 四、总结与应用场景
协议选择指南
技术选型决策树:
真实场景案例分析
金融转账场景:
// 使用 2PC 的银行转账服务
@Service
public class BankTransferService {
@Autowired
private TwoPhaseCoordinator coordinator;
public boolean transfer(TransferRequest request) {
String transactionId = coordinator.beginTransaction();
try {
// 注册参与者
coordinator.registerParticipant(transactionId, new AccountParticipant(
request.getFromAccount(), request.getAmount(), OperationType.DEBIT));
coordinator.registerParticipant(transactionId, new AccountParticipant(
request.getToAccount(), request.getAmount(), OperationType.CREDIT));
coordinator.registerParticipant(transactionId, new AuditParticipant(request));
// 执行分布式事务
return coordinator.commitTransaction(transactionId);
} catch (Exception e) {
coordinator.rollbackTransaction(transactionId);
throw new BusinessException("转账失败", e);
}
}
}
电商下单场景:
// 使用 3PC 的电商订单服务
@Service
public class OrderService {
public boolean createOrder(OrderRequest request) {
String transactionId = threePhaseCoordinator.beginTransaction();
// 第一阶段:检查库存、账户等
if (!threePhaseCoordinator.canCommit(transactionId)) {
return false;
}
// 第二阶段:预扣库存、预冻结金额
if (!threePhaseCoordinator.preCommit(transactionId)) {
return false;
}
// 第三阶段:实际扣减
return threePhaseCoordinator.doCommit(transactionId);
}
}
分布式事务的发展趋势
新一代分布式事务方案:
| 方案 | 核心思想 | 适用场景 | 与传统协议对比 |
|---|---|---|---|
| Saga 模式 | 将长事务拆分为一系列可补偿的本地事务,通过补偿动作实现最终一致性 | 微服务架构中的跨服务事务 | 避免同步阻塞,提升系统可用性 |
| TCC 模式 | Try-Confirm-Cancel 三阶段操作,显式控制资源预留与确认 | 高并发、高性能业务场景(如支付、下单) | 性能更好,但实现复杂 |
| 消息事务 | 通过消息中间件保证事务消息与业务操作的最终一致性 | 异步解耦的业务场景(如下单发券) | 松耦合、适合异步一致性 |
2PC/3PC 适合对一致性要求极高的金融级场景,但需要考虑其性能开销和复杂度。在互联网业务中,可以优先考虑基于消息的最终一致性方案。
浙公网安备 33010602011771号