分散式事務(六)之可靠訊息最終一致性
訊息傳送一致性:是指產生訊息的業務動作與訊息傳送的一致。也就是說,如果業務操作成功,那麼由這個業務操作所產生的訊息一定要成功投遞出去(一般是傳送到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進行保證(消費端無需考慮),可靠傳送訊息的步驟如下:
- 傳送一個事務訊息,這個時候,RocketMQ將訊息狀態標記為Prepared,注意此時這條訊息消費者是無法消費到的;
- 執行業務程式碼邏輯,可能是一個本地資料庫事務操作;
- 確認傳送訊息,這個時候,RocketMQ將訊息狀態標記為可消費,這個時候消費者,才能真正的保證消費到這條資料。
如果確認訊息傳送失敗了怎麼辦?RocketMQ會定期掃描訊息叢集中的事務訊息,如果發現了Prepared訊息,它會向訊息傳送端(生產者)確認。RocketMQ會根據傳送端設定的策略來決定是回滾還是繼續傳送確認訊息。這樣就保證了訊息傳送與本地事務同時成功或同時失敗。
如果消費失敗怎麼辦?阿里提供給我們的解決方法是:人工解決。
本地事務表
並不是所有的mq都支援事務訊息。也就是訊息一旦傳送到訊息佇列中,消費者立馬就可以消費到。此時可以使用獨立訊息服務、或者本地事務表。
可以看到,其實就是將訊息先發送到一個我們自己編寫的一個"獨立訊息服務"應用中,剛開始處於prepare狀態,業務邏輯處理成功後,確認傳送訊息,這個時候"獨立訊息服務"才會真正的把訊息傳送給訊息佇列。消費者消費成功後,ack時,除了對訊息佇列進行ack(圖中沒有畫出),對於獨立訊息服務也要進行ack,"獨立訊息服務"一般是把這條訊息刪除。而定時掃描prepare狀態的訊息,向訊息傳送端(生產者)確認的工作也由獨立訊息服務來完成。
對於"本地事務表",其實和"獨立訊息服務"的作用類似,只不過"獨立訊息服務"是需要獨立部署的,而"本地事務表"是將"獨立訊息服務"的功能內嵌到應用中。
我是御狐神,歡迎大家關注我的微信公眾號:wzm2zsd
參考文件
本文最先發布至微信公眾號,版權所有,禁止轉載!