RocketMQ進階必學:事物消息
分布式事務在微服務中是比較常見且又比較棘手的難題,當然,它也并不是無解的,如果熟悉分布式事務的同學,應該知道 XA、2PC/3PC、TCC 、事務消息等解決方案。
事務消息是分布式事務解決方案的一種,也是我們今天要討論的主題。
什么是事務消息?
事務消息的目標,是為了實現可靠性消息最終一致性。
這個又是怎么理解呢?舉個例子可能大家就比較容易理解。
還是我們比較常見的支付場景:用戶支付下單,修改訂單狀態為完成,然后會通過 MQ 發送訂單消息。
下游服務比如積分系統更新用戶的積分、物流系統需要更新訂單的物流信息、推送系統需要給用戶推送消息或者廣告等。
圖片
可以說這算是一個比較標準的微服務架構,根據業務領域劃分不同業務系統,各業務系統通過 MQ 消息來實現訂單業務的解耦,流程上看似沒什么問題。
但我們仔細看一下,在訂單系統訂單更新和發送消息的這個步驟中,實際上會發生不一致的情況:
- 訂單更新成功,消息發送失敗。
- 訂單更新成功,消息發送超時。
- 訂單更新失敗,消息發送不管成功還是失敗,整個事務都會回滾。
- 訂單更新成功,消息發送成功,業務端消費失敗。
我們逐一來分析這幾種情況:
- 第1種訂單更新成功,消息發送失敗。這時下游的業務系統和支付系統的訂單數據是不一致的。不過我們既然明確知道是消息發送失敗,是可以對消息進行重試或者補償。
- 第2種訂單更新成功,消息發送超時。這種情況我們是無法確定消息是否發送成功,如果要進行重試或補償,就需要對投遞的消息進行反查詢,不然可能會重復發送。
- 第3種情況比較簡單,訂單執行失敗,這時是拋出異常或者直接返回,都不會走到消息發送的流程。
- 第4種情況我們可以不用考慮,因為消息發送成功了之后,訂單服務就完成了自己的任務,至于下游是否消費成功,是各自業務系統各自負責的。
在這里其實可能有人會有疑問,包括我自己。如果把發送消息和訂單更新邏輯放在一個事務里面,執行事務成功就發送普通消息,失敗就回滾是不是也可以?
這里就是對應的第2種情況,如果事務執行失敗是因為消息發送超時,但是我們無法確定消息是否就真的成功發送到消息服務中。
總結一下,消息發送失敗或者超時,其實已經造成了數據不一致的情況,也就是訂單系統和下游的積分系統、物流系統、推送系統的訂單數據不一致。
而對消息進行重試或者補償,是為了保證訂單數據最終能同步到下方各業務系統,這就是所說的消息最終一致性。
如果我們要解決這個問題,實現消息最終一致性,有什么方案嗎?
增加一個消息發送記錄表,在訂單執行成功的同時,插入一條當前訂單的消息發送記錄,消息發送成功后,再更新數據的發送狀態。如果是消息發送失敗或者超時,由程序后臺任務來補償重發。
圖片
這個方法比較簡單好理解,就是通過記錄消息發送的狀態,來確保訂單更新成功后,消息最終一定能觸達到 MQ,后臺的定時任務則是對發送超時的消息補償。
如果對于要求消息最終一致性的業務場景,是不是都要這么實現一套繁瑣的流程呢?沒錯,這個確實很麻煩。
不過,事務消息可以幫我們解決這個問題。
事務消息原理
RocketMQ 事務消息流程??
圖片
我們結合上面用戶支付的例子,看一下使用 RocketMQ 如何實現事務消息:
- 首先,在訂單執行操作之前,先發送一條半事務消息到 RocketMQ,表示即將開始執行本地事務,RocketMQ 收到這個半事務消息之后,不會馬上投遞,而是把消息暫存在臨時隊列中。
- 半消息發送成功之后,訂單系統就開始執行本地事務,即更新訂單的操作。執行完成后會發送一條確認消息到 RocketMQ 中,告知訂單執行的結果。如果訂單執行成功,RocketMQ 會將之前收到的半事務消息投遞到目標隊列中,消費端可馬上訂閱消費;如果訂單執行失敗,RocketMQ 則會刪除之前收到的半事務消息,保證消息不會被消費。
當然,我們也要考慮到極端的情況:假如訂單更新后就宕機或者重啟,那么發給 RocketMQ 確認消息就會丟掉,訂單數據還是會發送不一致的情況。
所以,這里就需要消息回查的機制。RocketMQ 收到半事務消息后,在特定的時間內如果沒有收到確認消息,那么 RocketMQ 會反過來請求本地事務執行的最終狀態(即訂單更新的執行結果),根據返回的執行結果再對半事務消息做處理。
不過換個角度來看,其實 RocketMQ 的事務消息與我們上面使用消息記錄表來實現消息一致性的方案有異曲同工之妙。或者可以說其實我們已經逆向實現了這一套事務消息。
RocketMQ 自身會存儲消息,相當于幫我們實現了消息發送的記錄;發送半消息相當于插入一條消息發送記錄;發送確認消息相當于更新消息發送記錄;消息回查相當于后臺任務的補償和重試。
再從另外一個角度來看,無論是我們自己實現的事務消息還是 RocketMQ 的事務消息,其實都是二階段提交(2PC)的實現。第一階段的提交與第二階段的確認,目的都是保證本地事務與消息的一致性。
適用場景
事務消息是保證消息的最終一致性,在中間階段可能會出現數據不一致的情況。
所以適用于一些需要異步更新數據,并且對數據實時性要求不太高的場景。
比如在用戶支付訂單后,可能會出現在短暫的幾秒里,用戶積分沒有變動、購物車的商品沒有被處理等,這些情況是可以接受的,只要保證最終用戶積分、購物車的數據和訂單數據保持一致就可以。