啓發:從MNS事務消息談分佈式事務

啓發:從MNS事務消息談分佈式事務

事務消息本質上解決的問題是業務系統與消息系統之間的事務問題(跨系統分佈式事務),其基本原理即兩階段提交以及最終一致性保障。最近看了下阿里雲mns事務消息的實現原理,介紹的蠻簡潔透徹的,對了解分佈式事務實現原理挺有幫助,在閱讀本文前推薦你們先仔細閱讀下阿里雲"mns事務消息"一文。php

事務消息


背景描述

有時候咱們須要實現本地操做和消息發送的事務一致性功能。即:消息發送成功,則本地操做成功;反之,若是消息發送失敗,本地操做失敗(成功也須要rollback)。保證不出現操做成功但消息發送失敗;或者操做失敗但消息發送成功的狀況;
另外,消費端,咱們也但願消息必定被成功處理一次,不會由於消息端程序崩潰而致使消息沒有成功處理,進而須要人工重置消費進度。html

解決方案

利用消息服務MNS的延遲消息功能來實現。mysql

準備工做

建立兩個隊列:

  • 1.事務消息隊列sql

    消息的有效期小於消息延遲時間。即若是生產者不主動修改(提交)消息可見時間,消息對消費者不可見;
  • 2.操做日誌隊列網絡

    記錄事務消息的操做記錄信息。消息延遲時間爲事務操做超時時間。日誌隊列中的消息確認(刪除)後將對消費者不可見。

    <!-- more -->分佈式

具體步驟

  • 1.發送一條事務準備消息到事務消息隊列;
  • 2.寫操做日誌信息到操做日誌隊列,日誌中包含步驟1消息的消息句柄;
  • 3.執行本地事務操做;
  • 4.若是步驟3成功,提交消息(消息對消費者可見);反之,回滾消息;
  • 5.確認步驟2中的操做日誌(刪除該日誌消息);
  • 6.步驟4後,消費者能夠接收到事務消息;
  • 7.消費者處理消息;
  • 8.消費者確認刪除消息;
    以下圖:

事務

異常分析:

生產者異常(例如:進程重啓):

A.讀取操做日誌隊列超時未確認日誌
B.檢查事務結果
C.若是檢查獲得事務已經成功,則提交消息(重複提交無反作用,同一句柄的消息只能成功提交一次)
D.確認操做日誌阿里雲

消費者異常(例如:進程重啓):

消息服務提供至少保證消費一次的特性,只要步驟8不成功,消息在一段時間後能夠繼續可見,被當前消費者或者其餘消費者處理。spa

消息服務不可達(例如:斷網)

消息發送和接收處理狀態以及操做日誌都在消息服務端,消息服務自己具有高可靠和高可用的特色,因此只要網絡恢復,事務能夠繼續,能保證只要生產者:操做成功,則消費者必定可以拿到消息並處理成功;或操做失敗, 則消費者收不到消息的最終一致性。.net

原文地址日誌

在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事務消息「一文。

更多文章請訪問個人博客轉載請註明出處

相關文章
相關標籤/搜索