1. 程式人生 > >分散式事務一些思考和總結

分散式事務一些思考和總結

最近在做交易訂單的事情,隨著系統越來越複雜,交易量越來越大,出現不一致的情況也頻發。不禁引人思考,這裡做一些總結記錄。分散式事務有一些理論,最常聽的是CAP,ACID等。這裡就不做介紹了,大家自己去查資料看看。分散式事務也經常提到2PC,3PC,因為不太適用網際網路場景,這裡也不做介紹。

下面我們來看看一個電商典型的場景,下單後傳送訊息給其他業務系統,很多同學喜歡這樣做

    @Transactional
    public void submitOrder(){
        insertDb();
        sendMsg();
    }

用Spring註解來實現事務,首先先插資料到DB,然後發訊息。仔細分析遇到的各種情況:

  • 1.插入資料和傳送訊息都成功,這是最常見的情況,當然是皆大歡喜。
  • 2.插入資料失敗,丟擲異常,訊息當然也不會發,這也沒什麼問題。
  • 3.插入資料成功,只是在提交commit的時候由於網路原因連線超時,丟擲異常,回滾無效,訊息仍舊傳送成功。算是有驚無險。
  • 4.事務在commit的時候失敗回滾,訊息已經發送,造成訊息多發。
  • 5.訊息傳送失敗,事務回滾,沒什麼問題。
  • 6.訊息傳送成功,網路原因連線超時,事務回滾,造成訊息多發。
  • 7.事務提交的時候,機器宕機,也有可能造成訊息多發。

分析以上幾種情況,除了訊息多發,也沒什麼大問題,訊息訂閱者可以反查一下訂單,如果查不到可以丟棄該訊息,但是會對業務方造成一定的困惑。

但是如果把sendMsg替換一個服務,情況就不同了。假設取消訂單需要先更新訂單狀態,然後回沖庫存。

   @Transactional
    public void cancelOrder(){
        updateDB();
        inventoryService.rollback();
    }

從之前的案例分析來看,這樣會造成訂單狀態更新失敗的情況下,呼叫庫存介面成功,顯然出現了不一致情況。無論怎麼樣調整操作順序,都會出現類似問題。那麼怎麼樣儘量規避這種問題發生?

本地事務表

核心思路是將分散式事務轉換為本地事務。新建message表,儲存業務ID和自身狀態。更新訂單操作和插入message在同一個庫中做事務,能保證一致性。

   @Transactional
    public void cancelOrder(){
        updateDB();
        insertMessage();
    }

message表

id status
orderId 未處理

如果上述事務成功後了,接下來發送訊息給庫存服務或者直接呼叫庫存服務,成功後更新訊息表狀態。這裡有個問題,如果下來操作失敗怎麼辦?這時候message表就派上用場了,需要另外一個務定時掃描message表,將沒有處理髮送的訊息再次傳送出去。庫存服務還需要保持冪等性。

外接事務表

有些服務本身不開啟本地事務,而是呼叫其他RPC服務,例如下面介面呼叫了serviceA,serviceB,serviceC介面。怎麼保證這3個介面要麼都呼叫成功?其實沒有確切答案。只能借鑑前面的思路,設計一個外部事務表。每次呼叫前預先生成全域性序列號id,記錄這次事務需要呼叫哪些介面。每次呼叫成功後更新狀態為成功。當然還是存在多次呼叫的情況,被呼叫方需要做好冪等防重措施。這裡還有很多需要考慮的地方,一個是效能優化,還有就是完善失敗後補償機制,

全域性序列號 服務 呼叫服務 順序 狀態
123 doSomething serviceA 1 OK
123 doSomething serviceB 2 OK
123 doSomething serviceC 3 OK
public void doSomething(){
    serviceA();
    serviceB();
    serviceC();
}

事務訊息

Rocketmq有類似實現。
首先先發送prepare訊息給Rocketmq,這時訊息並不傳送。等事務提交後,傳送確認訊息給Rocketmq,訊息再發送出去。如果Rocketmq沒有收到確認訊息,會詢問業務方是否要傳送。
這裡寫圖片描述

資料庫日誌

通過資料庫日誌,比如監聽mysql的binlog日誌,通過canal等中介軟體把資料變更日誌推送出去,然後再呼叫後續服務,也是可行方案。

其他方案

支付寶TCC,這裡轉發一張圖片,有興趣的自己查閱資料
這裡寫圖片描述

最後屏障

自動對賬,人工介入

總結

實現分散式事務一些思路

  • 一定有一個協調者,在事務的不同階段介入,保證事務的最終一致性。這個協調者是一個高度抽象,它可能是一箇中間件,可能是一個服務,甚至是一個任務,不管怎麼說,它一定是各個事務單元的第三者,協調各個事務單元最終達成一致性。
  • 協調者必須知道分散式事務的全貌,知道了發生了什麼,沒發生什麼,該發生什麼。
  • 事務發生前結果應該是預知的,或者說事務發生後結果是確定的。這就需要協調者能處理各種情況發生,不能遺漏任何情況,知道各種情況下的補償策略,或回滾,或重試,或者盡最大努力送達,或者通知人工介入。