分散式事務(六)之可靠訊息最終一致性

御狐神發表於2021-11-22

訊息傳送一致性:是指產生訊息的業務動作與訊息傳送的一致。也就是說,如果業務操作成功,那麼由這個業務操作所產生的訊息一定要成功投遞出去(一般是傳送到kafka、rocketmq、rabbitmq等訊息中介軟體中),否則就丟訊息。

可靠訊息最終一致性

傳送訊息不可靠性

既然提到了可靠訊息的最終一致性,那麼說明現有的訊息傳送邏輯存在不可靠性,我們用下面的幾種情況來演示訊息的不可靠性。

  • 先進行資料庫操作,再傳送訊息:

    public void test1(){
    //1 資料庫操作
    //2 傳送MQ訊息
    }
    

    這種情況下無法保證資料庫操作與傳送訊息的一致性,因為可能資料庫操作成功,傳送訊息失敗

  • 先傳送訊息,再運算元據庫:

    public void test1(){
    //1 傳送MQ訊息
    //2 資料庫操作
    }
    

    這種情況下無法保證資料庫操作與傳送訊息的一致性,因為可能傳送訊息成功,資料庫操作失敗。

  • 在資料庫事務中,先傳送訊息,後運算元據庫:

    @Transactional
    public void test1(){
    //1 傳送MQ訊息
    //2 資料庫操作
    }
    

    這裡使用spring 的@Transactional註解,方法裡面的操作都在一個事務中。同樣無法保證一致性,因為傳送訊息成功了,資料庫操作失敗的情況下,資料庫操作是回滾了,但是MQ訊息沒法進行回滾。

  • 在資料庫事務中,先運算元據庫,後傳送訊息:

    @Transactional
    public void test1(){
    //1 資料庫操作
    //2 傳送MQ訊息
    }
    

    這種情況下,貌似沒有問題,如果傳送MQ訊息失敗,丟擲異常,事務一定會回滾(加上了@Transactional註解後,spring方法丟擲異常後,會自動進行回滾)。
    這只是一個假象,因為傳送MQ訊息可能事實上已經成功,如果是響應超時導致的異常。這個時候,資料庫操作依然回滾,但是MQ訊息實際上已經傳送成功,導致不一致。

  • 使用JTA事務管理器:

    前面通過spring的@Transactional註解加在方法上,來開啟事務。其實有一個條件沒有明確的說出來,就是我們配置的事務管理器是DataSourceTransactionManager。

    事實上,Spring還提供了另外一個分散式事務管理器JtaTransactionManager。這個是使用XA兩階段提交來保證事務的一致性。當然前提是,你的訊息中介軟體是實現了JMS規範中事務訊息相關API(回顧前面我們介紹JTA規範時,提到DB、MQ都只是資源管理器RM,對於事務管理器來說,二者是等價的)。

    因此如果你滿足了2個條件:1、使用JtaTransactionManager 2、DB、MQ分別實現了JDBC、JMS規範中規定的RM應該實現的兩階段提交的API,就可以保證訊息傳送的一致性。

    DB作為RM,一般都是支援兩階段提交的。不過,一些MQ中介軟體並不支援,所以你要找到支援兩階段提交的MQ中介軟體。另外,JtaTransactionManager只是一個代理,你需要提供一個真實的事務管理器(TM)實現。如前面提到了atomikos公司,就有這樣的產品。

    但是筆者依然不建議,這樣做。因為XA兩階段提交效能低,我們使用訊息中介軟體就是為了非同步解耦,這種情況,雖然保證了一致性,但是響應時間卻大大增加,系統可用性降低。

可靠傳送訊息的解決方案

有兩種方法可以實現可靠訊息傳送:基於MQ的事務訊息和本地事務表。

基於MQ的事務訊息

以RocketMQ的事務訊息為例,如下圖所示,訊息的可靠傳送由傳送端 Producer進行保證(消費端無需考慮),可靠傳送訊息的步驟如下:

  1. 傳送一個事務訊息,這個時候,RocketMQ將訊息狀態標記為Prepared,注意此時這條訊息消費者是無法消費到的;
  2. 執行業務程式碼邏輯,可能是一個本地資料庫事務操作;
  3. 確認傳送訊息,這個時候,RocketMQ將訊息狀態標記為可消費,這個時候消費者,才能真正的保證消費到這條資料。

如果確認訊息傳送失敗了怎麼辦?RocketMQ會定期掃描訊息叢集中的事務訊息,如果發現了Prepared訊息,它會向訊息傳送端(生產者)確認。RocketMQ會根據傳送端設定的策略來決定是回滾還是繼續傳送確認訊息。這樣就保證了訊息傳送與本地事務同時成功或同時失敗。

如果消費失敗怎麼辦?阿里提供給我們的解決方法是:人工解決。

RocketMQ

本地事務表

並不是所有的mq都支援事務訊息。也就是訊息一旦傳送到訊息佇列中,消費者立馬就可以消費到。此時可以使用獨立訊息服務、或者本地事務表。

本地事務

可以看到,其實就是將訊息先傳送到一個我們自己編寫的一個"獨立訊息服務"應用中,剛開始處於prepare狀態,業務邏輯處理成功後,確認傳送訊息,這個時候"獨立訊息服務"才會真正的把訊息傳送給訊息佇列。消費者消費成功後,ack時,除了對訊息佇列進行ack(圖中沒有畫出),對於獨立訊息服務也要進行ack,"獨立訊息服務"一般是把這條訊息刪除。而定時掃描prepare狀態的訊息,向訊息傳送端(生產者)確認的工作也由獨立訊息服務來完成。

對於"本地事務表",其實和"獨立訊息服務"的作用類似,只不過"獨立訊息服務"是需要獨立部署的,而"本地事務表"是將"獨立訊息服務"的功能內嵌到應用中。

我是御狐神,歡迎大家關注我的微信公眾號:wzm2zsd

qrcode_for_gh_83670e17bbd7_344-2021-09-04-10-55-16

參考文件

柔性事務:可靠訊息最終一致性

本文最先發布至微信公眾號,版權所有,禁止轉載!

相關文章