啟發:從MNS事務消息談分布式事務
啟發:從MNS事務消息談分布式事務
事務消息本質上解決的問題是業務係統與消息係統之間的事務問題(跨係統分布式事務),其基本原理即兩階段提交以及最終一致性保障。最近看了下阿裏雲mns事務消息的實現原理,介紹的蠻簡潔透徹的,對了解分布式事務實現原理挺有幫助,在閱讀本文前推薦大家先仔細閱讀下阿裏雲"mns事務消息"一文。
事務消息
背景描述
有時候我們需要實現本地操作和消息發送的事務一致性功能。即:消息發送成功,則本地操作成功;反之,如果消息發送失敗,本地操作失敗(成功也需要rollback)。保證不出現操作成功但消息發送失敗;或者操作失敗但消息發送成功的情況;
另外,消費端,我們也希望消息一定被成功處理一次,不會因為消息端程序崩潰而導致消息沒有成功處理,進而需要人工重置消費進度。解決方案
利用消息服務MNS的延遲消息功能來實現。
準備工作
創建兩個隊列:
- 1.事務消息隊列 消息的有效期小於消息延遲時間。即如果生產者不主動修改(提交)消息可見時間,消息對消費者不可見;
- 2.操作日誌隊列 記錄事務消息的操作記錄信息。消息延遲時間為事務操作超時時間。日誌隊列中的消息確認(刪除)後將對消費者不可見。 <!-- more -->
具體步驟
- 1.發送一條事務準備消息到事務消息隊列;
- 2.寫操作日誌信息到操作日誌隊列,日誌中包含步驟1消息的消息句柄;
- 3.執行本地事務操作;
- 4.如果步驟3成功,提交消息(消息對消費者可見);反之,回滾消息;
- 5.確認步驟2中的操作日誌(刪除該日誌消息);
- 6.步驟4後,消費者可以接收到事務消息;
- 7.消費者處理消息;
- 8.消費者確認刪除消息; 如下圖:
![]()
異常分析:
生產者異常(例如:進程重啟):
A.讀取操作日誌隊列超時未確認日誌
B.檢查事務結果
C.如果檢查得到事務已經成功,則提交消息(重複提交無副作用,同一句柄的消息隻能成功提交一次)
D.確認操作日誌消費者異常(例如:進程重啟):
消息服務提供至少保證消費一次的特性,隻要步驟8不成功,消息在一段時間後可以繼續可見,被當前消費者或者其他消費者處理。
消息服務不可達(例如:斷網)
消息發送和接收處理狀態以及操作日誌都在消息服務端,消息服務本身具備高可靠和高可用的特點,所以隻要網絡恢複,事務可以繼續,能保證隻要生產者:操作成功,則消費者一定能夠拿到消息並處理成功;或操作失敗, 則消費者收不到消息的最終一致性。
在mns消息模型中兩階段提交的體現是:
- 1.在執行事務前先preSendMessage:其背後的原理是創建一個delay message,但是這個delay message的delaytime > lifetime, 基於這個前提在得到確切的commit/rollback操作前,這個消息對於接受者是永遠不可見的;
- 2.本地事務結束後commit/rollback message:如果本地事務提交成功,需要將之前提交的delay message設置為消費者可見(底層實現應該與將delay變為0類似);對應的如果本地事務提交失敗,需要將之前的delay message刪除;
這個過程需要注意到,我們務必保證在preSendMessage沒得到最終確認之前不被消費者獲取到,因此需要將發送的lifetime小於delaytime。
看到這裏也許你有疑問,為什麼要將過程切分成兩階段提交?我們先假設如果采用一次提交的策略,很顯然這次提交的切入點隻能存在於①本地事務開始之前②本地事務中③本地事務結束之後,那麼先看這三個切入點各自存在什麼問題。
- ①本地事務開始之前提交消息:在本地事務未完成之前,消息的消費者讀取到了message,如果消費者後續的服務調用中存在對該次本地事務提交有依賴,那必然導致數據不一致問題;如果本地事務的執行結果是失敗的,卻通知了消費者,很顯然會導致不可預期的數據錯誤。
- ②本地事務中:在本地事務中提交消息同樣會存在①中的問題,即便sendmessage是在本地事務的最後執行,因為事務的提交和消息被接受到的時序是無法保證的;
- ③本地事務結束之後:不同於①②兩個提交點,本地事務完成之後我們能夠明確的知道本地事務的執行結果,因此能夠確保事務提交(回滾)與消息被接受是有序的;然而如果消息沒有被成功發送消費者接受不到消息,而本地事務卻得到了正確執行,這就導致了數據不一致問題,並且如果沒有操作日誌,這個問題將變得難以追溯;
”單次提交“遇到的主要問題是:無法保障本地事務與消息被接受到的時序問題(或者說兩個分布式事務的時序)以及數據的一致性問題。再回到”兩階段提交“,兩階段提交能解決這兩個問題嗎?兩階段提交的確認操作是在本地事務完成之後(這個類似於③),因此其能夠解決時序問題,但是如果這個確認操作執行的過程中發生了宕機等情況導致確認操作失敗,依然會導致數據不一致問題。
在mns事務消息中最終一致性的實現:
mns通過延遲消息機製實現了兩階段提交,其如何保證數據一致性問題呢?一般我們的策略都是通過操作流水來進行補償以達到數據的最終一致性,同樣的mns也是基於這個原理實現。
- 在preSendMessage之後,mns會在日誌隊列中記錄一條opLog(opLog通過記錄preSendMessage的receipthandle來進行關聯),並且將這個opLog的delayTime設置為事務的超時時間;
- 當本地事務執行結束,並且preSendMessage被commit/rollback之後,再將這條opLog刪除;
- 同時存在一個任務監聽日誌隊列,當接收到opLog的消息,檢查對應的preSendMessage相關聯的本地事務是否執行成功。如果本地事務執行成功,則通過opLog中保存的receipthandle補償一次對preSendMessage的commit操作,如果checker發現本地事務執行失敗,那對應的補償一次rollback操作;
通過建立對opLog的監聽,我們能夠確保事務的最終一致性嗎?回答這個問題前,我們先看這個問題的本質:最終、一致性。
最終一致性問題的產生是由於發生了一些不可預期的問題,導致一個事務被提交(回滾),但消息沒被commit(rollback)。我們通過opLog來追溯那些沒有得到最終確認的消息並進行補償(最終),並且通過檢查本地事務的狀態來確認這次補償是commit或者是rollback(一致性)。正是基於這個補償的策略,mns事務消息解決了"兩階段提交"所遺留的一致性問題,但這個過程中我們需要注意幾個細節:
- 補償策略執行的時候需要明確知道本地事務的執行結果,因此我們的本地事務中需要記錄preSendMessage所關聯的本地事務操作結果。我們的做法是本地事務中同時記錄下preSendMessage的receipthandle, 當補償任務執行的時候,會通過opLog關聯的receipthandle來檢查,如果沒有找到相關記錄,那認為之前的本地事務被rollback了,否則commit;
- MNS如何建立了preSendMessage<=>Local Transaction<=>opLog之間的關聯關係?最簡單的實現肯定是通過preSendMessage的MessageId來實現,不過mns通過preSendMessage的receipthandle來建立了這個關聯(ReceiptHandle含義)同時避免了額外的存儲;
- mns的補償機製建立在對opLog的監聽,那麼我們怎麼確定一個補償的執行時機是合適的呢?補償一定要在事務有明確結果之後執行才有意義,那麼什麼時候能得到明確的事務執行結果?其實我們是無法確切的知道這個時間點的,但我們能夠有一個最低期望時間:不管一個事務成功或者失敗,它的周期都不能超過事務的超時時間。因此我們在發送opLog時需要設置opLog的delayTime>TransactionTimeout(如何確認transactionTimeout)來保證補償任務執行的時候本地事務一定執行完成。
從mns事務消息到分布式事務的啟發
上麵囉嗦的寫了一堆,看到這我們不妨對思考下mns事務消息解決的是業務係統(本地事務)與消息中間件之間的事務協同問題,如果是兩個業務係統之間的分布式事務如何實現?
好吧,如果堅持看到這,你可能覺得我標題黨了...那麼我建議你再讀一下”mns事務消息“一文。
更多文章請訪問我的博客
轉載請注明出處
最後更新:2017-08-31 10:33:18