分佈式事務[7]柔性事務:可靠消息最終一致性

原文鏈接:http://www.tianshouzhi.com/api/tutorials/distributed_transaction/389

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

消息發送一致性:是指產生消息的業務動作與消息發送的一致。也就是說,如果業務操作成功,那麼由這個業務操作所產生的消息一定要成功投遞出去(一般是發送到kafkarocketmqrabbitmq等消息中間件中),否則就丟消息。

    柔性事務、可靠消息最終一致性、異步確保性

 下面用僞代碼進行演示消息發送和投遞的不可靠性:

1、先進行數據庫操作,再發送消息

  1. public void test1(){
  2. //1 數據庫操作
  3. //2 發送MQ消息
  4. }

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

2、先發送消息,再操作數據庫public void test1(){

  1. //1 發送MQ消息
  2. //2 數據庫操作
  3. }

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

3、在數據庫事務中,先發送消息,後操作數據庫

  1. @Transactional
  2. public void test1(){
  3. //1 發送MQ消息
  4. //2 數據庫操作
  5. }

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

4、在數據庫事務中,先操作數據庫,後發送消息

  1. @Transactional
  2. public void test1(){
  3. //1 數據庫操作
  4. //2 發送MQ消息
  5. }

    這種情況下,貌似沒有問題,如果發送MQ消息失敗,拋出異常,事務一定會回滾(加上了@Transactional註解後,spring方法拋出異常後,會自動進行回滾)。

    這只是一個假象,因爲發送MQ消息可能事實上已經成功,如果是響應超時導致的異常。這個時候,數據庫操作依然回滾,但是MQ消息實際上已經發送成功,導致不一致。

5、使用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的事務消息,以下展示了RocketMQ的事務消息機制。

BE2326B0-FC19-4620-BD2E-D34A32B8A405.png

事務消息的邏輯,由發送端 Producer進行保證(消費端無需考慮)

    首先,發送一個事務消息,這個時候,RocketMQ將消息狀態標記爲Prepared,注意此時這條消息消費者是無法消費到的。

    接着,執行業務代碼邏輯,可能是一個本地數據庫事務操作

    最後,確認發送消息,這個時候,RocketMQ將消息狀態標記爲可消費,這個時候消費者,才能真正的保證消費到這條數據。

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

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

    

    另外一種實現,並不是所有的mq都支持事務消息。也就是消息一旦發送到消息隊列中,消費者立馬就可以消費到。此時可以使用獨立消息服務、或者本地事務表。

ECEB0CD9-A61C-4ED9-98C1-CE02A9B658C3.png

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章