要保證多個系統間的資料一緻性,常用的方案是兩階段送出(2PC,Two-Phase Commit)。它的基本原理是:
第一階段(Prepare phase):事務的發起方請求所有參與方準備送出事務。參與方接收到請求後,會将事務的操作記錄到本地日志中作為準備送出狀态。然後參與方決定是否能準備送出該事務,并将決定通知給發起方。
第二階段(Commit phase):發起方根據所有參與方的回報,決定是送出(commit)事務,還是取消(abort)事務。
- 如果所有參與方均表明可以準備送出(voted commit),發起方會發出送出請求。參與方接收到送出請求後,會正式将事務的操作送出,并承諾無論發生任何故障,這些操作都不會被撤銷。
- 如果任一參與方表明無法準備送出(voted abort),發起方會發出取消請求。參與方接收到取消請求後,會撤銷之前準備送出的操作,并将事務恢複到開始時的狀态。
整個兩階段送出的過程如下圖所示:
兩階段送出能確定所有參與方對事務的決定達成一緻,要麼都送出,要麼都取消。這樣可以保證不同系統之間的資料強一緻性。它的缺點是如果在第二階段送出時發生故障,很難确定事務的最終狀态,可能導緻資料不一緻。
這裡是基于REST API的實作示例:
java
// 1. /prepare API - 更新資料庫但不送出
@RequestMapping("/prepare")
public ResponseEntity<String> prepare(HttpServletRequest request) {
Transfer transfer = getTransferFromRequest(request);
transferService.prepareTransfer(transfer);
return ResponseEntity.ok("Transaction prepared");
}
// 2. /commit API - 送出事務
@RequestMapping("/commit")
public ResponseEntity<String> commit(HttpServletRequest request) {
Transfer transfer = getTransferFromRequest(request);
transferService.commitTransfer(transfer);
return ResponseEntity.ok("Transaction committed");
}
// 3. /rollback API - 復原事務
@RequestMapping("/rollback")
public ResponseEntity<String> rollback(HttpServletRequest request) {
Transfer transfer = getTransferFromRequest(request);
transferService.rollbackTransfer(transfer);
return ResponseEntity.ok("Transaction rolled back");
}
TransferService類的實作:
java
@Service
public class TransferService {
@Autowired
private TransferDao transferDao;
public void prepareTransfer(Transfer transfer) {
Connection conn = transferDao.getConnection();
conn.setAutoCommit(false); // 關閉自動送出
transferDao.debitAccount(conn, transfer.getFromAccountId(), transfer.getAmount());
transferDao.creditAccount(conn, transfer.getToAccountId(), transfer.getAmount());
}
public void commitTransfer(Transfer transfer) {
Connection conn = transferDao.getConnection();
conn.commit(); // 送出事務
}
public void rollbackTransfer(Transfer transfer) {
Connection conn = transferDao.getConnection();
conn.rollback(); // 復原事務
}
}
TransferDao類用于執行資料庫操作:
java
@Repository
public class TransferDao {
public Connection getConnection() throws SQLException {
// 擷取資料庫連接配接...
}
public void debitAccount(Connection conn, int accountId, int amount) throws SQLException {
// 更新賬号餘額,減去amount...
}
public void creditAccount(Connection conn, int accountId, int amount) throws SQLException {
// 更新賬号餘額,加上amount...
}
}
在這個例子中:
1. /prepare API會關閉自動送出,執行轉賬操作但不送出。
2. /commit API會送出事務,完成轉賬。
3. /rollback API會復原事務,撤銷轉賬。
通過這三個API調用,可以實作二階段送出,確定轉賬要麼全部成功要麼全部失敗。