柔性事務:可靠消息最終一致性
消息發送一致性:是指產生消息的業務動作與消息發送的一致。也就是說,如果業務操作成功,那麼由這個業務操作所產生的消息一定要成功投遞出去(一般是發送到kafka
、rocketmq
、rabbitmq
等消息中間件中),否則就丟消息。
柔性事務、可靠消息最終一致性、異步確保性
下面用僞代碼進行演示消息發送和投遞的不可靠性:
1、先進行數據庫操作,再發送消息
- public void test1(){
- //1 數據庫操作
- //2 發送MQ消息
- }
這種情況下無法保證數據庫操作與發送消息的一致性,因爲可能數據庫操作成功,發送消息失敗
2、先發送消息,再操作數據庫public void test1(){
- //1 發送MQ消息
- //2 數據庫操作
- }
這種情況下無法保證數據庫操作與發送消息的一致性,因爲可能發送消息成功,數據庫操作失敗
3、在數據庫事務中,先發送消息,後操作數據庫
- @Transactional
- public void test1(){
- //1 發送MQ消息
- //2 數據庫操作
- }
這裏使用spring 的@Transactional
註解,方法裏面的操作都在一個事務中。同樣無法保證一致性,因爲發送消息成功了,數據庫操作失敗的情況下,數據庫操作是回滾了,但是MQ消息沒法進行回滾。
4、在數據庫事務中,先操作數據庫,後發送消息
- @Transactional
- public void test1(){
- //1 數據庫操作
- //2 發送MQ消息
- }
這種情況下,貌似沒有問題,如果發送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的事務消息機制。
事務消息的邏輯,由發送端 Producer進行保證(消費端無需考慮)
首先,發送一個事務消息,這個時候,RocketMQ將消息狀態標記爲Prepared,注意此時這條消息消費者是無法消費到的。
接着,執行業務代碼邏輯,可能是一個本地數據庫事務操作
最後,確認發送消息,這個時候,RocketMQ將消息狀態標記爲可消費,這個時候消費者,才能真正的保證消費到這條數據。
如果確認消息發送失敗了怎麼辦?RocketMQ會定期掃描消息集羣中的事務消息,如果發現了Prepared消息,它會向消息發送端(生產者)確認。RocketMQ會根據發送端設置的策略來決定是回滾還是繼續發送確認消息。這樣就保證了消息發送與本地事務同時成功或同時失敗。
如果消費失敗怎麼辦?阿里提供給我們的解決方法是:人工解決。
另外一種實現,並不是所有的mq都支持事務消息。也就是消息一旦發送到消息隊列中,消費者立馬就可以消費到。此時可以使用獨立消息服務、或者本地事務表。
可以看到,其實就是將消息先發送到一個我們自己編寫的一個"獨立消息服務"應用中,剛開始處於prepare狀態,業務邏輯處理成功後,確認發送消息,這個時候"獨立消息服務"纔會真正的把消息發送給消息隊列。消費者消費成功後,ack時,除了對消息隊列進行ack(圖中沒有畫出),對於獨立消息服務也要進行ack,"獨立消息服務"一般是把這條消息刪除。而定時掃描prepare狀態的消息,向消息發送端(生產者)確認的工作也由獨立消息服務來完成。
對於"本地事務表",其實和"獨立消息服務"的作用類似,只不過"獨立消息服務"是需要獨立部署的,而"本地事務表"是將"獨立消息服務"的功能內嵌到應用中。