RocketMQ 分散式事務訊息

AYSAML發表於2020-08-28

一、什麼是事務

事務是將一次執行過程中所涉及的所有操作納入到一個不可分割的執行單元,組成事務的所有操作只有在所有操作均能正常執行的情況下才能提交,只要其中任一操作執行失敗,都將導致整個事務的回滾。一句話來說,就是保證多個操作要麼都做,要麼都不做。同時一旦事務提交,則其所做的修改會永久儲存到資料庫。

二、事務的四個特性(ACID)

  • A:原子性(Atomicity)
    一個事務(transaction)中的所有操作,要麼全部完成,要麼全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。
  • C:一致性(Consistency)
    事務的一致性指的是在一個事務執行之前和執行之後資料庫都必須處於一致性狀態。如果事務成功地完成,那麼系統中所有變化將正確地應用,系統處於有效狀態。如果在事務中出現錯誤,那麼系統中的所有變化將自動地回滾,系統返回到原始狀態。
  • I:隔離性(Isolation)
    指的是在併發環境中,當不同的事務同時操縱相同的資料時,每個事務都有各自的完整資料空間。由併發事務所做的修改必須與任何其他併發事務所做的修改隔離。事務檢視資料更新時,資料所處的狀態要麼是另一事務修改它之前的狀態,要麼是另一事務修改它之後的狀態,事務不會檢視到中間狀態的資料。
  • D:永續性(Durability)
    指的是隻要事務成功結束,它對資料庫所做的更新就必須永久儲存下來。即使發生系統崩潰,重新啟動資料庫系統後,資料庫還能恢復到事務成功結束時的狀態。

三、InnoDB 事務實現

基於衡量事務的四個特性,InnoDB 實現事務實際上就是 4 個特性的實現。

  • 原子性

    • 在 MySQL 中有很多型別的日誌,二進位制日誌、查詢日誌、錯誤日誌、慢查詢日誌等等。除了這些日誌,還提供了兩種事務日誌,redo log 用來保證永續性, undo log 是原子性和隔離性實現的基礎。
    • 資料庫每執行一條更新資料的 sql 就會生成一條 undo log,比如 insert 一條資料,就會生出一條 delete 的 undo log。如果事務執行失敗或者呼叫 rollback 就可以根據 undo log 做資料回滾。
  • 隔離性

    • 隔離性是指,事務內部的操作與其他事務是隔離的,併發執行的各個事務之間不能互相干擾。嚴格的隔離性,對應了事務隔離級別中的Serializable (可序列化),但實際應用中出於效能方面的考慮很少會使用可序列化。
    • InnoDB 採用可重複讀隔離級別,使用 MVCC 和行鎖、間隙鎖實現隔離性。
  • 永續性

    • InnoDB作為MySQL的儲存引擎,資料是存放在磁碟中的,但如果每次讀寫資料都需要磁碟IO,效率會很低。為此,InnoDB提供了快取(Buffer Pool),Buffer Pool中包含了磁碟中部分資料頁的對映,作為訪問資料庫的緩衝:當從資料庫讀取資料時,會首先從Buffer Pool中讀取,如果Buffer Pool中沒有,則從磁碟讀取後放入Buffer Pool;當向資料庫寫入資料時,會首先寫入Buffer Pool,Buffer Pool中修改的資料會定期重新整理到磁碟中(這一過程稱為刷髒)。
    • Buffer Pool的使用大大提高了讀寫資料的效率,但是也帶了新的問題:如果MySQL當機,而此時Buffer Pool中修改的資料還沒有重新整理到磁碟,就會導致資料的丟失,事務的永續性無法保證。
    • 於是,redo log被引入來解決這個問題:當資料修改時,除了修改Buffer Pool中的資料,還會在redo log記錄這次操作;當事務提交時,會呼叫fsync介面對redo log進行刷盤。如果MySQL當機,重啟時可以讀取redo log中的資料,對資料庫進行恢復。redo log採用的是WAL(Write-ahead logging,預寫式日誌),所有修改先寫入日誌,再更新到Buffer Pool,保證了資料不會因MySQL當機而丟失,從而滿足了永續性要求。
    既然redo log也需要在事務提交時將日誌寫入磁碟,為什麼它比直接將Buffer Pool中修改的資料寫入磁碟(即刷髒)要快呢?主要有以下兩方面的原因:
    (1)刷髒是隨機IO,因為每次修改的資料位置隨機,但寫redo log是追加操作,屬於順序IO。
    (2)刷髒是以資料頁(Page)為單位的,MySQL預設頁大小是16KB,一個Page上一個小修改都要整頁寫入;而redo log中只包含真正需要寫入的部分,無效IO大大減少。
  • 一致性

    • 一致性是指事務執行結束後,資料庫的完整性約束沒有被破壞,事務執行的前後都是合法的資料狀態。
    • 一致性不僅由資料庫本身來保證,同時業務系統也保證資料的一致性。

四、分散式事務的由來

現代軟體架構隨著業務領域劃分為多個微服務,共同組成了複雜的軟體系統。而從資料庫層面來看,隨著資料量的爆發,不得不採用分庫分表的方式,降低資料庫的壓力。這樣,就造成多個服務依賴不同的資料庫,那麼在同時操作的時候,如何保證事務?這就是分散式事務。

簡而言之,分散式事務就是一個大的事務由不同的子事務組成,這些小的事務操作分佈在不同的伺服器節點上面,屬於不同的微服務,分散式事務需要保證同一事務下的子事務要麼全部成功,要麼全部失敗,即保證資料的最終一致性。

五、分散式事務解決方案

在這篇不想用太大的篇幅說一些概念上的東西,但是要說 RocketMQ 的分散式事務實現,所以在這裡順便提一下當前分散式事務的集中解決方案:

  • 兩階段提交(2PC)

    兩階段提交(2PC) 是 Oracle Tuxedo 系統提出的 XA 分散式事務協議的其中一種實現方式,參考 《分散式事務之兩階段提交(2PC)》

  • Try-Confirm-Cancle (TCC)

    TCC 是基於嘗試、確認、取消來實現分散式事務的,想了解更多,參考 《分散式事務之補償事務( TCC )》

  • 本地訊息表

    本地訊息表 方案最初是ebay提出的,核心是將需要分散式處理的任務通過訊息日誌的方式來非同步執行。訊息日誌可以儲存到本地文字、資料庫或訊息佇列,再通過業務規則自動或人工發起重試。人工重試更多的是應用於支付場景,通過對賬系統對事後問題的處理。

image.png

除了上述外,還有一些解決方案,比如阿里 SEATA ,SAGA方案和最大努力通知...感興趣同學們可以自行了解,當然還有我們這篇要說的 MQ 事務。

六、MQ 事務

RocketMQ 是阿里開源的一款高效能、高吞吐量的分散式訊息中介軟體,基於訊息非同步方式提供了對分散式事務的支援,實現事務最終一致性。

下面是 RocketMQ 事務訊息的基本流程互動圖:

image.png

如圖其中分為兩個流程:正常事務訊息的傳送及提交、事務訊息的補償流程。

1.事務訊息傳送及提交:

(1) 傳送 half 訊息。
(2) 服務端響應訊息寫入結果。
(3) 根據傳送結果執行本地事務(如果寫入失敗,此時half訊息對業務不可見,本地邏輯不執行)。
(4) 根據本地事務狀態執行 Commit 或者 Rollback( Commit 操作生成訊息索引,訊息對消費者可見)

流程圖如下:

image.png

2.補償流程:

(1) 對沒有 Commit/Rollback 的事務訊息( pending 狀態的訊息),從服務端發起一次“回查”
(2) Producer收到回查訊息,檢查回查訊息對應的本地事務的狀態
(3) 根據本地事務狀態,重新Commit或者Rollback

其中,補償階段使用定時器回查方式用於解決訊息 Commit 或者 Rollback 發生超時或者失敗的情況。

七、RocektMQ 事務訊息的使用

如上,小夥伴們應該對 RocketMQ 的事務訊息有了一定的瞭解,下面看下如何在開發場景下如何使用。

傳送事務訊息時和普通的訊息區別是,自己要新建一個 TransactionMQProducer 和對應的一個 TransactionListener的實現。

  • TransactionMQProducer
    具體的配置有 group、 nameServer 地址、執行本地事務的執行緒池和事務監聽器的實現。
this.producer = new TransactionMQProducer(config.getGroup());
    this.producer.setNamesrvAddr(config.getNameServer());
    this.producer.setExecutorService(config.getExecutorService());
    this.producer.setTransactionListener(config.getTransactionListener());
  • TransactionListener
    實現 TransactionListener 介面的兩個方法:

    • executeLocalTransaction(Message message, Object o)
      用於執行本地事務的方法。
    • checkLocalTransaction(MessageExt messageExt)
      RocketMQ 回查本地事務狀態呼叫的方法。

程式碼詳見 ? : https://github.com/wangning1018/rocketmq-transaction-message-demo

歡迎訪問個人部落格 獲取更多知識分享。

相關文章