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

消息發送一致性:是指產生消息的業務動作與消息發送的一致。也就是說,如果業務操作成功,那麼由這個業務操作所產生的消息一定要成功投遞出去(一般是發送到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

參考文檔

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

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

Tags: