詳細講解一下分布式事務-兩階段提交的實現原理
一、為什么需要分布式事務
隨著微服務架構和分布式系統的普及,一個業務操作往往需要調用多個服務,修改多個數據源的數據。例如:
- 電商系統中的下單操作:需要扣減庫存、創建訂單、支付等多個操作
- 銀行轉賬操作:需要從一個賬戶扣款,另一個賬戶加款
這些操作需要作為一個整體要么全部成功,要么全部失敗,這就需要分布式事務來保證。
二、兩階段提交(2PC) 原理
兩階段提交(Two-Phase Commit,簡稱2PC)是分布式系統中實現分布式事務的經典算法。它將事務的提交過程分為兩個階段:
- 準備階段(Prepare Phase):協調者詢問所有參與者是否可以提交
- 提交/回滾階段(Commit/Rollback Phase):根據準備階段的結果決定提交或回滾
角色劃分:
- 協調者(Coordinator):事務的發起者,負責協調所有參與者
- 參與者(Participant/Cohort):事務的實際執行者,負責本地事務的執行
兩階段提交流程:
(1)準備階段
- 協調者向所有參與者發送prepare請求
- 參與者執行本地事務但不提交,記錄undo/redo日志
- 參與者向協調者反饋響應:
成功:返回"同意"
失敗:返回"中止"
(2)提交/回滾階段
情況1:所有參與者都返回"同意"
- 協調者向所有參與者發送commit請求
- 參與者完成本地事務提交
- 參與者向協調者發送ack響應
- 協調者收到所有ack后完成事務
情況2:任一參與者返回"中止"或超時
- 協調者向所有參與者發送rollback請求
- 參與者利用undo日志回滾本地事務
- 參與者向協調者發送ack響應
- 協調者收到所有ack后中斷事務
三、基本實現示例
首先定義協調者和參與者的接口:
public interface Coordinator {
void startTransaction(List<Participant> participants);
boolean prepare();
void commit();
void rollback();
}
public interface Participant {
boolean prepare();
void commit();
void rollback();
}參與都實現代碼:
public class DatabaseParticipant implements Participant {
private Connection connection;
private String transactionId;
public DatabaseParticipant(Connection connection, String transactionId) {
this.connection = connection;
this.transactionId = transactionId;
}
@Override
public boolean prepare() {
try {
// 設置不自動提交
connection.setAutoCommit(false);
// 執行SQL但不提交
// 這里應該有實際的業務SQL,如:
// PreparedStatement ps = connection.prepareStatement("UPDATE account SET balance = balance - 100 WHERE user_id = 1");
// ps.executeUpdate();
// 記錄redo/undo日志
logRedoUndo();
return true;
} catch (SQLException e) {
return false;
}
}
@Override
public void commit() {
try {
connection.commit();
cleanRedoUndo();
} catch (SQLException e) {
// 處理異常
}
}
@Override
public void rollback() {
try {
connection.rollback();
cleanRedoUndo();
} catch (SQLException e) {
// 處理異常
}
}
private void logRedoUndo() {
// 實現記錄redo/undo日志的邏輯
}
private void cleanRedoUndo() {
// 清理日志
}
}協調者實現代碼:
public class TwoPhaseCommitCoordinator implements Coordinator {
private List<Participant> participants;
private String transactionId;
public TwoPhaseCommitCoordinator(String transactionId) {
this.transactionId = transactionId;
}
@Override
public void startTransaction(List<Participant> participants) {
this.participants = participants;
}
@Override
public boolean prepare() {
for (Participant participant : participants) {
if (!participant.prepare()) {
return false;
}
}
return true;
}
@Override
public void commit() {
if (prepare()) {
for (Participant participant : participants) {
participant.commit();
}
} else {
rollback();
}
}
@Override
public void rollback() {
for (Participant participant : participants) {
try {
participant.rollback();
} catch (Exception e) {
// 記錄日志,繼續回滾其他參與者
}
}
}
}使用示例:
public class TwoPhaseCommitExample {
public static void main(String[] args) {
// 模擬兩個數據庫參與者
Connection conn1 = getConnection(); // 獲取第一個數據庫連接
Connection conn2 = getConnection(); // 獲取第二個數據庫連接
Participant participant1 = new DatabaseParticipant(conn1, "tx123");
Participant participant2 = new DatabaseParticipant(conn2, "tx123");
Coordinator coordinator = new TwoPhaseCommitCoordinator("tx123");
coordinator.startTransaction(Arrays.asList(participant1, participant2));
try {
coordinator.commit();
System.out.println("事務提交成功");
} catch (Exception e) {
coordinator.rollback();
System.out.println("事務回滾");
}
}
private static Connection getConnection() {
// 實際應用中應該從數據源獲取連接
return null;
}
}四、Seata AT模式
Seata(Simple Extensible Autonomous Transaction Architecture)是一款開源的分布式事務解決方案,提供了AT、TCC、SAGA和XA四種事務模式。其中,AT(Auto Transaction)模式是基于兩階段提交協議改進而來,通過數據源代理和全局鎖機制實現了對業務代碼幾乎零侵入的分布式事務支持。
包含組件:
Transaction Coordinator (TC,事務協調器)
- 獨立部署的服務,維護全局事務和分支事務的狀態
- 負責協調全局事務的提交或回滾
- 管理全局鎖的獲取與釋放
Transaction Manager (TM,事務管理器)
- 嵌入在應用中,負責定義全局事務邊界
- 通過@GlobalTransactional注解標記分布式事務方法
- 向TC發起全局事務的開始、提交或回滾指令
Resource Manager (RM,資源管理器)
- 管理分支事務上的資源
- 向TC注冊分支事務并報告狀態
- 驅動分支事務的提交或回滾
- 負責生成和操作undo log
AT模式的整體流程:
(1)業務執行與本地提交
- 解析SQL:攔截業務SQL,解析SQL類型(INSERT/UPDATE/DELETE)、表、條件等信息
- 查詢前鏡像:根據SQL條件查詢修改前的數據快照(before image)
- 執行業務SQL:執行用戶的實際業務SQL
- 查詢后鏡像:根據主鍵查詢修改后的數據快照(after image)
- 插入回滾日志:將前后鏡像和業務SQL信息組成undo log記錄,插入到undo_log表
- 注冊分支事務:向TC注冊分支事務并獲取全局鎖
- 提交本地事務:業務SQL和undo log在同一個本地事務中提交
- 上報執行結果:將本地事務執行結果上報給TC
(2)全局提交或回滾
全局提交:
- TC異步通知各分支事務提交
- RM異步刪除對應的undo log記錄
- 釋放全局鎖
全局回滾:
- TC通知各分支事務回滾
- RM根據undo log中的before image生成補償SQL并執行
- 校驗數據一致性(對比after image與當前數據)
- 刪除undo log記錄
- 釋放全局鎖
Seata的詳細信息,請查看官網:https://seata.apache.org/zh-cn/docs/dev/mode/at-mode
五、兩階段提交的問題
(1)協調者單點故障
如果在第二階段協調者宕機,部分參與者收到commit而部分沒收到,系統將處于不一致狀態。
解決方法:記錄事務日志,協調者恢復后能繼續處理。
(2)網絡分區
網絡分區可能導致部分參與者無法收到協調者的指令。
為了解決2PC的網絡阻塞問題,引入了3PC:
- CanCommit階段:詢問參與者是否可以提交
- PreCommit階段:預提交,執行事務但不提交
- DoCommit階段:實際提交
3PC通過引入超時機制減少了阻塞,但增加了復雜度。

































