两阶段提交(2PC)协议
约 1680 字大约 6 分钟
distributed2pc
2025-05-27
概述
两阶段提交(Two-Phase Commit, 2PC)是最经典的分布式事务协议之一,由Jim Gray在1978年提出。它通过一个协调者(Coordinator)来协调多个参与者(Participant)的事务提交,确保分布式事务的原子性——要么全部提交,要么全部回滚。
协议流程
正常提交流程
回滚流程
代码实现
public class TwoPhaseCommitCoordinator {
private final List<Participant> participants;
private final TransactionLog txLog;
public boolean executeTransaction(Transaction tx) {
String txId = tx.getId();
// ============ 阶段一:Prepare ============
txLog.write(txId, TxState.PREPARE_STARTED);
List<CompletableFuture<Boolean>> votes = new ArrayList<>();
for (Participant p : participants) {
votes.add(CompletableFuture.supplyAsync(() -> {
try {
return p.prepare(txId, tx);
} catch (Exception e) {
return false;
}
}).orTimeout(5, TimeUnit.SECONDS)
.exceptionally(ex -> false));
}
// 等待所有投票结果
boolean allYes = votes.stream()
.map(CompletableFuture::join)
.allMatch(v -> v);
// ============ 阶段二:Commit/Rollback ============
if (allYes) {
txLog.write(txId, TxState.COMMIT_DECIDED);
commitAll(txId);
txLog.write(txId, TxState.COMMITTED);
return true;
} else {
txLog.write(txId, TxState.ABORT_DECIDED);
rollbackAll(txId);
txLog.write(txId, TxState.ABORTED);
return false;
}
}
private void commitAll(String txId) {
for (Participant p : participants) {
retryUntilSuccess(() -> p.commit(txId));
}
}
private void rollbackAll(String txId) {
for (Participant p : participants) {
retryUntilSuccess(() -> p.rollback(txId));
}
}
// 阶段二的操作必须最终成功(无限重试)
private void retryUntilSuccess(Runnable action) {
while (true) {
try {
action.run();
return;
} catch (Exception e) {
Thread.sleep(1000); // backoff
}
}
}
}public class Participant {
private final Map<String, TransactionContext> activeTx = new ConcurrentHashMap<>();
public boolean prepare(String txId, Transaction tx) {
try {
// 获取锁
acquireLocks(tx);
// 执行本地事务(不提交)
TransactionContext ctx = executeLocally(tx);
// 写redo/undo日志
writeWAL(txId, ctx);
activeTx.put(txId, ctx);
return true; // Vote YES
} catch (Exception e) {
rollbackLocal(txId);
return false; // Vote NO
}
}
public void commit(String txId) {
TransactionContext ctx = activeTx.get(txId);
ctx.getConnection().commit();
releaseLocks(ctx);
activeTx.remove(txId);
}
public void rollback(String txId) {
TransactionContext ctx = activeTx.get(txId);
if (ctx != null) {
ctx.getConnection().rollback();
releaseLocks(ctx);
activeTx.remove(txId);
}
}
}故障场景分析
阻塞问题
2PC最大的缺陷是阻塞性(Blocking)。当协调者在阶段二之前宕机时,参与者无法自行决定提交或回滚,必须等待协调者恢复。
超时处理
三阶段提交(3PC)
3PC是对2PC的改进,在Prepare和Commit之间增加了一个PreCommit阶段,通过引入超时机制减少阻塞。
3PC的局限: 在网络分区的情况下仍可能出现数据不一致,因此在实际生产中很少使用。
XA事务
XA是X/Open组织制定的分布式事务处理规范,是2PC的工业标准实现。
// XA事务示例(Java JTA)
public class XATransactionExample {
public void transferMoney(DataSource ds1, DataSource ds2,
String from, String to, BigDecimal amount) throws Exception {
// 获取XA连接
XAConnection xaConn1 = ((XADataSource) ds1).getXAConnection();
XAConnection xaConn2 = ((XADataSource) ds2).getXAConnection();
XAResource xaRes1 = xaConn1.getXAResource();
XAResource xaRes2 = xaConn2.getXAResource();
Xid xid1 = createXid(1);
Xid xid2 = createXid(2);
try {
// 阶段一:Prepare
xaRes1.start(xid1, XAResource.TMNOFLAGS);
// 执行扣款SQL
Connection conn1 = xaConn1.getConnection();
conn1.prepareStatement(
"UPDATE account SET balance = balance - ? WHERE id = ?")
.execute();
xaRes1.end(xid1, XAResource.TMSUCCESS);
xaRes2.start(xid2, XAResource.TMNOFLAGS);
// 执行加款SQL
Connection conn2 = xaConn2.getConnection();
conn2.prepareStatement(
"UPDATE account SET balance = balance + ? WHERE id = ?")
.execute();
xaRes2.end(xid2, XAResource.TMSUCCESS);
// Prepare
int ret1 = xaRes1.prepare(xid1);
int ret2 = xaRes2.prepare(xid2);
// 阶段二:Commit
if (ret1 == XAResource.XA_OK && ret2 == XAResource.XA_OK) {
xaRes1.commit(xid1, false);
xaRes2.commit(xid2, false);
} else {
xaRes1.rollback(xid1);
xaRes2.rollback(xid2);
}
} catch (Exception e) {
xaRes1.rollback(xid1);
xaRes2.rollback(xid2);
throw e;
}
}
}2PC vs Saga vs TCC
| 特性 | 2PC | 3PC | Saga | TCC |
|---|---|---|---|---|
| 一致性 | 强一致 | 强一致 | 最终一致 | 最终一致 |
| 性能 | 低 | 低 | 高 | 中 |
| 阻塞 | 是 | 减少 | 否 | 否 |
| 实现复杂度 | 中 | 高 | 中 | 高 |
| 资源锁持有时间 | 长 | 中 | 无全局锁 | 短 |
| 适用场景 | 数据库跨库事务 | 理论方案 | 微服务编排 | 资金交易 |
总结
2PC是分布式事务的经典基础协议,它以简洁的两阶段交互实现了分布式原子提交。然而其阻塞问题、单点故障风险和性能瓶颈使其不适合高并发的互联网场景。在实际工程中,通常根据一致性要求的不同,选择Saga(最终一致性,适合微服务编排)或TCC(需要资源预留的金融场景),而将2PC/XA保留给对一致性要求极高的数据库跨库事务。
贡献者
更新日志
2026/3/14 13:09
查看所有更新日志
9f6c2-feat: organize wiki content and refresh site setup于