RocketMQ實戰(三):一文搞懂分布式事務

接 ,RocketMQ實戰(二),本篇博客主要討論的話題是:順序消費、RMQ在分佈式事務中的應用等。

關於多Master多Slave的說明

由於在之前的博客中已經搭建了雙Master,其實多Master多Slave大同小異,因此這裡並不會一步步的演示搭建多Master多Slave,而是從思路上,分析下重點應該注意的配置項。

RocketMQ實戰(三):一文搞懂分佈式事務

多Master多Slave

第一,這四臺機器,對外是一個統一的整體,是一個rocketmq cluster,因此需要brokerClusterName保持統一

第二,123機器是121的從,124機器是122的從,如何在配置中體現? 主和從的brokerName需要保持一致,另外brokerId標示了誰是主,誰是從(brokerId=0的就是主,大於0的就是從)

第三,注意namesrvAddr的地址是4臺NameServer

第四,配置項中brokerRole需要指明 ASYNC_MASTER(異步複製Master) or SYNC_MASTER(同步雙寫Master) or SLAVE(從)

第五,和以前的多Master啟動方式一致,先啟動4臺Namesrv,然後用指定配置文件的方式啟動Master/Slave即可

第六,多Master多Slave的好處在於,即便集群中某個broker掛了,也可以繼續消費,保證了實時性的高可用,但是並不是說某個master掛了,slave就可以升級master,開源版本的rocketmq是不可以的。也就是說,在這種情況下,slave只能提供讀的功能,將失去消息負載的能力。


Queue in Topic

對於RocketMQ而言,Topic只是一個邏輯上的概念,真正的消息存儲其實是在Topic中的Queue中。想一想,為什麼RocketMQ要這要設計呢?其實是為了消息的順序消費,後文中將為大家介紹。

RocketMQ實戰(三):一文搞懂分佈式事務

queue in topic

RocketMQ實戰(三):一文搞懂分佈式事務

默認一個Topic中4個隊列

RocketMQ實戰(三):一文搞懂分佈式事務

配置文件中指定

初步認識RocketMQ的核心模塊

RocketMQ實戰(三):一文搞懂分佈式事務

rocketmq模塊

rocketmq-broker:接受生產者發來的消息並存儲(通過調用rocketmq-store),消費者從這裡取得消息。

rocketmq-client:提供發送、接受消息的客戶端API。

rocketmq-namesrv:NameServer,類似於Zookeeper,這裡保存著消息的TopicName,隊列等運行時的元信息。(有點NameNode的味道)

rocketmq-common:通用的一些類,方法,數據結構等

rocketmq-remoting:基於Netty4的client/server + fastjson序列化 + 自定義二進制協議

rocketmq-store:消息、索引存儲等

rocketmq-filtersrv:消息過濾器Server,需要注意的是,要實現這種過濾,需要上傳代碼到MQ!【一般而言,我們利用Tag足以滿足大部分的過濾需求,如果更靈活更復雜的過濾需求,可以考慮filtersrv組件】

rocketmq-tools:命令行工具

Order Message

RocketMQ提供了3種模式的Producer:

NormalProducer(普通)、OrderProducer(順序)、TransactionProducer(事務)

在前面的博客當中,涉及的都是NormalProducer,調用傳統的send方法,消息是無序的。接下來,我們來看看順序消費。模擬這樣一個場景,如果一個用戶完成一個訂單需要3條消息,比如訂單的創建、訂單的支付、訂單的發貨,很顯然,同一個用戶的訂單消息必須要順序消費,但是不同用戶之間的訂單可以並行消費。

生產者端代碼示例:

RocketMQ實戰(三):一文搞懂分佈式事務

順序消息模式

注意,一個Message除了Topic/Tag外,還有Key的概念。

上圖的send方法不同於以往,有一個MessageQueueSelector,將用於指定特定的消息發往特定的隊列當中!

RocketMQ實戰(三):一文搞懂分佈式事務

順序消費

注意在以前普通消費消息時設置的回調是MessageListenerConcurrently,而順序消費的回調設置是MessageListenerOrderly。

當我們啟動2個Consumer進行消費時,可以觀察到:

RocketMQ實戰(三):一文搞懂分佈式事務

多個消費者消費的結果

可以觀察得到,雖然從全局上來看,消息的消費不是有序的,但是每一個訂單下的3條消息是順序消費的!

其實,如果需要保證消息的順序消費,那麼很簡單,首先需要做到一組需要有序消費的消息發往同一個broker的同一個隊列上!其次消費者端採用有序Listener即可。

這裡,RocketMQ底層是如何做到消息順序消費的,看一看源碼你就能大概瞭解到,至少來說,在多線程消費場景下,一個線程只去消費一個隊列上的消息,那麼自然就保證了消息消費的順序性,同時也保證了多個線程之間的併發性。也就是說其實broker並不能完全保證消息的順序消費,它僅僅能保證的消息的順序發送而已!

關於多線程消費這塊,RocketMQ早就替我們想好了,這樣設置即可:

RocketMQ實戰(三):一文搞懂分佈式事務

消費多線程設置

想一想,在ActiveMQ中,我們如果想實現併發消費的話,恐怕還得搞個線程池提交任務吧,RocketMQ讓我們的工作變得簡單!

Transaction Message

在說事務消息之前,我們先來說說分佈式事務的那些事!

什麼是分佈式事務,我的理解是一半事務。怎麼說,比如有2個異構系統,A異構系統要做T1,B異構系統要做T2,要麼都成功,要麼都失敗。

要知道異構系統,很顯然,不在一個數據庫實例上,它們往往分佈在不同物理節點上,本地事務已經失效。

RocketMQ實戰(三):一文搞懂分佈式事務

2階段提交

2階段提交協議,Two-Phase Commit,是處理分佈式事務的一種常見手段。2PC,存在2個重要角色:事務協調器(TC),事務執行者。

2PC,可以看到節點之間的通信次數太多了,時間很長!時間變長了,從而導致,事務鎖定的資源時間也變長了,造成資源等待時間變長!在高併發場景下,存在嚴重的性能問題!

下面,我們來看看MQ在高併發場景下,是如何解決分佈式事務的。

考慮生活中的場景:

我們去北京慶豐包子鋪吃炒肝,先去營業員那裡付款(Action1),拿到小票(Ticket),然後去取餐窗口排隊拿炒肝(Action2)。思考2個問題:第一,為什麼不在付款的同時,給顧客炒肝?如果這樣的話,會增加處理時間,使得後面的顧客等待時間變長,相當於降低了接待顧客的能力(降低了系統的QPS)。第二,付了款,拿到的是Ticket,顧客為什麼會接受?從心理上說,顧客相信Ticket會兌現炒肝。事實上也是如此,就算在最後炒肝沒了,或者斷電斷水(系統出現異常),顧客依然可以通過Ticket進行退款操作,這樣都不會有什麼損失!(雖然這麼說,但是實際上包子鋪最大化了它的利益,如果炒肝真的沒了,浪費了顧客的時間,不過顧客頂多發發牢騷,最後接受)

生活已經告訴我們處理分佈式事務,保證數據最終一致性的思路!這個Ticket(憑證)其實就是消息!

RocketMQ實戰(三):一文搞懂分佈式事務

業務和消息生成耦合在一起

業務操作和消息的生成耦合在一起,保證了只要A銀行的賬戶發生扣款,那麼一定會生成一條轉賬消息。只要A銀行系統的事務成功提交,我們可以通過實時消息服務,將轉賬消息通知B銀行系統,如果B銀行系統回覆成功,那麼A銀行系統可以在table中設置這條轉賬消息的狀態。

這樣耦合的方式,從架構上來看,就有點不太優雅,而且存在一些問題。比如說,消息的存儲實質上是在A銀行系統中的,如果A銀行系統出了問題,將導致無法轉賬。如果解耦,將消息獨立出來呢?

RocketMQ實戰(三):一文搞懂分佈式事務

業務和消息解耦

如上圖所示,消息數據獨立存儲,業務和消息解耦,實質上消息的發送有2次,一條是轉賬消息,另一條是確認消息。

到這裡,我們先來看看基於RocketMQ的代碼:

RocketMQ實戰(三):一文搞懂分佈式事務

生產者示例代碼

生產者這裡用到是:TransactionMQProducer。

這裡涉及到2個角色:本地事務執行器(代碼中的TransactionExecuterImpl)、服務器回查客戶端Listener(代碼中的TransactionCheckListener)。

如果事務消息發送到MQ上後,會回調 本地事務執行器;但是此時事務消息是prepare狀態,對消費者還不可見,需要 本地事務執行器 返回RMQ一個確認消息。

RocketMQ實戰(三):一文搞懂分佈式事務

本地事務執行器

事務消息是否對消費者可見,完全由事務返回給RMQ的狀態碼決定(狀態碼的本質也是一條消息)。

RocketMQ實戰(三):一文搞懂分佈式事務

回查Listener

RocketMQ實戰(三):一文搞懂分佈式事務

運行結果

生產者發送了2條消息給RMQ,有一條本地事務執行成功,有一條本地事務執行失敗。

2條業務消息 + 2條確認消息 因此是4條;

注意到消費者只消費了一條數據,就是隻有告訴RMQ本地事務執行成功的那條消息才會被消費!因此是1條!

但是,注意到本地事務執行失敗的消息,RMQ並沒有check listener?這是為什麼呢?因為RMQ在3.0.8的時候還是支持check listener回查機制的,但是到了3.2.6的時候將事務回查機制“閹割”了!

那麼3.0.8的時候,RMQ是怎麼做事務回查的呢?看一看源碼,你會知道,其實事務消息開始是prepare狀態,然後RMQ會將其持久化到MySQL當中,然後如果收到確認消息,就刪除掉這條prepare消息,如果遲遲收不到確認消息,那麼RMQ會定時的掃描prepare消息,發送給produce group進行回查確認!

到這裡,問題來了,要知道3.2.6版本,沒有回查機制了,會存在問題麼?

當然會存在問題!假設,我們發送一條轉賬事務消息給RMQ,成功後回調本地事務,DB減操作成功,剛準備給RMQ一個確認消息,此時突然斷電,或者網絡抖動,使得這條確認消息沒有發送出去。此時RMQ中的那條轉賬事務消息,始終處於prepare狀態,消費者讀取不到,但是卻已經完成一方的賬戶資金變動!!!

既然,RMQ3.2.6版本不為我們進行回查,那麼只能由我們自己完成了。具體怎麼做呢,咱們下期再來分析~

see u , good night~

鏈接:https://www.jianshu.com/p/53324ea2df92


分享到:


相關文章: