1. 程式人生 > >結合現有分散式系統的資料一致性思考

結合現有分散式系統的資料一致性思考

背景

我們專案本身分成了多套系統,但資料上有要求一致性的地方(比如訂單狀態,通俗點講就是系統A更新了訂單狀態為狀態一,那麼系統B也需要把相同訂單的訂單狀態更新成狀態一,這樣可以讓我們不管是讀系統A還是系統B的同一個訂單的狀態,都可以讀出相同的狀態資料)。

最原始的虛擬碼大概為:

sysA.db.transaction.begin();
var success = sysA.db.transaction.updateOrderStatus();
if(!success){
    sysA.db.transaction.rollback();
    return;
}

success = httpclient.post(sysB.url);
if(!success){
    sysA.db.transaction.rollback();
    return;
}

sysA.db.transaction.commit();

看了這個程式碼後,大家應該知道這裡有個問題是呼叫系統B介面,可能出現網路或超時等問題,所以你是不知道系統B是執行成功了還是失敗,如果他是執行成功了的,那麼系統A回滾了就出問題了,因為兩邊資料就不一致了。

那麼基於CAP理論的實現版BASE,我們採用資料最終一致方案。關於CAP跟BASE,大家可以參考這裡

然後我這裡參考了個別文章跟同事想法,思考了其中兩個解決方案

簡單解決方案一

非同步任務處理,由原來同步呼叫B系統變為非同步呼叫。

  1. 系統A先寫資料庫更新訂單狀態,然後在本地同時新建一個任務(任務初始狀態為待執行),當呼叫B系統介面完成之後該任務改為已經執行,修改訂單狀態跟建立任務需要在一個數據庫事務下。

  2. 後臺定時指令碼來執行待執行狀態的任務。

  3. 如果非同步呼叫系統B介面返回失敗,則需要對之前訂單狀態更新進行回退。

  4. 如果非同步呼叫系統B介面遇到網路問題或者超時,則考慮重試機制,注意重試機制要避免重複提交,可採取在系統A重試前確認和在系統B保證介面的冪等。

想了一下,這個方案有個問題就是狀態可能發生了多次改變,如果先後順序出現問題,那麼將造成訂單狀態更新錯誤,所以就得有個佇列來儲存訂單狀態的多次先後順序更新才行,於是有了方案二

簡單解決方案二

引入訊息佇列,相當於對方案一的升級版,新建立任務變成新建立訊息

  1. 系統A為訊息生產者,系統B為訊息消費者。

  2. 生產者系統A接收到使用者請求,先寫資料庫更新訂單狀態,然後寫一條更新訂單狀態的訊息到訊息佇列,並且要新建一張訊息狀態表用於記錄訊息的執行狀態(初始狀態為待執行),以上三個操作要在同一個本地事務中進行,這個是容易忽略的地方。其實也可以在業務訂單表增加一個欄位用來表示訊息執行狀態。當然對於這個訂單的後續業務操作,只有在這個訊息執行成功後才能繼續,也就是有一個因果先後關係在裡面才行。

  3. 消費者系統B取出一條訊息,進行相同訂單的狀態更新,處理完成後需要告訴系統A訊息執行結果,是成功還是失敗。這裡簡單說下失敗的情況,第一次失敗的話,系統B就可以進入重試邏輯,重試多次如果還是失敗才需要告訴系統A我這邊最終執行失敗了,你可能要採取點措施才行,比如回滾呀,還是什麼的。

總結

  1. 分散式系統以基本可用跟快速高效的最終一致為目標
  2. 遠端RPC呼叫中的網路跟硬體等問題是造成一致性的主因,非同步解耦加訊息佇列等方式可作為分散式系統滿足最終一致性的一個比較好的方案。

參考:

  1. https://www.cnblogs.com/kerwing/p/9098893.html 主要參考了此文,然後做了一些自己的理解跟修改
  2. https://www.cnblogs.com/frankltf/p/10374662.html
  3. https://www.cnblogs.com/hzmark/p/consistency_model.html