RocketMQ(七)——Transaction Message(事務消息)


本文介紹RocketMQ提供的第三種類型的消息——Transaction Message(事務消息)。在說事務消息之前,我們先來說說分佈式事務的那些事!

分佈式事務

什麼是分佈式事務,我的理解是一半事務。怎麼說,比如有2個異構系統,A異構系統要做T1,B異構系統要做T2,要麼都成功,要麼都失敗。
要知道異構系統,很顯然,不在一個數據庫實例上,它們往往分佈在不同物理節點上,本地事務已經失效。
2階段提交

2階段提交

2階段提交協議,Two-Phase Commit,是處理分佈式事務的一種常見手段。2PC,存在2個重要角色:事務協調器(TC),事務執行者。

2PC,可以看到節點之間的通信次數太多了,時間很長!時間變長了,從而導致,事務鎖定的資源時間也變長了,造成資源等待時間變長!在高併發場景下,存在嚴重的性能問題!

通過MQ解決分佈式事務的思路

下面,我們來看看MQ在高併發場景下,是如何解決分佈式事務的。

考慮生活中的場景:

我們去北京慶豐包子鋪喫炒肝,先去營業員那裏付款(Action1),拿到小票(Ticket),然後去取餐窗口排隊拿炒肝(Action2)。思考2個問題:第一,爲什麼不在付款的同時,給顧客炒肝?如果這樣的話,會增加處理時間,使得後面的顧客等待時間變長,相當於降低了接待顧客的能力(降低了系統的QPS)。第二,付了款,拿到的是Ticket,顧客爲什麼會接受?從心理上說,顧客相信Ticket會兌現炒肝。事實上也是如此,就算在最後炒肝沒了,或者斷電斷水(系統出現異常),顧客依然可以通過Ticket進行退款操作,這樣都不會有什麼損失!(雖然這麼說,但是實際上包子鋪最大化了它的利益,如果炒肝真的沒了,浪費了顧客的時間,不過顧客頂多發發牢騷,最後接受)

生活已經告訴我們處理分佈式事務,保證數據最終一致性的思路!這個Ticket(憑證)其實就是消息!

1) 業務和消息生成耦合在一起

業務和消息生成耦合在一起

業務和消息生成耦合在一起

業務操作和消息的生成耦合在一起,保證了只要A銀行的賬戶發生扣款,那麼一定會生成一條轉賬消息。只要A銀行系統的事務成功提交,我們可以通過實時消息服務,將轉賬消息通知B銀行系統,如果B銀行系統回覆成功,那麼A銀行系統可以在table中設置這條轉賬消息的狀態。

這樣耦合的方式,從架構上來看,就有點不太優雅,而且存在一些問題。比如說,消息的存儲實質上是在A銀行系統中的,如果A銀行系統出了問題,將導致無法轉賬。如果解耦,將消息獨立出來呢?

2) 業務和消息解耦

在這裏插入圖片描述

業務和消息解耦

如上圖所示,消息數據獨立存儲,業務和消息解耦,實質上消息的發送有2次,一條是轉賬消息,另一條是確認消息。

RocketMQ 中的事務消息

1) 目前RMQ3.2.6中事務消息的實現原理及存在的問題

到這裏,先來看看基於RocketMQ的代碼:
在這裏插入圖片描述

生產者示例代碼

生產者這裏用到是:TransactionMQProducer
這裏涉及到2個角色:本地事務執行器(代碼中的TransactionExecuterImpl)、服務器回查客戶端Listener(代碼中的TransactionCheckListener)。如果事務消息發送到MQ上後,會回調 本地事務執行器;但是此時事務消息是prepare狀態,對消費者還不可見,需要 本地事務執行器 返回RMQ一個確認消息。

本地事務執行器

本地事務執行器

事務消息是否對消費者可見,完全由事務返回給RMQ的狀態碼決定(狀態碼的本質也是一條消息)。
回查Listener

回查Listener

運行結果

運行結果

生產者發送了2條消息給RMQ,有一條本地事務執行成功,有一條本地事務執行失敗。總共是2條業務消息 + 2條確認消息,因此是4條。注意到消費者只消費了一條數據,就是隻有告訴RMQ本地事務執行成功的那條消息纔會被消費!因此是1條!

但是,注意到本地事務執行失敗的消息,RMQ並沒有check listener?
這是爲什麼呢?因爲RMQ在3.0.8的時候還是支持check listener回查機制的,但是到了3.2.6的時候將事務回查機制“閹割”了!

那麼3.0.8的時候,RMQ是怎麼做事務回查的呢?
看一看源碼,你會知道,其實事務消息開始是prepare狀態,然後RMQ會將其持久化到MySQL當中,然後如果收到確認消息,就刪除掉這條prepare消息,如果遲遲收不到確認消息,那麼RMQ會定時的掃描prepare消息,發送給produce group進行回查確認!

到這裏,問題來了,要知道3.2.6版本,沒有回查機制了,會存在問題麼?
當然會存在問題!假設,我們發送一條轉賬事務消息給RMQ,成功後回調本地事務,DB減操作成功,剛準備給RMQ一個確認消息,此時突然斷電,或者網絡抖動,使得這條確認消息沒有發送出去。此時RMQ中的那條轉賬事務消息,始終處於prepare狀態,消費者讀取不到,但是卻已經完成一方的賬戶資金變動!!!

2) 問題解決思路

既然,RMQ3.2.6版本不爲我們進行回查,那麼只能由我們自己完成了。
重新看一下“業務和消息解耦”的那張轉賬流程的圖:
在這裏插入圖片描述

轉賬流程

在正常情況下,當然沒有問題,如果第五步(向MQ發送確認消息)出現失敗,加上RocketMQ 3.2.6版本沒有事務回查機制,就會導致這條轉賬消息,在A銀行完成了操作,但是遲遲對B銀行系統不可見!
在這裏插入圖片描述

解決RocketMQ 3.2.6不支持事務回查的思路

用戶U1從A銀行系統轉賬給B銀行系統的用戶U2的處理過程如下:

  1. A銀行系統生成一條轉賬消息,以事務消息的方式寫入RocketMQ,此時B銀行系統不可見這條消息
  2. 寫入MQ成功後,回調A銀行系統,對T1,T2表進行操作(很顯然需要是一個事務)
    我們重點關注下T2表,這個表是用來幹嘛的呢?每條轉賬消息都會在T2表中,該表有2個特殊的字段:status,updatetime。(用途會在後文詳述)
  3. 完成第二步,接下來發送確認消息給MQ,如果這個確認消息發送成功,那麼這條轉賬消息,將對B銀行系統可見。然後B銀行系統,會在一個事務中完成對t3,t5的操作。

如果發送確認消息給MQ失敗的處理思路:

  • 首先,B銀行系統,有一個定時任務(比如說每隔1MIN執行一次),掃描表t5,取得一段時間內的數據,發送給A銀行系統。要知道t5中的數據,必然是A銀行系統成功處理併發送確認消息成功的轉賬數據。爲什麼要發送給A銀行系統呢,其實就是爲了找到那些發送確認消息失敗的轉賬數據。那麼怎麼發給A銀行系統呢,這個方式比較多,可以考慮在來一個Topic,也可以考慮Netty等。發送給A銀行系統,其實就是爲了更新t2表的status,updatetime。
    這裏有一個關鍵,如何“掃描表t5,取得一段時間內的數據”?這就是t4的作用,在t4中記錄一個time字段,每次定時任務啓動,先更新time(比如設定爲當前系統時間,設置前的的時間爲old),然後掃描出t5中大於這個old時間的轉賬數據,如此循環往復。
  • 其次,A銀行系統,也有一個定時任務(可以根據業務消費能力定,可以大一些),掃描t2表(指定status及updatetime條件),將那些確認消息發送失敗的轉賬消息找出來,更新updatetime併發送給MQ。
    這樣,我們並沒有改動RocketMQ 3.2.6的源碼,而是在外圍解決了事務回查!

其實到這裏,你可以發現RocketMQ的一個特點,就是將生產者和MQ綁定,而不需要特別處理消費者,這是爲什麼呢?
因爲消息只要發往RocketMQ成功,那麼就意味着成功,爲什麼這麼說?前面,我們說過,消費者端消費消息只會產生2種錯誤,第一:timeout,第二:exception。要知道RocketMQ對於超時,會不斷重試;對於消費異常,會根據消費端的返回碼,會有重試機制保證。也就是,RocketMQ一定會讓消息得到消費,如果消費有問題,只能是消費者的問題,而不會是RocketMQ的問題!


本系統博客差不多是兩年前寫的,由於種種原因中途就停了,因此一直有些遺憾。正好簡書上有個大牛的博客很贊,基本就搬了過來。—— 2019年5月24日
RocketMQ實戰(三):分佈式事務
RocketMQ實戰(四)

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