分佈式事務解決方案之可靠消息最終一致性

語言: CN / TW / HK

theme: vuepress

攜手創作,共同成長!這是我參與「掘金日新計劃 · 8 月更文挑戰」的第5天,點擊查看活動詳情

一、什麼是可靠消息最終一致性事務

可靠消息最終一致性方案是指當事務發起方執行完成本地事務後併發出一條消息,事務參與方(消息消費者)一定能夠接收消息並處理事務成功,此方案強調的是隻要消息發給事務參與方最終事務要達到一致。

此方案是利用消息中間件完成,如下圖:

事務發起方(消息生產方)將消息發給消息中間件,事務參與方從消息中間件接收消息,事務發起方和消息中間件之間,事務參與方(消息消費方)和消息中間件之間都是通過網絡通信,由於網絡通信的不確定性會導致分佈式事務問題。

image.png

因此可靠消息最終一致性方案要解決以下幾個問題:

1、本地事務與消息發送的原子性問題

本地事務與消息發送的原子性問題即:事務發起方在本地事務執行成功後消息必須發出去,否則就丟棄消息。即實現本地事務和消息發送的原子性,要麼都成功,要麼都失敗。本地事務與消息發送的原子性問題是實現可靠消息最終一致性方案的關鍵問題。

先來嘗試下這種操作,先發送消息,再操作數據庫:

java begin transaction; //1.發送MQ //2.數據庫操作 commit transation;

這種情況下無法保證數據庫操作與發送消息的一致性,因為可能發送消息成功,數據庫操作失敗。

你立馬想到第二種方案,先進行數據庫操作,再發送消息:

java begin transaction; //1.數據庫操作 //2.發送MQ commit transation;

這種情況下貌似沒有問題,如果發送MQ消息失敗,就會拋出異常,導致數據庫事務回滾。但如果是超時異常,數據庫回滾,但MQ其實已經正常發送了,同樣會導致不一致。

2、事務參與方接收消息的可靠性

事務參與方必須能夠從消息隊列接收到消息,如果接收消息失敗可以重複接收消息。

3、消息重複消費的問題

由於網絡2的存在,若某一個消費節點超時但是消費成功,此時消息中間件會重複投遞此消息,就導致了消息的重複消費。

要解決消息重複消費的問題就要實現事務參與方的方法冪等性。

二、解決方案

1、本地消息表方案

本地消息表這個方案最初是eBay提出的,此方案的核心是通過本地事務保證數據業務操作和消息的一致性,然後通過定時任務將消息發送至消息中間件,待確認消息發送給消費方成功再將消息刪除。

下面以註冊送積分為例來説明:兩個微服務交互,用户服務和積分服務,用户服務負責添加用户,積分服務負責增加積分。

image.png

交互流程如下:

  1. 用户註冊

    用户服務在本地事務新增用户和增加 ”積分消息日誌“。(用户表和消息表通過本地事務保證一致)

    下邊是偽代碼:

    java begin transaction; //1.新增用户 //2.存儲積分消息日誌 commit transation; 這種情況下,本地數據庫操作與存儲積分消息日誌處於同一個事務中,本地數據庫操作與記錄消息日誌操作具備原子性。

  2. 定時任務掃描日誌

    如何保證將消息發送給消息隊列呢?

    經過第一步消息已經寫到消息日誌表中,可以啟動獨立的線程,定時對消息日誌表中的消息進行掃描併發送至消息中間件,在消息中間件反饋發送成功後刪除該消息日誌,否則等待定時任務下一週期重試。

  3. 消費消息

    如何保證消費者一定能消費到消息呢?

    這裏可以使用MQ的ack(即消息確認)機制,消費者監聽MQ,如果消費者接收到消息並且業務處理完成後向MQ發送ack(即消息確認),此時説明消費者正常消費消息完成,MQ將不再向消費者推送消息,否則消費者會不斷重試向消費者來發送消息。

    積分服務接收到”增加積分“消息,開始增加積分,積分增加成功後向消息中間件迴應ack,否則消息中間件將重複投遞此消息。

    由於消息會重複投遞,積分服務的”增加積分“功能需要實現冪等性。

2、RocketMQ事務消息方案

RocketMQ 是一個來自阿里巴巴的分佈式消息中間件,於 2012 年開源,並在 2017 年正式成為 Apache 頂級項目。據瞭解,包括阿里雲上的消息產品以及收購的子公司在內,阿里集團的消息產品全線都運行在 RocketMQ 之上,並且最近幾年的雙十一大促中,RocketMQ 都有搶眼表現。Apache RocketMQ 4.3之後的版本正式支持事務消息,為分佈式事務實現提供了便利性支持。

RocketMQ 事務消息設計則主要是為了解決 Producer 端的消息發送與本地事務執行的原子性問題,RocketMQ 的設計中 broker 與 producer 端的雙向通信能力,使得 broker 天生可以作為一個事務協調者存在;而 RocketMQ本身提供的存儲機制為事務消息提供了持久化能力;RocketMQ 的高可用機制以及可靠消息設計則為事務消息在系統發生異常時依然能夠保證達成事務的最終一致性。

在RocketMQ 4.3後實現了完整的事務消息,實際上其實是對本地消息表的一個封裝,將本地消息表移動到了MQ內部,解決 Producer 端的消息發送與本地事務執行的原子性問題。

image.png

執行流程如下:

為方便理解我們還以註冊送積分的例子來描述 整個流程。

Producer 即MQ發送方,本例中是用户服務,負責新增用户。MQ訂閲方即消息消費方,本例中是積分服務,負責新增積分。

  1. Producer 發送事務消息

    Producer (MQ發送方)發送事務消息至MQ Server,MQ Server將消息狀態標記為Prepared(預備狀態),注意此時這條消息消費者(MQ訂閲方)是無法消費到的。

    本例中,Producer 發送 ”增加積分消息“ 到MQ Server。

  2. MQ Server迴應消息發送成功

    MQ Server接收到Producer 發送給的消息則迴應發送成功表示MQ已接收到消息。

  3. Producer 執行本地事務

    Producer 端執行業務代碼邏輯,通過本地數據庫事務控制。

    本例中,Producer 執行添加用户操作。

  4. 消息投遞

    若Producer 本地事務執行成功則自動向MQServer發送commit消息,MQ Server接收到commit消息後將”增加積分消息“ 狀態標記為可消費,此時MQ訂閲方(積分服務)即正常消費消息;若Producer 本地事務執行失敗則自動向MQServer發送rollback消息,MQ Server接收到rollback消息後將刪除”增加積分消息“ 。

    MQ訂閲方(積分服務)消費消息,消費成功則向MQ迴應ack,否則將重複接收消息。這裏ack默認自動迴應,即程序執行正常則自動迴應ack。

  5. 事務回查

    如果執行Producer端本地事務過程中,執行端掛掉,或者超時,MQ Server將會不停的詢問同組的其他 Producer來獲取事務執行狀態,這個過程叫事務回查。MQ Server會根據事務回查結果來決定是否投遞消息。

    以上主幹流程已由RocketMQ實現,對用户側來説,用户需要分別實現本地事務執行以及本地事務回查方法,因此只需關注本地事務的執行狀態即可。