接 《RocketMQ實戰(zhàn)(一)》扫俺,《RocketMQ實戰(zhàn)(二)》,本篇博客主要討論的話題是:順序消費火脉、RMQ在分布式事務(wù)中的應(yīng)用等牵舵。
關(guān)于多Master多Slave的說明
由于在之前的博客中已經(jīng)搭建了雙Master柒啤,其實多Master多Slave大同小異,因此這里并不會一步步的演示搭建多Master多Slave畸颅,而是從思路上担巩,分析下重點應(yīng)該注意的配置項。
第一没炒,這四臺機器涛癌,對外是一個統(tǒng)一的整體,是一個rocketmq cluster送火,因此需要brokerClusterName保持統(tǒng)一
第二拳话,123機器是121的從,124機器是122的從种吸,如何在配置中體現(xiàn)弃衍? 主和從的brokerName需要保持一致,另外brokerId標(biāo)示了誰是主坚俗,誰是從(brokerId=0的就是主镜盯,大于0的就是從)
第三,注意namesrvAddr的地址是4臺NameServer
第四猖败,配置項中brokerRole需要指明 ASYNC_MASTER(異步復(fù)制Master) or SYNC_MASTER(同步雙寫Master) or SLAVE(從)
第五速缆,和以前的多Master啟動方式一致,先啟動4臺Namesrv恩闻,然后用指定配置文件的方式啟動Master/Slave即可
第六艺糜,多Master多Slave的好處在于,即便集群中某個broker掛了幢尚,也可以繼續(xù)消費破停,保證了實時性的高可用,但是并不是說某個master掛了侠草,slave就可以升級master辱挥,開源版本的rocketmq是不可以的。也就是說边涕,在這種情況下晤碘,slave只能提供讀的功能,將失去消息負(fù)載的能力功蜓。
Queue in Topic
對于RocketMQ而言园爷,Topic只是一個邏輯上的概念,真正的消息存儲其實是在Topic中的Queue中式撼。想一想童社,為什么RocketMQ要這要設(shè)計呢?其實是為了消息的順序消費著隆,后文中將為大家介紹扰楼。
初步認(rèn)識RocketMQ的核心模塊
rocketmq-broker:接受生產(chǎn)者發(fā)來的消息并存儲(通過調(diào)用rocketmq-store)呀癣,消費者從這里取得消息。
rocketmq-client:提供發(fā)送弦赖、接受消息的客戶端API项栏。
rocketmq-namesrv:NameServer,類似于Zookeeper蹬竖,這里保存著消息的TopicName沼沈,隊列等運行時的元信息。(有點NameNode的味道)
rocketmq-common:通用的一些類币厕,方法列另,數(shù)據(jù)結(jié)構(gòu)等
rocketmq-remoting:基于Netty4的client/server + fastjson序列化 + 自定義二進(jìn)制協(xié)議
rocketmq-store:消息、索引存儲等
rocketmq-filtersrv:消息過濾器Server旦装,需要注意的是页衙,要實現(xiàn)這種過濾,需要上傳代碼到MQ同辣!【一般而言拷姿,我們利用Tag足以滿足大部分的過濾需求,如果更靈活更復(fù)雜的過濾需求旱函,可以考慮filtersrv組件】
rocketmq-tools:命令行工具
Order Message
RocketMQ提供了3種模式的Producer:
NormalProducer(普通)、OrderProducer(順序)描滔、TransactionProducer(事務(wù))
在前面的博客當(dāng)中棒妨,涉及的都是NormalProducer,調(diào)用傳統(tǒng)的send方法含长,消息是無序的券腔。接下來,我們來看看順序消費拘泞。模擬這樣一個場景纷纫,如果一個用戶完成一個訂單需要3條消息,比如訂單的創(chuàng)建陪腌、訂單的支付辱魁、訂單的發(fā)貨,很顯然诗鸭,同一個用戶的訂單消息必須要順序消費染簇,但是不同用戶之間的訂單可以并行消費。
生產(chǎn)者端代碼示例:
注意强岸,一個Message除了Topic/Tag外锻弓,還有Key的概念。
上圖的send方法不同于以往蝌箍,有一個MessageQueueSelector青灼,將用于指定特定的消息發(fā)往特定的隊列當(dāng)中暴心!
注意在以前普通消費消息時設(shè)置的回調(diào)是MessageListenerConcurrently,而順序消費的回調(diào)設(shè)置是MessageListenerOrderly杂拨。
當(dāng)我們啟動2個Consumer進(jìn)行消費時专普,可以觀察到:
可以觀察得到,雖然從全局上來看扳躬,消息的消費不是有序的脆诉,但是每一個訂單下的3條消息是順序消費的!
其實贷币,如果需要保證消息的順序消費击胜,那么很簡單,首先需要做到一組需要有序消費的消息發(fā)往同一個broker的同一個隊列上役纹!其次消費者端采用有序Listener即可偶摔。
這里,RocketMQ底層是如何做到消息順序消費的促脉,看一看源碼你就能大概了解到辰斋,至少來說,在多線程消費場景下瘸味,一個線程只去消費一個隊列上的消息宫仗,那么自然就保證了消息消費的順序性,同時也保證了多個線程之間的并發(fā)性旁仿。也就是說其實broker并不能完全保證消息的順序消費藕夫,它僅僅能保證的消息的順序發(fā)送而已!
關(guān)于多線程消費這塊枯冈,RocketMQ早就替我們想好了毅贮,這樣設(shè)置即可:
想一想,在ActiveMQ中尘奏,我們?nèi)绻雽崿F(xiàn)并發(fā)消費的話滩褥,恐怕還得搞個線程池提交任務(wù)吧,RocketMQ讓我們的工作變得簡單炫加!
Transaction Message
在說事務(wù)消息之前瑰煎,我們先來說說分布式事務(wù)的那些事!
什么是分布式事務(wù)琢感,我的理解是一半事務(wù)丢间。怎么說,比如有2個異構(gòu)系統(tǒng)驹针,A異構(gòu)系統(tǒng)要做T1烘挫,B異構(gòu)系統(tǒng)要做T2,要么都成功,要么都失敗饮六。
要知道異構(gòu)系統(tǒng)其垄,很顯然,不在一個數(shù)據(jù)庫實例上卤橄,它們往往分布在不同物理節(jié)點上绿满,本地事務(wù)已經(jīng)失效。
2階段提交協(xié)議窟扑,Two-Phase Commit喇颁,是處理分布式事務(wù)的一種常見手段。2PC嚎货,存在2個重要角色:事務(wù)協(xié)調(diào)器(TC)橘霎,事務(wù)執(zhí)行者。
2PC殖属,可以看到節(jié)點之間的通信次數(shù)太多了姐叁,時間很長!時間變長了洗显,從而導(dǎo)致外潜,事務(wù)鎖定的資源時間也變長了,造成資源等待時間變長挠唆!在高并發(fā)場景下处窥,存在嚴(yán)重的性能問題!
下面玄组,我們來看看MQ在高并發(fā)場景下碧库,是如何解決分布式事務(wù)的。
考慮生活中的場景:
我們?nèi)ケ本c豐包子鋪吃炒肝巧勤,先去營業(yè)員那里付款(Action1),拿到小票(Ticket)弄匕,然后去取餐窗口排隊拿炒肝(Action2)颅悉。思考2個問題:第一,為什么不在付款的同時迁匠,給顧客炒肝剩瓶?如果這樣的話,會增加處理時間城丧,使得后面的顧客等待時間變長延曙,相當(dāng)于降低了接待顧客的能力(降低了系統(tǒng)的QPS)。第二亡哄,付了款枝缔,拿到的是Ticket,顧客為什么會接受?從心理上說愿卸,顧客相信Ticket會兌現(xiàn)炒肝灵临。事實上也是如此,就算在最后炒肝沒了趴荸,或者斷電斷水(系統(tǒng)出現(xiàn)異常)儒溉,顧客依然可以通過Ticket進(jìn)行退款操作,這樣都不會有什么損失7⒍邸(雖然這么說顿涣,但是實際上包子鋪最大化了它的利益,如果炒肝真的沒了酝豪,浪費了顧客的時間涛碑,不過顧客頂多發(fā)發(fā)牢騷,最后接受)
生活已經(jīng)告訴我們處理分布式事務(wù)寓调,保證數(shù)據(jù)最終一致性的思路锌唾!這個Ticket(憑證)其實就是消息!
業(yè)務(wù)操作和消息的生成耦合在一起夺英,保證了只要A銀行的賬戶發(fā)生扣款晌涕,那么一定會生成一條轉(zhuǎn)賬消息。只要A銀行系統(tǒng)的事務(wù)成功提交痛悯,我們可以通過實時消息服務(wù)余黎,將轉(zhuǎn)賬消息通知B銀行系統(tǒng),如果B銀行系統(tǒng)回復(fù)成功载萌,那么A銀行系統(tǒng)可以在table中設(shè)置這條轉(zhuǎn)賬消息的狀態(tài)惧财。
這樣耦合的方式,從架構(gòu)上來看扭仁,就有點不太優(yōu)雅垮衷,而且存在一些問題。比如說乖坠,消息的存儲實質(zhì)上是在A銀行系統(tǒng)中的搀突,如果A銀行系統(tǒng)出了問題,將導(dǎo)致無法轉(zhuǎn)賬熊泵。如果解耦仰迁,將消息獨立出來呢?
如上圖所示顽分,消息數(shù)據(jù)獨立存儲徐许,業(yè)務(wù)和消息解耦,實質(zhì)上消息的發(fā)送有2次卒蘸,一條是轉(zhuǎn)賬消息雌隅,另一條是確認(rèn)消息。
到這里,我們先來看看基于RocketMQ的代碼:
生產(chǎn)者這里用到是:TransactionMQProducer澄步。
這里涉及到2個角色:本地事務(wù)執(zhí)行器(代碼中的TransactionExecuterImpl)冰蘑、服務(wù)器回查客戶端Listener(代碼中的TransactionCheckListener)。
如果事務(wù)消息發(fā)送到MQ上后村缸,會回調(diào) ?本地事務(wù)執(zhí)行器祠肥;但是此時事務(wù)消息是prepare狀態(tài),對消費者還不可見梯皿,需要 ?本地事務(wù)執(zhí)行器 ?返回RMQ一個確認(rèn)消息仇箱。
事務(wù)消息是否對消費者可見,完全由事務(wù)返回給RMQ的狀態(tài)碼決定(狀態(tài)碼的本質(zhì)也是一條消息)东羹。
生產(chǎn)者發(fā)送了2條消息給RMQ剂桥,有一條本地事務(wù)執(zhí)行成功,有一條本地事務(wù)執(zhí)行失敗属提。
2條業(yè)務(wù)消息 + 2條確認(rèn)消息 ?因此是4條权逗;
注意到消費者只消費了一條數(shù)據(jù),就是只有告訴RMQ本地事務(wù)執(zhí)行成功的那條消息才會被消費冤议!因此是1條斟薇!
但是,注意到本地事務(wù)執(zhí)行失敗的消息恕酸,RMQ并沒有check listener堪滨?這是為什么呢?因為RMQ在3.0.8的時候還是支持check listener回查機制的蕊温,但是到了3.2.6的時候?qū)⑹聞?wù)回查機制“閹割”了袱箱!
那么3.0.8的時候,RMQ是怎么做事務(wù)回查的呢义矛?看一看源碼发笔,你會知道,其實事務(wù)消息開始是prepare狀態(tài)凉翻,然后RMQ會將其持久化到MySQL當(dāng)中筐咧,然后如果收到確認(rèn)消息,就刪除掉這條prepare消息噪矛,如果遲遲收不到確認(rèn)消息,那么RMQ會定時的掃描prepare消息铺罢,發(fā)送給produce group進(jìn)行回查確認(rèn)艇挨!
到這里,問題來了韭赘,要知道3.2.6版本缩滨,沒有回查機制了,會存在問題么?
當(dāng)然會存在問題脉漏!假設(shè)苞冯,我們發(fā)送一條轉(zhuǎn)賬事務(wù)消息給RMQ,成功后回調(diào)本地事務(wù)侧巨,DB減操作成功舅锄,剛準(zhǔn)備給RMQ一個確認(rèn)消息,此時突然斷電司忱,或者網(wǎng)絡(luò)抖動皇忿,使得這條確認(rèn)消息沒有發(fā)送出去。此時RMQ中的那條轉(zhuǎn)賬事務(wù)消息坦仍,始終處于prepare狀態(tài)鳍烁,消費者讀取不到,但是卻已經(jīng)完成一方的賬戶資金變動7痹a;摹!
既然梳玫,RMQ3.2.6版本不為我們進(jìn)行回查爹梁,那么只能由我們自己完成了。具體怎么做呢汽纠,咱們下期再來分析~?
see u , good night~