分布式事務(wù)之2PC兩階段提交
1. 分布式事務(wù)概述
1.1 問題背景
在分布式系統(tǒng)中,業(yè)務(wù)操作可能跨越多個服務(wù)或數(shù)據(jù)庫(如訂單服務(wù)、庫存服務(wù)、支付服務(wù)),傳統(tǒng)單機(jī)事務(wù)(ACID)無法滿足跨網(wǎng)絡(luò)節(jié)點(diǎn)的數(shù)據(jù)一致性需求。
- 網(wǎng)絡(luò)不可靠:服務(wù)間調(diào)用可能失敗或超時。
- 數(shù)據(jù)一致性:不同節(jié)點(diǎn)間的狀態(tài)需最終一致。
- 性能與可用性:避免長時間鎖資源導(dǎo)致系統(tǒng)阻塞。
分布式事務(wù)的核心目標(biāo)是確保 跨服務(wù)/數(shù)據(jù)庫的操作要么全部成功,要么全部回滾。
2. 兩階段提交(2PC)
原理
- 階段一(Prepare):協(xié)調(diào)者詢問所有參與者是否可提交,參與者鎖定資源并返回“同意”或“拒絕”。
- 階段二(Commit/Rollback):若所有參與者同意,協(xié)調(diào)者發(fā)送提交命令;否則發(fā)送回滾命令。
以下是一個簡化的 Java 兩階段提交(2PC) 具體實(shí)現(xiàn)示例,包含協(xié)調(diào)者(Coordinator)和參與者(Participant)的核心邏輯。代碼通過模擬數(shù)據(jù)庫操作展示2PC的關(guān)鍵流程:
1. 參與者(Participant)實(shí)現(xiàn)
每個參與者代表一個獨(dú)立的數(shù)據(jù)庫或服務(wù),需支持準(zhǔn)備(Prepare)、提交(Commit)、回滾(Rollback)操作。
import java.util.concurrent.atomic.AtomicBoolean;
/**
* 參與者(如數(shù)據(jù)庫或服務(wù))
*/
public class Participant {
private String name; // 參與者名稱(如"DB1")
private AtomicBoolean prepared = new AtomicBoolean(false); // 準(zhǔn)備狀態(tài)
private AtomicBoolean committed = new AtomicBoolean(false); // 提交狀態(tài)
public Participant(String name) {
this.name = name;
}
/**
* 階段一:準(zhǔn)備操作(鎖定資源)
* @return true表示準(zhǔn)備成功,false表示失敗
*/
public boolean prepare() {
try {
// 模擬資源鎖定,實(shí)際可能為操作數(shù)據(jù)庫
System.out.println(name + ": Trying to prepare...");
Thread.sleep(100); // 模擬網(wǎng)絡(luò)延遲
boolean success = Math.random() > 0.2; // 80%概率成功
if (success) {
prepared.set(true);
System.out.println(name + ": Prepared successfully.");
return true;
} else {
System.out.println(name + ": Prepare failed.");
return false;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
/**
* 階段二:提交操作
*/
public void commit() {
if (prepared.get()) {
// 實(shí)際提交事務(wù)(如更新數(shù)據(jù)庫)
committed.set(true);
System.out.println(name + ": Committed.");
} else {
System.out.println(name + ": Cannot commit without preparation.");
}
}
/**
* 階段二:回滾操作
*/
public void rollback() {
if (prepared.get()) {
// 實(shí)際回滾事務(wù)(如恢復(fù)數(shù)據(jù))
prepared.set(false);
System.out.println(name + ": Rolled back.");
} else {
System.out.println(name + ": No need to rollback.");
}
}
// 檢查是否已提交
public boolean isCommitted() {
return committed.get();
}
}
2. 協(xié)調(diào)者(Coordinator)實(shí)現(xiàn)
協(xié)調(diào)者負(fù)責(zé)管理所有參與者,驅(qū)動兩階段提交流程。
import java.util.List;
/**
* 協(xié)調(diào)者(事務(wù)管理器)
*/
public class Coordinator {
private List<Participant> participants;
public Coordinator(List<Participant> participants) {
this.participants = participants;
}
/**
* 執(zhí)行兩階段提交事務(wù)
* @return true表示事務(wù)成功提交,false表示失敗
*/
public boolean executeTransaction() {
System.out.println("===== Phase 1: Prepare =====");
boolean allPrepared = participants.stream()
.allMatch(Participant::prepare);
System.out.println("===== Phase 2: Commit/Rollback =====");
if (allPrepared) {
participants.forEach(Participant::commit);
System.out.println("Transaction committed successfully.");
return true;
} else {
participants.forEach(Participant::rollback);
System.out.println("Transaction rolled back due to failures.");
return false;
}
}
}
3. 客戶端測試代碼
模擬包含兩個參與者的分布式事務(wù)場景。
import java.util.Arrays;
public class TwoPhaseCommitDemo {
public static void main(String[] args) {
// 創(chuàng)建兩個參與者(如數(shù)據(jù)庫DB1和DB2)
Participant db1 = new Participant("DB1");
Participant db2 = new Participant("DB2");
// 創(chuàng)建協(xié)調(diào)者并關(guān)聯(lián)參與者
Coordinator coordinator = new Coordinator(Arrays.asList(db1, db2));
// 執(zhí)行兩階段提交事務(wù)
boolean success = coordinator.executeTransaction();
// 輸出最終狀態(tài)
System.out.println("\nFinal Status:");
System.out.println("DB1 Committed: " + db1.isCommitted());
System.out.println("DB2 Committed: " + db2.isCommitted());
System.out.println("Transaction Result: " + (success ? "SUCCESS" : "FAILURE"));
}
}
4. 運(yùn)行結(jié)果示例
成功場景(所有參與者準(zhǔn)備成功)
===== Phase 1: Prepare =====
DB1: Trying to prepare...
DB1: Prepared successfully.
DB2: Trying to prepare...
DB2: Prepared successfully.
===== Phase 2: Commit/Rollback =====
DB1: Committed.
DB2: Committed.
Transaction committed successfully.
Final Status:
DB1 Committed: true
DB2 Committed: true
Transaction Result: SUCCESS
失敗場景(某一參與者準(zhǔn)備失敗)
===== Phase 1: Prepare =====
DB1: Trying to prepare...
DB1: Prepared successfully.
DB2: Trying to prepare...
DB2: Prepare failed.
===== Phase 2: Commit/Rollback =====
DB1: Rolled back.
DB2: No need to rollback.
Transaction rolled back due to failures.
Final Status:
DB1 Committed: false
DB2 Committed: false
Transaction Result: FAILURE
5. 關(guān)鍵點(diǎn)說明
-
階段一(Prepare)
- 協(xié)調(diào)者詢問所有參與者是否可以提交。
- 參與者鎖定資源并記錄操作日志。
- 任一參與者失敗則整個事務(wù)回滾。
-
階段二(Commit/Rollback)
- 若所有參與者準(zhǔn)備成功,協(xié)調(diào)者發(fā)送提交命令。
- 若任一參與者失敗,協(xié)調(diào)者發(fā)送回滾命令。
-
代碼簡化說明
- 實(shí)際應(yīng)用中需處理網(wǎng)絡(luò)超時、重試和持久化日志。
- 分布式場景下需使用RPC或HTTP替代本地方法調(diào)用。
- 生產(chǎn)環(huán)境建議使用成熟的XA協(xié)議實(shí)現(xiàn)(如Atomikos、Narayana)。
6. 2PC的局限性
- 同步阻塞:參與者在Prepare階段后需阻塞等待協(xié)調(diào)者指令。
- 單點(diǎn)故障:協(xié)調(diào)者宕機(jī)可能導(dǎo)致事務(wù)懸掛。
- 數(shù)據(jù)不一致:協(xié)調(diào)者與參與者在Commit階段同時宕機(jī)時,可能部分提交。
關(guān)注微信公眾號,查看更多技術(shù)文章。

浙公網(wǎng)安備 33010602011771號