1. 程式人生 > >啟發:從MNS事務訊息談分散式事務(轉)

啟發:從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事務訊息“一文。