消息隊(duì)列中間件重要嗎?面試必問(wèn)問(wèn)題之一,你說(shuō)重不重要。我有時(shí)會(huì)問(wèn)同事,為啥你用RabbitMQ,不用Kafka,或者RocketMQ呢,他給我的回答“因?yàn)楣居玫木褪沁@個(gè),大家都這么用”,如果你去面試,直接就被Pass,今天這篇文章,告訴你如何回答。 這篇文章純理論,主要整理網(wǎng)絡(luò)資料,肝了我整整一個(gè)月!文章依然延續(xù)上幾篇的風(fēng)格,很長(zhǎng),長(zhǎng)到我只整理排版,手都整麻了。全文2.5萬(wàn)字,建議先收藏,后續(xù)面試、或者技術(shù)選型,再拿出來(lái)喵喵,不BB,上思維導(dǎo)圖! ![]() 消息隊(duì)列消息隊(duì)列模式消息隊(duì)列目前主要2種模式,分別為“點(diǎn)對(duì)點(diǎn)模式”和“發(fā)布/訂閱模式”。 點(diǎn)對(duì)點(diǎn)模式一個(gè)具體的消息只能由一個(gè)消費(fèi)者消費(fèi)。多個(gè)生產(chǎn)者可以向同一個(gè)消息隊(duì)列發(fā)送消息;但是,一個(gè)消息在被一個(gè)消息者處理的時(shí)候,這個(gè)消息在隊(duì)列上會(huì)被鎖住或者被移除并且其他消費(fèi)者無(wú)法處理該消息。需要額外注意的是,如果消費(fèi)者處理一個(gè)消息失敗了,消息系統(tǒng)一般會(huì)把這個(gè)消息放回隊(duì)列,這樣其他消費(fèi)者可以繼續(xù)處理。 ![]() 發(fā)布/訂閱模式單個(gè)消息可以被多個(gè)訂閱者并發(fā)的獲取和處理。一般來(lái)說(shuō),訂閱有兩種類型:
![]() 衡量標(biāo)準(zhǔn)對(duì)消息隊(duì)列進(jìn)行技術(shù)選型時(shí),需要通過(guò)以下指標(biāo)衡量你所選擇的消息隊(duì)列,是否可以滿足你的需求:
消息隊(duì)列比較下圖是從網(wǎng)上摘抄過(guò)來(lái)的,可以看到主流MQ的對(duì)比: ![]() 下面簡(jiǎn)單介紹常用的消息隊(duì)列:
優(yōu)缺點(diǎn)Kafka優(yōu)點(diǎn):
缺點(diǎn):
總結(jié):
RabbitMQ優(yōu)點(diǎn):
缺點(diǎn):
總結(jié):
RocketMQ優(yōu)點(diǎn):
缺點(diǎn):
總結(jié):
ActiveMQ優(yōu)點(diǎn)
缺點(diǎn):
Kafka![]() Kafka 是由 Linkedin 公司開(kāi)發(fā)的,它是一個(gè)分布式的,支持多分區(qū)、多副本,基于 Zookeeper 的分布式消息流平臺(tái),它同時(shí)也是一款開(kāi)源的基于發(fā)布訂閱模式的消息引擎系統(tǒng)。 基本概念
![]()
![]()
系統(tǒng)架構(gòu)一個(gè)典型的 Kafka 集群中包含若干Producer(可以是web前端產(chǎn)生的Page View,或者是服務(wù)器日志,系統(tǒng)CPU、Memory等),若干broker(Kafka支持水平擴(kuò)展,一般broker數(shù)量越多,集群吞吐率越高),若干Consumer Group,以及一個(gè)Zookeeper集群。Kafka通過(guò)Zookeeper管理集群配置,選舉leader,以及在Consumer Group發(fā)生變化時(shí)進(jìn)行rebalance。Producer使用push模式將消息發(fā)布到broker,Consumer使用pull模式從broker訂閱并消費(fèi)消息。 ![]() 生產(chǎn)者數(shù)據(jù)執(zhí)行流程在 Kafka 中,我們把產(chǎn)生消息的那一方稱為生產(chǎn)者,比如我們經(jīng)?;厝ヌ詫氋?gòu)物,你打開(kāi)淘寶的那一刻,你的登陸信息,登陸次數(shù)都會(huì)作為消息傳輸?shù)?Kafka 后臺(tái),當(dāng)你瀏覽購(gòu)物的時(shí)候,你的瀏覽信息,你的搜索指數(shù),你的購(gòu)物愛(ài)好都會(huì)作為一個(gè)個(gè)消息傳遞給 Kafka 后臺(tái),然后淘寶會(huì)根據(jù)你的愛(ài)好做智能推薦,致使你的錢包從來(lái)都禁不住誘惑,那么這些生產(chǎn)者產(chǎn)生的消息是怎么傳到 Kafka 應(yīng)用程序的呢?發(fā)送過(guò)程是怎么樣的呢? 盡管消息的產(chǎn)生非常簡(jiǎn)單,但是消息的發(fā)送過(guò)程還是比較復(fù)雜的,如圖: ![]() 我們從創(chuàng)建一個(gè)ProducerRecord 對(duì)象開(kāi)始,ProducerRecord 是 Kafka 中的一個(gè)核心類,它代表了一組 Kafka 需要發(fā)送的 key/value 鍵值對(duì),它由記錄要發(fā)送到的主題名稱(Topic Name),可選的分區(qū)號(hào)(Partition Number)以及可選的鍵值對(duì)構(gòu)成。 在發(fā)送 ProducerRecord 時(shí),我們需要將鍵值對(duì)對(duì)象由序列化器轉(zhuǎn)換為字節(jié)數(shù)組,這樣它們才能夠在網(wǎng)絡(luò)上傳輸。然后消息到達(dá)了分區(qū)器。如果發(fā)送過(guò)程中指定了有效的分區(qū)號(hào),那么在發(fā)送記錄時(shí)將使用該分區(qū)。如果發(fā)送過(guò)程中未指定分區(qū),則將使用key 的 hash 函數(shù)映射指定一個(gè)分區(qū)。如果發(fā)送的過(guò)程中既沒(méi)有分區(qū)號(hào)也沒(méi)有,則將以循環(huán)的方式分配一個(gè)分區(qū)。選好分區(qū)后,生產(chǎn)者就知道向哪個(gè)主題和分區(qū)發(fā)送數(shù)據(jù)了。ProducerRecord 還有關(guān)聯(lián)的時(shí)間戳,如果用戶沒(méi)有提供時(shí)間戳,那么生產(chǎn)者將會(huì)在記錄中使用當(dāng)前的時(shí)間作為時(shí)間戳。Kafka 最終使用的時(shí)間戳取決于 topic 主題配置的時(shí)間戳類型。然后,這條消息被存放在一個(gè)記錄批次里,這個(gè)批次里的所有消息會(huì)被發(fā)送到相同的主題和分區(qū)上。由一個(gè)獨(dú)立的線程負(fù)責(zé)把它們發(fā)到 Kafka Broker 上。 Kafka Broker 在收到消息時(shí)會(huì)返回一個(gè)響應(yīng),如果寫入成功,會(huì)返回一個(gè) RecordMetaData 對(duì)象,它包含了主題和分區(qū)信息,以及記錄在分區(qū)里的偏移量,上面兩種的時(shí)間戳類型也會(huì)返回給用戶。如果寫入失敗,會(huì)返回一個(gè)錯(cuò)誤。生產(chǎn)者在收到錯(cuò)誤之后會(huì)嘗試重新發(fā)送消息,幾次之后如果還是失敗的話,就返回錯(cuò)誤消息。
分區(qū)策略Kafka 對(duì)于數(shù)據(jù)的讀寫是以分區(qū)為粒度的,分區(qū)可以分布在多個(gè)主機(jī)(Broker)中,這樣每個(gè)節(jié)點(diǎn)能夠?qū)崿F(xiàn)獨(dú)立的數(shù)據(jù)寫入和讀取,并且能夠通過(guò)增加新的節(jié)點(diǎn)來(lái)增加 Kafka 集群的吞吐量,通過(guò)分區(qū)部署在多個(gè) Broker 來(lái)實(shí)現(xiàn)負(fù)載均衡的效果,下面我們看看數(shù)據(jù)如何選擇分區(qū)。 方式1:順序輪詢順序分配,消息是均勻的分配給每個(gè) partition,即每個(gè)分區(qū)存儲(chǔ)一次消息,見(jiàn)下圖。輪訓(xùn)策略是 Kafka Producer 提供的默認(rèn)策略,如果你不使用指定的輪訓(xùn)策略的話,Kafka 默認(rèn)會(huì)使用順序輪訓(xùn)策略的方式。 ![]() 方式2:隨機(jī)輪詢本質(zhì)上看隨機(jī)策略也是力求將數(shù)據(jù)均勻地打散到各個(gè)分區(qū),但從實(shí)際表現(xiàn)來(lái)看,它要遜于輪詢策略,所以如果追求數(shù)據(jù)的均勻分布,還是使用輪詢策略比較好。事實(shí)上,隨機(jī)策略是老版本生產(chǎn)者使用的分區(qū)策略,在新版本中已經(jīng)改為輪詢了。 ![]() 方式3:key hash這個(gè)策略也叫做 key-ordering 策略,Kafka 中每條消息都會(huì)有自己的key,一旦消息被定義了 Key,那么你就可以保證同一個(gè) Key 的所有消息都進(jìn)入到相同的分區(qū)里面,由于每個(gè)分區(qū)下的消息處理都是有順序的,故這個(gè)策略被稱為按消息鍵保序策略,如下圖所示 ![]() 消費(fèi)者消費(fèi)者群組應(yīng)用程序使用 KafkaConsumer 從 Kafka 中訂閱主題并接收來(lái)自這些主題的消息,然后再把他們保存起來(lái)。應(yīng)用程序首先需要?jiǎng)?chuàng)建一個(gè) KafkaConsumer 對(duì)象,訂閱主題并開(kāi)始接受消息,驗(yàn)證消息并保存結(jié)果。一段時(shí)間后,生產(chǎn)者往主題寫入的速度超過(guò)了應(yīng)用程序驗(yàn)證數(shù)據(jù)的速度,這時(shí)候該如何處理?如果只使用單個(gè)消費(fèi)者的話,應(yīng)用程序會(huì)跟不上消息生成的速度,就像多個(gè)生產(chǎn)者像相同的主題寫入消息一樣,這時(shí)候就需要多個(gè)消費(fèi)者共同參與消費(fèi)主題中的消息,對(duì)消息進(jìn)行分流處理。Kafka 消費(fèi)者從屬于消費(fèi)者群組。一個(gè)群組中的消費(fèi)者訂閱的都是相同的主題,每個(gè)消費(fèi)者接收主題一部分分區(qū)的消息。下面是一個(gè) Kafka 分區(qū)消費(fèi)示意圖。 ![]() 上圖中的主題 T1 有四個(gè)分區(qū),分別是分區(qū)0、分區(qū)1、分區(qū)2、分區(qū)3,我們創(chuàng)建一個(gè)消費(fèi)者群組1,消費(fèi)者群組中只有一個(gè)消費(fèi)者,它訂閱主題T1,接收到 T1 中的全部消息。由于一個(gè)消費(fèi)者處理四個(gè)生產(chǎn)者發(fā)送到分區(qū)的消息,壓力有些大,需要幫手來(lái)幫忙分擔(dān)任務(wù),于是就演變?yōu)橄聢D ![]() 這樣一來(lái),消費(fèi)者的消費(fèi)能力就大大提高了,但是在某些環(huán)境下比如用戶產(chǎn)生消息特別多的時(shí)候,生產(chǎn)者產(chǎn)生的消息仍舊讓消費(fèi)者吃不消,那就繼續(xù)增加消費(fèi)者。 ![]() 如上圖所示,每個(gè)分區(qū)所產(chǎn)生的消息能夠被每個(gè)消費(fèi)者群組中的消費(fèi)者消費(fèi),如果向消費(fèi)者群組中增加更多的消費(fèi)者,那么多余的消費(fèi)者將會(huì)閑置,如下圖所示。 ![]() 向群組中增加消費(fèi)者是橫向伸縮消費(fèi)能力的主要方式??偠灾?,我們可以通過(guò)增加消費(fèi)組的消費(fèi)者來(lái)進(jìn)行水平擴(kuò)展提升消費(fèi)能力。這也是為什么建議創(chuàng)建主題時(shí)使用比較多的分區(qū)數(shù),這樣可以在消費(fèi)負(fù)載高的情況下增加消費(fèi)者來(lái)提升性能。另外,消費(fèi)者的數(shù)量不應(yīng)該比分區(qū)數(shù)多,因?yàn)槎喑鰜?lái)的消費(fèi)者是空閑的,沒(méi)有任何幫助。 Kafka 一個(gè)很重要的特性就是,只需寫入一次消息,可以支持任意多的應(yīng)用讀取這個(gè)消息。換句話說(shuō),每個(gè)應(yīng)用都可以讀到全量的消息。為了使得每個(gè)應(yīng)用都能讀到全量消息,應(yīng)用需要有不同的消費(fèi)組。對(duì)于上面的例子,假如我們新增了一個(gè)新的消費(fèi)組 G2,而這個(gè)消費(fèi)組有兩個(gè)消費(fèi)者,那么就演變?yōu)橄聢D這樣。在這個(gè)場(chǎng)景中,消費(fèi)組 G1 和消費(fèi)組 G2 都能收到 T1 主題的全量消息,在邏輯意義上來(lái)說(shuō)它們屬于不同的應(yīng)用。 ![]()
消費(fèi)者重平衡我們從上面的消費(fèi)者演變圖中可以知道這么一個(gè)過(guò)程:最初是一個(gè)消費(fèi)者訂閱一個(gè)主題并消費(fèi)其全部分區(qū)的消息,后來(lái)有一個(gè)消費(fèi)者加入群組,隨后又有更多的消費(fèi)者加入群組,而新加入的消費(fèi)者實(shí)例分?jǐn)偭俗畛跸M(fèi)者的部分消息,這種把分區(qū)的所有權(quán)通過(guò)一個(gè)消費(fèi)者轉(zhuǎn)到其他消費(fèi)者的行為稱為重平衡,英文名也叫做 Rebalance 。如下圖所示。 ![]() 重平衡非常重要,它為消費(fèi)者群組帶來(lái)了高可用性 和 伸縮性,我們可以放心的添加消費(fèi)者或移除消費(fèi)者,不過(guò)在正常情況下我們并不希望發(fā)生這樣的行為。在重平衡期間,消費(fèi)者無(wú)法讀取消息,造成整個(gè)消費(fèi)者組在重平衡的期間都不可用。另外,當(dāng)分區(qū)被重新分配給另一個(gè)消費(fèi)者時(shí),消息當(dāng)前的讀取狀態(tài)會(huì)丟失,它有可能還需要去刷新緩存,在它重新恢復(fù)狀態(tài)之前會(huì)拖慢應(yīng)用程序。 消費(fèi)者通過(guò)向組織協(xié)調(diào)者(Kafka Broker)發(fā)送心跳來(lái)維護(hù)自己是消費(fèi)者組的一員并確認(rèn)其擁有的分區(qū)。對(duì)于不同不的消費(fèi)群體來(lái)說(shuō),其組織協(xié)調(diào)者可以是不同的。只要消費(fèi)者定期發(fā)送心跳,就會(huì)認(rèn)為消費(fèi)者是存活的并處理其分區(qū)中的消息。當(dāng)消費(fèi)者檢索記錄或者提交它所消費(fèi)的記錄時(shí)就會(huì)發(fā)送心跳。如果過(guò)了一段時(shí)間 Kafka 停止發(fā)送心跳了,會(huì)話(Session)就會(huì)過(guò)期,組織協(xié)調(diào)者就會(huì)認(rèn)為這個(gè) Consumer 已經(jīng)死亡,就會(huì)觸發(fā)一次重平衡。如果消費(fèi)者宕機(jī)并且停止發(fā)送消息,組織協(xié)調(diào)者會(huì)等待幾秒鐘,確認(rèn)它死亡了才會(huì)觸發(fā)重平衡。在這段時(shí)間里,死亡的消費(fèi)者將不處理任何消息。在清理消費(fèi)者時(shí),消費(fèi)者將通知協(xié)調(diào)者它要離開(kāi)群組,組織協(xié)調(diào)者會(huì)觸發(fā)一次重平衡,盡量降低處理停頓。 重平衡是一把雙刃劍,它為消費(fèi)者群組帶來(lái)高可用性和伸縮性的同時(shí),還有有一些明顯的缺點(diǎn)(bug),而這些 bug 到現(xiàn)在社區(qū)還無(wú)法修改。重平衡的過(guò)程對(duì)消費(fèi)者組有極大的影響。因?yàn)槊看沃仄胶膺^(guò)程中都會(huì)導(dǎo)致萬(wàn)物靜止,參考 JVM 中的垃圾回收機(jī)制,也就是 Stop The World ,STW。也就是說(shuō),在重平衡期間,消費(fèi)者組中的消費(fèi)者實(shí)例都會(huì)停止消費(fèi),等待重平衡的完成。而且重平衡這個(gè)過(guò)程很慢... 特性分析這里才是內(nèi)容的重點(diǎn),不僅需要知道Kafka的特性,還需要知道支持這些特性的原因:
RocketMQ![]() RocketMQ是一個(gè)純Java、分布式、隊(duì)列模型的開(kāi)源消息中間件,前身是MetaQ,是阿里參考Kafka特點(diǎn)研發(fā)的一個(gè)隊(duì)列模型的消息中間件,后開(kāi)源給apache基金會(huì)成為了apache的頂級(jí)開(kāi)源項(xiàng)目,具有高性能、高可靠、高實(shí)時(shí)、分布式特點(diǎn)。 基本概念先對(duì)常用的詞匯有個(gè)基本認(rèn)識(shí),相關(guān)詞匯后面會(huì)再詳細(xì)介紹:
消息模型RockerMQ 中的消息模型就是按照主題模型所實(shí)現(xiàn)的,在主題模型中,消息的生產(chǎn)者稱為發(fā)布者(Publisher),消息的消費(fèi)者稱為訂閱者(Subscriber),存放消息的容器稱為主題(Topic)。RocketMQ 中的主題模型到底是如何實(shí)現(xiàn)的呢? ![]() 我們可以看到在整個(gè)圖中有 Producer Group、Topic、Consumer Group 三個(gè)角色,你可以看到圖中生產(chǎn)者組中的生產(chǎn)者會(huì)向主題發(fā)送消息,而主題中存在多個(gè)隊(duì)列,生產(chǎn)者每次生產(chǎn)消息之后是指定主題中的某個(gè)隊(duì)列發(fā)送消息的。 每個(gè)主題中都有多個(gè)隊(duì)列(這里還不涉及到 Broker),集群消費(fèi)模式下,一個(gè)消費(fèi)者集群多臺(tái)機(jī)器共同消費(fèi)一個(gè) topic 的多個(gè)隊(duì)列,一個(gè)隊(duì)列只會(huì)被一個(gè)消費(fèi)者消費(fèi)。如果某個(gè)消費(fèi)者掛掉,分組內(nèi)其它消費(fèi)者會(huì)接替掛掉的消費(fèi)者繼續(xù)消費(fèi)。就像上圖中 Consumer1 和 Consumer2 分別對(duì)應(yīng)著兩個(gè)隊(duì)列,而 Consuer3 是沒(méi)有隊(duì)列對(duì)應(yīng)的,所以一般來(lái)講要控制消費(fèi)者組中的消費(fèi)者個(gè)數(shù)和主題中隊(duì)列個(gè)數(shù)相同。這個(gè)簡(jiǎn)直和kafak一毛一樣??! 當(dāng)然也可以消費(fèi)者個(gè)數(shù)小于隊(duì)列個(gè)數(shù),只不過(guò)不太建議。如下圖: ![]() 每個(gè)消費(fèi)組在每個(gè)隊(duì)列上維護(hù)一個(gè)消費(fèi)位置,為什么呢?因?yàn)槲覀儎倓偖嫷膬H僅是一個(gè)消費(fèi)者組,我們知道在發(fā)布訂閱模式中一般會(huì)涉及到多個(gè)消費(fèi)者組,而每個(gè)消費(fèi)者組在每個(gè)隊(duì)列中的消費(fèi)位置都是不同的。如果此時(shí)有多個(gè)消費(fèi)者組,那么消息被一個(gè)消費(fèi)者組消費(fèi)完之后是不會(huì)刪除的(因?yàn)槠渌M(fèi)者組也需要呀),它僅僅是為每個(gè)消費(fèi)者組維護(hù)一個(gè)消費(fèi)位移(offset),每次消費(fèi)者組消費(fèi)完會(huì)返回一個(gè)成功的響應(yīng),然后隊(duì)列再把維護(hù)的消費(fèi)位移加一,這樣就不會(huì)出現(xiàn)剛剛消費(fèi)過(guò)的消息再一次被消費(fèi)了。 ![]() 可能你還有一個(gè)問(wèn)題,為什么一個(gè)主題中需要維護(hù)多個(gè)隊(duì)列?答案是提高并發(fā)能力。的確,每個(gè)主題中只存在一個(gè)隊(duì)列也是可行的。你想一下,如果每個(gè)主題中只存在一個(gè)隊(duì)列,這個(gè)隊(duì)列中也維護(hù)著每個(gè)消費(fèi)者組的消費(fèi)位置,這樣也可以做到發(fā)布訂閱模式。如下圖:
所以總結(jié)來(lái)說(shuō),RocketMQ 通過(guò)使用在一個(gè) Topic 中配置多個(gè)隊(duì)列,并且每個(gè)隊(duì)列維護(hù)每個(gè)消費(fèi)者組的消費(fèi)位置,實(shí)現(xiàn)了主題模式/發(fā)布訂閱模式。 系統(tǒng)架構(gòu)講完了消息模型,我們理解起 RocketMQ 的技術(shù)架構(gòu)起來(lái)就容易多了。RocketMQ 技術(shù)架構(gòu)中有四大角色 NameServer、Broker、Producer、Consumer。這4大角色,已經(jīng)在基本概念中簡(jiǎn)單解釋過(guò),對(duì)于相關(guān)詞匯,這里再重點(diǎn)解釋一下。
![]() 聽(tīng)完了上面的解釋你可能會(huì)覺(jué)得,這玩意好簡(jiǎn)單。不就是這樣的么? ![]() 嗯?你可能會(huì)發(fā)現(xiàn)一個(gè)問(wèn)題,這老家伙 NameServer 干啥用的,這不多余嗎?直接 Producer、Consumer 和 Broker 直接進(jìn)行生產(chǎn)消息,消費(fèi)消息不就好了么?但是,我們上文提到過(guò) Broker 是需要保證高可用的,如果整個(gè)系統(tǒng)僅僅靠著一個(gè) Broker 來(lái)維持的話,那么這個(gè) Broker 的壓力會(huì)不會(huì)很大?所以我們需要使用多個(gè) Broker 來(lái)保證負(fù)載均衡。如果說(shuō),我們的消費(fèi)者和生產(chǎn)者直接和多個(gè) Broker 相連,那么當(dāng) Broker 修改的時(shí)候必定會(huì)牽連著每個(gè)生產(chǎn)者和消費(fèi)者,這樣就會(huì)產(chǎn)生耦合問(wèn)題,而 NameServer 注冊(cè)中心就是用來(lái)解決這個(gè)問(wèn)題的。 當(dāng)然,RocketMQ 中的技術(shù)架構(gòu)肯定不止前面那么簡(jiǎn)單,因?yàn)樯厦鎴D中的四個(gè)角色都是需要做集群的。我給出一張官網(wǎng)的架構(gòu)圖,大家嘗試?yán)斫庖幌隆?/p> 其實(shí)和我們最開(kāi)始畫的那張乞丐版的架構(gòu)圖也沒(méi)什么區(qū)別,主要是一些細(xì)節(jié)上的差別,聽(tīng)我細(xì)細(xì)道來(lái)。
高級(jí)特性&常見(jiàn)問(wèn)題順序消費(fèi)在上面的技術(shù)架構(gòu)介紹中,我們已經(jīng)知道了 RocketMQ 在主題上是無(wú)序的、它只有在隊(duì)列層面才是保證有序的。這又扯到兩個(gè)概念——普通順序和嚴(yán)格順序。所謂普通順序是指消費(fèi)者通過(guò)同一個(gè)消費(fèi)隊(duì)列收到的消息是有順序的,不同消息隊(duì)列收到的消息則可能是無(wú)順序的。普通順序消息在 Broker 重啟情況下不會(huì)保證消息順序性(短暫時(shí)間)。
那么,我們現(xiàn)在使用了普通順序模式,我們從上面學(xué)習(xí)知道了在 Producer 生產(chǎn)消息的時(shí)候會(huì)進(jìn)行輪詢(取決你的負(fù)載均衡策略)來(lái)向同一主題的不同消息隊(duì)列發(fā)送消息。那么如果此時(shí)我有幾個(gè)消息分別是同一個(gè)訂單的創(chuàng)建、支付、發(fā)貨,在輪詢的策略下這三個(gè)消息會(huì)被發(fā)送到不同隊(duì)列,因?yàn)樵诓煌年?duì)列此時(shí)就無(wú)法使用 RocketMQ 帶來(lái)的隊(duì)列有序特性來(lái)保證消息有序性了。 ![]() 那么,怎么解決呢?其實(shí)很簡(jiǎn)單,我們需要處理的僅僅是將同一語(yǔ)義下的消息放入同一個(gè)隊(duì)列(比如這里是同一個(gè)訂單),那我們就可以使用 Hash 取模法來(lái)保證同一個(gè)訂單在同一個(gè)隊(duì)列中就行了。 重復(fù)消費(fèi)就兩個(gè)字——冪等。在編程中一個(gè)冪等操作的特點(diǎn)是其任意多次執(zhí)行所產(chǎn)生的影響均與一次執(zhí)行的影響相同。比如說(shuō),這個(gè)時(shí)候我們有一個(gè)訂單的處理積分的系統(tǒng),每當(dāng)來(lái)一個(gè)消息的時(shí)候它就負(fù)責(zé)為創(chuàng)建這個(gè)訂單的用戶的積分加上相應(yīng)的數(shù)值。可是有一次,消息隊(duì)列發(fā)送給訂單系統(tǒng) FrancisQ 的訂單信息,其要求是給 FrancisQ 的積分加上 500。但是積分系統(tǒng)在收到 FrancisQ 的訂單信息處理完成之后返回給消息隊(duì)列處理成功的信息的時(shí)候出現(xiàn)了網(wǎng)絡(luò)波動(dòng)(當(dāng)然還有很多種情況,比如 Broker 意外重啟等等),這條回應(yīng)沒(méi)有發(fā)送成功。 那么,消息隊(duì)列沒(méi)收到積分系統(tǒng)的回應(yīng)會(huì)不會(huì)嘗試重發(fā)這個(gè)消息?問(wèn)題就來(lái)了,我再發(fā)這個(gè)消息,萬(wàn)一它又給 FrancisQ 的賬戶加上 500 積分怎么辦呢?所以我們需要給我們的消費(fèi)者實(shí)現(xiàn)冪等,也就是對(duì)同一個(gè)消息的處理結(jié)果,執(zhí)行多少次都不變。 那么如何給業(yè)務(wù)實(shí)現(xiàn)冪等呢?這個(gè)還是需要結(jié)合具體的業(yè)務(wù)的。你可以使用寫入 Redis 來(lái)保證,因?yàn)?Redis 的 key 和 value 就是天然支持冪等的。當(dāng)然還有使用數(shù)據(jù)庫(kù)插入法,基于數(shù)據(jù)庫(kù)的唯一鍵來(lái)保證重復(fù)數(shù)據(jù)不會(huì)被插入多條。不過(guò)最主要的還是需要根據(jù)特定場(chǎng)景使用特定的解決方案,你要知道你的消息消費(fèi)是否是完全不可重復(fù)消費(fèi)還是可以忍受重復(fù)消費(fèi)的,然后再選擇強(qiáng)校驗(yàn)和弱校驗(yàn)的方式。畢竟在 CS 領(lǐng)域還是很少有技術(shù)銀彈的說(shuō)法。 簡(jiǎn)單了來(lái)說(shuō),冪等的校驗(yàn),還是需要業(yè)務(wù)方來(lái)支持,因?yàn)槟憬鉀Q不了網(wǎng)絡(luò)抖動(dòng)問(wèn)題哈~~ 分布式事務(wù)如何解釋分布式事務(wù)呢?事務(wù)大家都知道吧?要么都執(zhí)行要么都不執(zhí)行。在同一個(gè)系統(tǒng)中我們可以輕松地實(shí)現(xiàn)事務(wù),但是在分布式架構(gòu)中,我們有很多服務(wù)是部署在不同系統(tǒng)之間的,而不同服務(wù)之間又需要進(jìn)行調(diào)用。比如此時(shí)我下訂單然后增加積分,如果保證不了分布式事務(wù)的話,就會(huì)出現(xiàn)A系統(tǒng)下了訂單,但是B系統(tǒng)增加積分失敗或者A系統(tǒng)沒(méi)有下訂單,B系統(tǒng)卻增加了積分。前者對(duì)用戶不友好,后者對(duì)運(yùn)營(yíng)商不利,這是我們都不愿意見(jiàn)到的。那么,如何去解決這個(gè)問(wèn)題呢? 如今比較常見(jiàn)的分布式事務(wù)實(shí)現(xiàn)有 2PC、TCC 和事務(wù)消息(half 半消息機(jī)制)。每一種實(shí)現(xiàn)都有其特定的使用場(chǎng)景,但是也有各自的問(wèn)題,都不是完美的解決方案。在 RocketMQ 中使用的是事務(wù)消息加上事務(wù)反查機(jī)制來(lái)解決分布式事務(wù)問(wèn)題的。
消息堆積消息中間件的主要功能是異步解耦,還有個(gè)重要功能是擋住前端的數(shù)據(jù)洪峰,保證后端系統(tǒng)的穩(wěn)定性,這就要求消息中間件具有一定的消息堆積能力,消息堆積分以下兩種情況:
評(píng)估消息堆積能力主要有以下四點(diǎn):
簡(jiǎn)單來(lái)說(shuō),RocketMQ支持大量消息堆積,消息會(huì)存在內(nèi)存,超出內(nèi)存的消息會(huì)持久化到磁盤中。 定時(shí)消息定時(shí)消息是指消息發(fā)到Broker后,不能立刻被Consumer消費(fèi),要到特定的時(shí)間點(diǎn)或者等待特定的時(shí)間后才能被消費(fèi)。如果要支持任意的時(shí)間精度,在Broker層面,必須要做消息排序,如果再涉及到持久化,那么消息排序要不可避免的產(chǎn)生巨大性能開(kāi)銷。RocketMQ支持定時(shí)消息,但是不支持任意時(shí)間精度,支持特定的level,例如定時(shí)5s,10s,1m等。 簡(jiǎn)單來(lái)說(shuō),RocketMQ支持定時(shí)消息,但是只支持固定時(shí)間,不支持任意精度時(shí)間。 回溯消費(fèi)同步刷盤和異步刷盤上面我講了那么多的 RocketMQ 的架構(gòu)和設(shè)計(jì)原理,你有沒(méi)有好奇,在 Topic 中的隊(duì)列是以什么樣的形式存在的?隊(duì)列中的消息又是如何進(jìn)行存儲(chǔ)持久化的呢?我在上文中提到的同步刷盤和異步刷盤又是什么呢?它們會(huì)給持久化帶來(lái)什么樣的影響呢?下面我將給你們一一解釋。 ![]() 如上圖所示,在同步刷盤中需要等待一個(gè)刷盤成功的 ACK,同步刷盤對(duì) MQ 消息可靠性來(lái)說(shuō)是一種不錯(cuò)的保障,但是性能上會(huì)有較大影響,一般地適用于金融等特定業(yè)務(wù)場(chǎng)景。而異步刷盤往往是開(kāi)啟一個(gè)線程去異步地執(zhí)行刷盤操作。消息刷盤采用后臺(tái)異步線程提交的方式進(jìn)行,降低了讀寫延遲,提高了 MQ 的性能和吞吐量,一般適用于如發(fā)驗(yàn)證碼等對(duì)于消息保證要求不太高的業(yè)務(wù)場(chǎng)景。一般地,異步刷盤只有在 Broker 意外宕機(jī)的時(shí)候會(huì)丟失部分?jǐn)?shù)據(jù),你可以設(shè)置 Broker 的參數(shù) FlushDiskType 來(lái)調(diào)整你的刷盤策略(ASYNC_FLUSH 或者 SYNC_FLUSH)。 簡(jiǎn)單來(lái)說(shuō),同步刷盤是刷盤后請(qǐng)求再返回,異步刷盤是直接返回請(qǐng)求,再去慢慢刷盤,可能會(huì)導(dǎo)致數(shù)據(jù)丟失。 同步復(fù)制和異步復(fù)制上面的同步刷盤和異步刷盤是在單個(gè)結(jié)點(diǎn)層面的,而同步復(fù)制和異步復(fù)制主要是指的 Borker 主從模式下,主節(jié)點(diǎn)返回消息給客戶端的時(shí)候是否需要同步從節(jié)點(diǎn)。
這個(gè)機(jī)制,感覺(jué)就像大眾化的版本,基本思路都一樣,為了保證數(shù)據(jù)可用性,我還是推薦同步復(fù)制,當(dāng)大多數(shù)節(jié)點(diǎn)復(fù)制成功,就認(rèn)為復(fù)制完畢,和ETCD的Raft協(xié)議的日志同步原理一樣。 容錯(cuò)機(jī)制在實(shí)際使用RocketMQ的時(shí)候我們并不能保證每次發(fā)送的消息都剛好能被消費(fèi)者一次性正常消費(fèi)成功,可能會(huì)存在需要多次消費(fèi)才能成功或者一直消費(fèi)失敗的情況,那作為發(fā)送者該做如何處理呢? RocketMQ提供了ack機(jī)制,以保證消息能夠被正常消費(fèi)。發(fā)送者為了保證消息肯定消費(fèi)成功,只有使用方明確表示消費(fèi)成功,RocketMQ才會(huì)認(rèn)為消息消費(fèi)成功。中途斷電,拋出異常等都不會(huì)認(rèn)為成功——即都會(huì)重新投遞。當(dāng)然,如果消費(fèi)者不告知發(fā)送者我這邊消費(fèi)信息異常,那么發(fā)送者是不會(huì)知道的,所以消費(fèi)者在設(shè)置監(jiān)聽(tīng)的時(shí)候需要給個(gè)回調(diào)。 為了保證消息是肯定被至少消費(fèi)成功一次,RocketMQ會(huì)把這批消息重發(fā)回Broker(topic不是原topic而是這個(gè)消費(fèi)租的RETRY topic),在延遲的某個(gè)時(shí)間點(diǎn)(默認(rèn)是10秒,業(yè)務(wù)可設(shè)置)后,再次投遞到這個(gè)ConsumerGroup。而如果一直這樣重復(fù)消費(fèi)都持續(xù)失敗到一定次數(shù)(默認(rèn)16次),就會(huì)投遞到DLQ死信隊(duì)列。應(yīng)用可以監(jiān)控死信隊(duì)列來(lái)做人工干預(yù)。 簡(jiǎn)單來(lái)說(shuō),通過(guò)ACK保證消息一定能正常消費(fèi),對(duì)于異常消息,會(huì)重新放回Broker,但是這樣就會(huì)打亂消息的順序,所以容錯(cuò)機(jī)制和消息嚴(yán)格順序,魚和熊掌不可兼得。 特性分析這里才是內(nèi)容的重點(diǎn),不僅需要知道RocketMQ的特性,還需要知道支持這些特性的原因:
RabbitMQ![]() 我們也不能天天去背八股,還需要實(shí)踐,RabbitMQ的實(shí)操實(shí)例,直接看這篇《入門RabbitMQ,這一篇絕對(duì)夠!》 |
|
|
來(lái)自: 昵稱10087950 > 《中間件》