天天看點

如何保證多個系統間的資料一緻性?

要保證多個系統間的資料一緻性,常用的方案是兩階段送出(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調用,可以實作二階段送出,確定轉賬要麼全部成功要麼全部失敗。

繼續閱讀