解析 RocketMQ 業務消息——“事務消息”

作者:合伯

引言:在分佈式系統調用場景中存在這樣一個通用問題,即在執行一個核心業務邏輯的同時,還需要調用多個下游做業務處理,而且要求多個下游業務和當前核心業務必須同時成功或者同時失敗,進而避免部分成功和失敗的不一致情況出現。簡單來說,消息隊列中的“事務”,主要解決的是消息生產者和消費者的數據一致性問題。本篇文章通過拆解 RocketMQ 事務消息的使用場景、基本原理、實現細節和實戰使用,幫助大家更好的理解和使用 RocketMQ 的事務消息。

點擊下方鏈接,查看視頻講解:

https://yqh.aliyun.com/live/detail/29199

場景:爲什麼需要事務消息

以電商交易場景爲例,用戶支付訂單這一核心操作的同時會涉及到下游物流發貨、積分變更、購物車狀態清空等多個子系統的變更。當前業務的處理分支包括:

  • 主分支訂單系統狀態更新:由未支付變更爲支付成功;
  • 物流系統狀態新增:新增待發貨物流記錄,創建訂單物流記錄;
  • 積分系統狀態變更:變更用戶積分,更新用戶積分表;
  • 購物車系統狀態變更:清空購物車,更新用戶購物車記錄。

1.png

分佈式系統調用的特點是:一個核心業務邏輯的執行,同時需要調用多個下游業務進行處理。因此,如何保證核心業務和多個下游業務的執行結果完全一致,是分佈式事務需要解決的主要問題。

傳統 XA 事務方案:性能不足

爲了保證上述四個分支的執行結果一致性,典型方案是基於XA協議的分佈式事務系統來實現。將四個調用分支封裝成包含四個獨立事務分支的大事務,基於XA分佈式事務的方案可以滿足業務處理結果的正確性,但最大的缺點是多分支環境下資源鎖定範圍大,併發度低,隨着下游分支的增加,系統性能會越來越差。

基於普通消息方案:一致性保障困難

將上述基於 XA 事務的方案進行簡化,將訂單系統變更作爲本地事務,剩下的系統變更作爲普通消息的下游來執行,事務分支簡化成普通消息+訂單表事務,充分利用消息異步化的能力縮短鏈路,提高併發度。

2.png

該方案中消息下游分支和訂單系統變更的主分支很容易出現不一致的現象,例如:

  • 消息發送成功,訂單沒有執行成功,需要回滾整個事務;
  • 訂單執行成功,消息沒有發送成功,需要額外補償才能發現不一致;
  • 消息發送超時未知,此時無法判斷需要回滾訂單還是提交訂單變更。

基於RocketMQ分佈式事務消息:支持最終一致性

上述普通消息方案中,普通消息和訂單事務無法保證一致的本質原因是普通消息無法像單機數據庫事務一樣,具備提交、回滾和統一協調的能力。

而基於消息隊列 RocketMQ 版實現的分佈式事務消息功能,在普通消息基礎上,支持二階段的提交能力。將二階段提交和本地事務綁定,實現全局提交結果的一致性。

3.png

消息隊列 RocketMQ 版事務消息的方案,具備高性能、可擴展、業務開發簡單的優勢。

基本原理

概念介紹

  • 事務消息:RocketMQ 提供類似 XA 或 Open XA 的分佈式事務功能,通過 RocketMQ 事務消息能達到分佈式事務的最終一致;

  • 半事務消息:暫不能投遞的消息,生產者已經成功地將消息發送到了 RocketMQ 服務端,但是 RocketMQ 服務端未收到生產者對該消息的二次確認,此時該消息被標記成“暫不能投遞”狀態,處於該種狀態下的消息即半事務消息;

  • 消息回查:由於網絡閃斷、生產者應用重啓等原因,導致某條事務消息的二次確認丟失,RocketMQ 服務端通過掃描發現某條消息長期處於“半事務消息”時,需要主動向消息生產者詢問該消息的最終狀態(Commit 或是 Rollback),該詢問過程即消息回查。

事務消息生命週期

4.png

  • 初始化:半事務消息被生產者構建並完成初始化,待發送到服務端的狀態;

  • 事務待提交:半事務消息被髮送到服務端,和普通消息不同,並不會直接被服務端持久化,而是會被單獨存儲到事務存儲系統中,等待第二階段本地事務返回執行結果後再提交。此時消息對下游消費者不可見;

  • 消息回滾:第二階段如果事務執行結果明確爲回滾,服務端會將半事務消息回滾,該事務消息流程終止;

  • 提交待消費:第二階段如果事務執行結果明確爲提交,服務端會將半事務消息重新存儲到普通存儲系統中,此時消息對下游消費者可見,等待被消費者獲取並消費;

  • 消費中:消息被消費者獲取,並按照消費者本地的業務邏輯進行處理的過程。此時服務端會等待消費者完成消費並提交消費結果,如果一定時間後沒有收到消費者的響應,RocketMQ 會對消息進行重試處理。具體信息,請參見消息重試;

  • 消費提交:消費者完成消費處理,並向服務端提交消費結果,服務端標記當前消息已經被處理(包括消費成功和失敗);RocketMQ 默認支持保留所有消息,此時消息數據並不會立即被刪除,只是邏輯標記已消費。消息在保存時間到期或存儲空間不足被刪除前,消費者仍然可以回溯消息重新消費。

  • 消息刪除:當消息存儲時長到期或存儲空間不足時,RocketMQ 會按照滾動機制清理最早保存的消息數據,將消息從物理文件中刪除。

事務消息基本流程

事務消息交互流程如下圖所示:

5.png

  1. 生產者將消息發送至 RocketMQ 服務端;

  2. RocketMQ 服務端將消息持久化成功之後,向生產者返回 Ack 確認消息已經發送成功,此時消息被標記爲“暫不能投遞”,這種狀態下的消息即爲半事務消息;

  3. 生產者開始執行本地事務邏輯;

  4. 生產者根據本地事務執行結果向服務端提交二次確認結果(Commit 或是 Rollback),服務端收到確認結果後處理邏輯如下:

    • 二次確認結果爲 Commit:服務端將半事務消息標記爲可投遞,並投遞給消費者;
    • 二次確認結果爲 Rollback:服務端將回滾事務,不會將半事務消息投遞給消費者。
  1. 在斷網或者是生產者應用重啓的特殊情況下,若服務端未收到發送者提交的二次確認結果,或服務端收到的二次確認結果爲Unknown未知狀態,經過固定時間後,服務端將對消息生產者即生產者集羣中任一生產者實例發起消息回查;

  2. 生產者收到消息回查後,需要檢查對應消息的本地事務執行的最終結果;

  3. 生產者根據檢查到的本地事務的最終狀態再次提交二次確認,服務端仍按照步驟 4 對半事務消息進行處理。

實現細節:RocketMQ 事務消息如何實現

6.png

根據發送事務消息的基本流程的需要,實現分爲三個主要流程:接收處理 Half 消息、Commit 或 Rollback 命令處理、事務消息 check。

處理 Half 消息

發送方第一階段發送 Half 消息到 Broker 後,Broker 處理 Half 消息。Broker 流程參考下圖:

7.jpeg

具體流程是首先把消息轉換 Topic 爲 RMQ_SYS_TRANS_HALF_TOPIC,其餘消息內容不變,寫入 Half 隊列。具體實現參考 SendMessageProcessor 的邏輯處理。

Commit 或 Rollback 命令處理

發送方完成本地事務後,繼續發送 Commit 或 Rollback 到 Broker。由於當前事務已經完結,Broker 需要刪除原有的 Half 消息,由於 RocketMQ 的 appendOnly 特性,Broker通過 OP 消息實現標記刪除。Broker 流程參考下圖:

8.jpeg

  • Commit。Broker 寫入 OP 消息,OP 消息的 body 指定 Commit 消息的 queueOffset,標記之前 Half 消息已被刪除;同時,Broker 讀取原 Half 消息,把 Topic 還原,重新寫入 CommitLog,消費者則可以拉取消費;

  • Rollback。Broker 同樣寫入 OP 消息,流程和 Commit 一樣。但後續不會讀取和還原 Half 消息。這樣消費者就不會消費到該消息。

具體實現在 EndTransactionProcessor 中。

事務消息 check

如果發送端事務時間執行過程,發送 UNKNOWN 命令,或者 Broker/發送端重啓發布等原因,流程 2 的標記刪除的 OP 消息可能會缺失,因此增加了事務消息 check 流程,該流程是在異步線程定期執行(transactionCheckInterval 默認 30s 間隔),針對這些缺失 OP 消息的 Half 消息進行 check 狀態。具體參考下圖:

9.jpeg

事務消息 check 流程掃描當前的 OP 消息隊列,讀取已經被標記刪除的 Half 消息的 queueOffset。如果發現某個 Half 消息沒有 OP 消息對應標記,並且已經超時(transactionTimeOut 默認 6 秒),則讀取該 Half 消息重新寫入 half 隊列,並且發送 check 命令到原發送方檢查事務狀態;如果沒有超時,則會等待後讀取 OP 消息隊列,獲取新的 OP 消息。

另外,爲了避免發送方的異常導致長期無法確定事務狀態,如果某個 Half 消息的 bornTime 超過最大保留時間(transactionCheckMaxTimeInMs 默認 12 小時),則會自動跳過此消息,不再 check。

具體實現參考:

TransactionalMessageServiceImpl#check 方法。

實戰:使用事務消息

瞭解了 RocketMQ 事務消息的原理後,我們看下如何使用事務。首先,我們需要創建一個 “事務消息” 類型的 Topic,可以使用控制檯或者 CLi 命令創建。

10.png

事務消息相比普通消息發送時需要修改以下幾點:

  • 發送事務消息前,需要開啓事務並關聯本地的事務執行。
  • 爲保證事務一致性,在構建生產者時,必須設置事務檢查器和預綁定事務消息發送的主題列表,客戶端內置的事務檢查器會對綁定的事務主題做異常狀態恢復。

11.png

當事務消息 commit 之後,這條消息其實就是一條投遞到用戶 Topic 的普通消息而已。所以對於消費者來說,和普通消息的消費沒有區別。

12.png

注意:

  1. 避免大量未決事務導致超時:在事務提交階段異常的情況下發起事務回查,保證事務一致性;但生產者應該儘量避免本地事務返回未知結果;大量的事務檢查會導致系統性能受損,容易導致事務處理延遲;
  2. 事務消息的 Group ID 不能與其他類型消息的 Group ID 共用:與其他類型的消息不同,事務消息有回查機制,回查時服務端會根據 Group ID 去查詢生產者客戶端;
  3. 事務超時機制:半事務消息被生產者發送服務端後,如果在指定時間內服務端無法確認提交或者回滾狀態,則消息默認會被回滾。

今天通過對 RocketMQ 事務消息的介紹,希望能夠幫大家對事務消息的原理和應用有更深入的瞭解,同時也期望 RocketMQ 的事務消息能夠幫助您更有效的解決業務問題。如果您對 RocktMQ 的業務消息感興趣,也歡迎您掃描下方二維碼加入釘釘羣一起溝通交流~

13.png

點擊此處,進入官網瞭解更多詳情~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章