分佈式事務實現

本地消息表

假設消息中間件沒有提供「事務消息」功能,好比你用的是Kafka。那如何解決分佈式事務呢?網絡

解決方案以下: 
(1)Producer端準備1張消息表,把update DB和insert message這2個操做,放在一個DB事務裏面。分佈式

(2)準備一個後臺程序,源源不斷的把消息表中的message傳送給消息中間件。失敗了,不斷重試重傳。容許消息重複,但消息不會丟,順序也不會打亂。源碼分析

(3)Consumer端準備一個判重表。處理過的消息,記在判重表裏面。實現業務的冪等。但這裏又涉及一個原子性問題:若是保證消息消費 + insert message到判重表這2個操做的原子性?this

消費成功,但insert判重表失敗,怎麼辦?關於這個,在Kafka的源碼分析系列,第1篇, exactly once問題的時候,有過討論。spa

經過上面3步,咱們基本就解決了這裏update db和發送網絡消息這2個操做的原子性問題。設計

但這個方案的一個缺點就是:須要設計DB消息表,同時還須要一個後臺任務,不斷掃描本地消息。致使消息的處理和業務邏輯耦合額外增長業務方的負擔。code

事務型消息

爲了能解決該問題,同時又不和業務耦合,RocketMQ/Notify提出了「事務消息」的概念。中間件

具體來講,就是把消息的發送分紅了2個階段:Prepare階段和確認階段。隊列

具體來講,上面的2個步驟,被分解成3個步驟: 
(1) 發送Prepared消息 
(2) update DB 
(3) 根據update DB結果成功或失敗,Confirm或者取消Prepared消息。事務

可能有人會問了,前2步執行成功了,最後1步失敗了怎麼辦?這裏就涉及到了RocketMQ的關鍵點:RocketMQ會按期(默認是1分鐘)掃描全部的Prepared消息,詢問發送方,究竟是要確認這條消息發出去?仍是取消此條消息?

具體代碼實現以下:

也就是定義了一個checkListener,RocketMQ會回調此Listener,從而實現上面所說的方案。

// 也就是上文所說的,當RocketMQ發現`Prepared消息`時,會根據這個Listener實現的策略來決斷事務
TransactionCheckListener transactionCheckListener = new TransactionCheckListenerImpl();
// 構造事務消息的生產者
TransactionMQProducer producer = new TransactionMQProducer("groupName");
// 設置事務決斷處理類
producer.setTransactionCheckListener(transactionCheckListener);
// 本地事務的處理邏輯,至關於示例中檢查Bob帳戶並扣錢的邏輯
TransactionExecuterImpl tranExecuter = new TransactionExecuterImpl();
producer.start()
// 構造MSG,省略構造參數
Message msg = new Message(......);
// 發送消息
SendResult sendResult = producer.sendMessageInTransaction(msg, tranExecuter, null);
producer.shutdown();

 

public TransactionSendResult sendMessageInTransaction(.....)  {
    // 邏輯代碼,非實際代碼
    // 1.發送消息
    sendResult = this.send(msg);
    // sendResult.getSendStatus() == SEND_OK
    // 2.若是消息發送成功,處理與消息關聯的本地事務單元
    LocalTransactionState localTransactionState = tranExecuter.executeLocalTransactionBranch(msg, arg);
    // 3.結束事務
    this.endTransaction(sendResult, localTransactionState, localException);
}

 

總結:對比方案2和方案1,RocketMQ最大的改變,其實就是把「掃描消息表」這個事情,不讓業務方作,而是消息中間件幫着作了。

至於消息表,其實仍是沒有省掉。由於消息中間件要詢問發送方,事物是否執行成功,仍是須要一個「變相的本地消息表」,記錄事物執行狀態。

人工介入

可能有人又要說了,不管方案1,仍是方案2,發送端把消息成功放入了隊列,但消費端消費失敗怎麼辦?

消費失敗了,重試,還一直失敗怎麼辦?是否是要自動回滾整個流程?

答案是人工介入。從工程實踐角度講,這種整個流程自動回滾的代價是很是巨大的,不但實現複雜,還會引入新的問題。好比自動回滾失敗,又怎麼處理?

對應這種極低機率的case,採起人工處理,會比實現一個高複雜的自動化回滾系統,更加可靠,也更加簡單。

相關文章
相關標籤/搜索