RabbitMQ、Kafka、RocketMQ和ActiveMQ,肝了我一個月,原理是什么,如何選型,本文會告訴你答案。
消息隊列中間件重要嗎?面試必問問題之一,你說重不重要。我有時會問同事,為啥你用RabbitMQ,不用Kafka,或者RocketMQ呢,他給我的回答“因為公司用的就是這個,大家都這么用”,如果你去面試,直接就被Pass,今天這篇文章,告訴你如何回答。
這篇文章純理論,主要整理網(wǎng)絡(luò)資料,肝了我整整一個月!文章依然延續(xù)上幾篇的風格,很長,長到我只整理排版,手都整麻了。全文2.5萬字,建議先收藏,后續(xù)面試、或者技術(shù)選型,再拿出來喵喵,不BB,上思維導(dǎo)圖!
消息隊列 消息隊列模式 消息隊列目前主要2種模式,分別為“點對點模式”和“發(fā)布/訂閱模式”。
點對點模式 一個具體的消息只能由一個消費者消費。多個生產(chǎn)者可以向同一個消息隊列發(fā)送消息;但是,一個消息在被一個消息者處理的時候,這個消息在隊列上會被鎖住或者被移除并且其他消費者無法處理該消息。需要額外注意的是,如果消費者處理一個消息失敗了,消息系統(tǒng)一般會把這個消息放回隊列,這樣其他消費者可以繼續(xù)處理。
發(fā)布/訂閱模式 單個消息可以被多個訂閱者并發(fā)的獲取和處理。一般來說,訂閱有兩種類型:
臨時(ephemeral)訂閱,這種訂閱只有在消費者啟動并且運行的時候才存在。一旦消費者退出,相應(yīng)的訂閱以及尚未處理的消息就會丟失。 持久(durable)訂閱,這種訂閱會一直存在,除非主動去刪除。消費者退出后,消息系統(tǒng)會繼續(xù)維護該訂閱,并且后續(xù)消息可以被繼續(xù)處理。 衡量標準 對消息隊列進行技術(shù)選型時,需要通過以下指標衡量你所選擇的消息隊列,是否可以滿足你的需求:
消息順序:發(fā)送到隊列的消息,消費時是否可以保證消費的順序,比如A先下單,B后下單,應(yīng)該是A先去扣庫存,B再去扣,順序不能反。 消息路由:根據(jù)路由規(guī)則,只訂閱匹配路由規(guī)則的消息,比如有A/B兩者規(guī)則的消息,消費者可以只訂閱A消息,B消息不會消費。 消息可靠性:是否會存在丟消息的情況,比如有A/B兩個消息,最后只有B消息能消費,A消息丟失。 消息時序:主要包括“消息存活時間”和“延遲/預(yù)定的消息”,“消息存活時間”表示生產(chǎn)者可以對消息設(shè)置TTL,如果超過該TTL,消息會自動消失;“延遲/預(yù)定的消息”指的是可以延遲或者預(yù)訂消費消息,比如延時5分鐘,那么消息會5分鐘后才能讓消費者消費,時間未到的話,是不能消費的。 消息留存:消息消費成功后,是否還會繼續(xù)保留在消息隊列。 容錯性:當一條消息消費失敗后,是否有一些機制,保證這條消息是一種能成功,比如異步第三方退款消息,需要保證這條消息消費掉,才能確定給用戶退款成功,所以必須保證這條消息消費成功的準確性。 伸縮:當消息隊列性能有問題,比如消費太慢,是否可以快速支持庫容;當消費隊列過多,浪費系統(tǒng)資源,是否可以支持縮容。 消息隊列比較 下圖是從網(wǎng)上摘抄過來的,可以看到主流MQ的對比:
下面簡單介紹常用的消息隊列:
Kafka:Apache Kafka它最初由LinkedIn公司基于獨特的設(shè)計實現(xiàn)為一個分布式的提交日志系統(tǒng)( a distributed commit log),之后成為Apache項目的一部分。號稱大數(shù)據(jù)的殺手锏,談到大數(shù)據(jù)領(lǐng)域內(nèi)的消息傳輸,則繞不開Kafka,這款為大數(shù)據(jù)而生的消息中間件,以其百萬級TPS的吞吐量名聲大噪,迅速成為大數(shù)據(jù)領(lǐng)域的寵兒,在數(shù)據(jù)采集、傳輸、存儲的過程中發(fā)揮著舉足輕重的作用。 RabbitMQ:RabbitMQ 2007年發(fā)布,是使用Erlang語言開發(fā)的開源消息隊列系統(tǒng),基于AMQP協(xié)議來實現(xiàn)。AMQP的主要特征是面向消息、隊列、路由(包括點對點和發(fā)布/訂閱)、可靠性、安全。AMQP協(xié)議更多用在企業(yè)系統(tǒng)內(nèi),對數(shù)據(jù)一致性、穩(wěn)定性和可靠性要求很高的場景,對性能和吞吐量的要求還在其次。 RocketMQ:是阿里開源的消息中間件,它是純Java開發(fā),具有高吞吐量、高可用性、適合大規(guī)模分布式系統(tǒng)應(yīng)用的特點。RocketMQ思路起源于Kafka,但并不是Kafka的一個Copy,它對消息的可靠傳輸及事務(wù)性做了優(yōu)化,目前在阿里集團被廣泛應(yīng)用于交易、充值、流計算、消息推送、日志流式處理、binglog分發(fā)等場景。 ActiveMQ:是Apache出品,最流行的,能力強勁的開源消息總線。官方社區(qū)現(xiàn)在對ActiveMQ 5.x維護越來越少,較少在大規(guī)模吞吐的場景中使用,所以該消息隊列也不是我們文章中重點討論的內(nèi)容。 優(yōu)缺點 Kafka 優(yōu)點:
高吞吐、低延遲:kakfa 最大的特點就是收發(fā)消息非??欤琸afka 每秒可以處理幾十萬條消息,它的最低延遲只有幾毫秒; 高伸縮性:每個主題(topic) 包含多個分區(qū)(partition),主題中的分區(qū)可以分布在不同的主機(broker)中; 持久性、可靠性:Kafka 能夠允許數(shù)據(jù)的持久化存儲,消息被持久化到磁盤,并支持數(shù)據(jù)備份防止數(shù)據(jù)丟失,Kafka 底層的數(shù)據(jù)存儲是基于 Zookeeper 存儲的,Zookeeper 我們知道它的數(shù)據(jù)能夠持久存儲; 容錯性:非常高,kafka是分布式的,一個數(shù)據(jù)多個副本,某個節(jié)點宕機,Kafka 集群能夠正常工作; 消息有序:消費者采用Pull方式獲取消息,消息有序,通過控制能夠保證所有消息被消費且僅被消費一次; 有優(yōu)秀的第三方Kafka Web管理界面Kafka-Manager,在日志領(lǐng)域比較成熟,被多家公司和多個開源項目使用; 功能支持:功能較為簡單,主要支持簡單的MQ功能,在大數(shù)據(jù)領(lǐng)域的實時計算以及日志采集被大規(guī)模使用。 缺點:
Kafka單機超過64個隊列/分區(qū),Load會發(fā)生明顯的飆高現(xiàn)象,隊列越多,load越高,發(fā)送消息響應(yīng)時間變長; 支持消息順序,但是一臺代理宕機后,就會產(chǎn)生消息亂序; 總結(jié):
Kafka主要特點是基于Pull的模式來處理消息消費,追求高吞吐量,一開始的目的就是用于日志收集和傳輸,適合產(chǎn)生大量數(shù)據(jù)的互聯(lián)網(wǎng)服務(wù)的數(shù)據(jù)收集業(yè)務(wù)。 大型公司建議可以選用,如果有日志采集功能,肯定是首選kafka。 RabbitMQ 優(yōu)點:
異步消息傳遞:支持多種消息協(xié)議,消息隊列,傳送確認,靈活的路由到隊列,多種交換類型; 支持幾乎所有最受歡迎的編程語言:Java,C,C ++,C#,Ruby,Perl,Python,PHP等等; 可以部署為高可用性和吞吐量的集群;,跨多個可用區(qū)域和區(qū)域進行聯(lián)合; 可插入的身份驗證,授權(quán),支持TLS和LDAP; 提供了許多插件,來從多方面進行擴展,也可以編寫自己的插件; 提供了一個易用的用戶界面,使得用戶可以監(jiān)控和管理消息Broker,社區(qū)活躍度高。 缺點:
erlang開發(fā),很難去看懂源碼,基本職能依賴于開源社區(qū)的快速維護和修復(fù)bug,不利于做二次開發(fā)和維護; RabbitMQ確實吞吐量會低一些,這是因為他做的實現(xiàn)機制比較重; 需要學習比較復(fù)雜的接口和協(xié)議,學習和維護成本較高。 總結(jié):
結(jié)合erlang語言本身的并發(fā)優(yōu)勢,性能較好,社區(qū)活躍度也比較高,但是不利于做二次開發(fā)和維護。不過RabbitMQ的社區(qū)十分活躍,可以解決開發(fā)過程中遇到的bug。 如果你的數(shù)據(jù)量沒有那么大,小公司優(yōu)先選擇功能比較完備的RabbitMQ。 RocketMQ 優(yōu)點:
支持發(fā)布/訂閱(Pub/Sub)和點對點(P2P)消息模型; 在一個隊列中可靠的先進先出(FIFO)和嚴格的順序傳遞; 支持多種消息協(xié)議,如 JMS、MQTT 等; 靈活的分布式橫向擴展部署架構(gòu),滿足至少一次消息傳遞語義; 提供 docker 鏡像用于隔離測試和云集群部署; 提供配置、指標和監(jiān)控等功能豐富的 Dashboard。 缺點:
支持的客戶端語言不多,目前是java及c++,其中c++不成熟 沒有在 mq 核心中去實現(xiàn)JMS等接口,有些系統(tǒng)要遷移需要修改大量代碼 總結(jié):
天生為金融互聯(lián)網(wǎng)領(lǐng)域而生,對于可靠性要求很高的場景,尤其是電商里面的訂單扣款,以及業(yè)務(wù)削峰,在大量交易涌入時,后端可能無法及時處理的情況。 RoketMQ在穩(wěn)定性上可能更值得信賴,這些業(yè)務(wù)場景在阿里雙11已經(jīng)經(jīng)歷了多次考驗,如果你的業(yè)務(wù)有上述并發(fā)場景,建議可以選擇RocketMQ。 ActiveMQ 優(yōu)點
支持來自Java,C,C ++,C#,Ruby,Perl,Python,PHP的各種跨語言客戶端和協(xié)議; 完全支持JMS客戶端和Message Broker中的企業(yè)集成模式; 支持許多高級功能,如消息組,虛擬目標,通配符和復(fù)合目標; 完全支持JMS 1.1和J2EE 1.4,支持瞬態(tài),持久,事務(wù)和XA消息; Spring支持,以便ActiveMQ可以輕松嵌入到Spring應(yīng)用程序中,并使用Spring的XML配置機制進行配置; 專為高性能集群,客戶端 - 服務(wù)器,基于對等的通信而設(shè)計; CXF和Axis支持,以便ActiveMQ可以輕松地放入這些Web服務(wù)堆棧中以提供可靠的消息傳遞; 可以用作內(nèi)存JMS提供程序,非常適合單元測試JMS; 支持可插拔傳輸協(xié)議,例如in-VM,TCP,SSL,NIO,UDP,多播,JGroups和JXTA傳輸; 缺點:
官方社區(qū)現(xiàn)在對ActiveMQ 5.x維護越來越少,較少在大規(guī)模吞吐的場景中使用。 Kafka Kafka 是由 Linkedin 公司開發(fā)的,它是一個分布式的,支持多分區(qū)、多副本,基于 Zookeeper 的分布式消息流平臺,它同時也是一款開源的基于發(fā)布訂閱模式的消息引擎系統(tǒng)。
基本概念 消息:Kafka 中的數(shù)據(jù)單元被稱為消息,也被稱為記錄,可以把它看作數(shù)據(jù)庫表中某一行的記錄。 批次:為了提高效率, 消息會分批次寫入 Kafka,批次就代指的是一組消息。 主題:消息的種類稱為 主題(Topic),可以說一個主題代表了一類消息,相當于是對消息進行分類。主題就像是數(shù)據(jù)庫中的表。 分區(qū):主題可以被分為若干個分區(qū)(partition),同一個主題中的分區(qū)可以不在一個機器上,有可能會部署在多個機器上,由此來實現(xiàn) kafka 的伸縮性,單一主題中的分區(qū)有序,但是無法保證主題中所有的分區(qū)有序。 生產(chǎn)者:向主題發(fā)布消息的客戶端應(yīng)用程序稱為生產(chǎn)者(Producer),生產(chǎn)者用于持續(xù)不斷的向某個主題發(fā)送消息。 消費者:訂閱主題消息的客戶端程序稱為消費者(Consumer),消費者用于處理生產(chǎn)者產(chǎn)生的消息。 消費者群組:生產(chǎn)者與消費者的關(guān)系就如同餐廳中的廚師和顧客之間的關(guān)系一樣,一個廚師對應(yīng)多個顧客,也就是一個生產(chǎn)者對應(yīng)多個消費者,消費者群組(Consumer Group)指的就是由一個或多個消費者組成的群體。 偏移量:偏移量(Consumer Offset)是一種元數(shù)據(jù),它是一個不斷遞增的整數(shù)值,用來記錄消費者發(fā)生重平衡時的位置,以便用來恢復(fù)數(shù)據(jù)。 broker: 一個獨立的 Kafka 服務(wù)器就被稱為 broker,broker 接收來自生產(chǎn)者的消息,為消息設(shè)置偏移量,并提交消息到磁盤保存。 broker 集群:broker 是集群 的組成部分,broker 集群由一個或多個 broker 組成,每個集群都有一個 broker 同時充當了集群控制器的角色(自動從集群的活躍成員中選舉出來)。 副本:Kafka 中消息的備份又叫做 副本(Replica),副本的數(shù)量是可以配置的,Kafka 定義了兩類副本:領(lǐng)導(dǎo)者副本(Leader Replica) 和 追隨者副本(Follower Replica),前者對外提供服務(wù),后者只是被動跟隨。 重平衡:Rebalance。消費者組內(nèi)某個消費者實例掛掉后,其他消費者實例自動重新分配訂閱主題分區(qū)的過程。Rebalance 是 Kafka 消費者端實現(xiàn)高可用的重要手段。 系統(tǒng)架構(gòu) 一個典型的 Kafka 集群中包含若干Producer(可以是web前端產(chǎn)生的Page View,或者是服務(wù)器日志,系統(tǒng)CPU、Memory等),若干broker(Kafka支持水平擴展,一般broker數(shù)量越多,集群吞吐率越高),若干Consumer Group,以及一個Zookeeper集群。Kafka通過Zookeeper管理集群配置,選舉leader,以及在Consumer Group發(fā)生變化時進行rebalance。Producer使用push模式將消息發(fā)布到broker,Consumer使用pull模式從broker訂閱并消費消息。
生產(chǎn)者 數(shù)據(jù)執(zhí)行流程 在 Kafka 中,我們把產(chǎn)生消息的那一方稱為生產(chǎn)者,比如我們經(jīng)?;厝ヌ詫氋徫铮愦蜷_淘寶的那一刻,你的登陸信息,登陸次數(shù)都會作為消息傳輸?shù)?Kafka 后臺,當你瀏覽購物的時候,你的瀏覽信息,你的搜索指數(shù),你的購物愛好都會作為一個個消息傳遞給 Kafka 后臺,然后淘寶會根據(jù)你的愛好做智能推薦,致使你的錢包從來都禁不住誘惑,那么這些生產(chǎn)者產(chǎn)生的消息是怎么傳到 Kafka 應(yīng)用程序的呢?發(fā)送過程是怎么樣的呢?
盡管消息的產(chǎn)生非常簡單,但是消息的發(fā)送過程還是比較復(fù)雜的,如圖:
我們從創(chuàng)建一個ProducerRecord 對象開始,ProducerRecord 是 Kafka 中的一個核心類,它代表了一組 Kafka 需要發(fā)送的 key/value 鍵值對,它由記錄要發(fā)送到的主題名稱(Topic Name),可選的分區(qū)號(Partition Number)以及可選的鍵值對構(gòu)成。
在發(fā)送 ProducerRecord 時,我們需要將鍵值對對象由序列化器轉(zhuǎn)換為字節(jié)數(shù)組,這樣它們才能夠在網(wǎng)絡(luò)上傳輸。然后消息到達了分區(qū)器。如果發(fā)送過程中指定了有效的分區(qū)號,那么在發(fā)送記錄時將使用該分區(qū)。如果發(fā)送過程中未指定分區(qū),則將使用key 的 hash 函數(shù)映射指定一個分區(qū)。如果發(fā)送的過程中既沒有分區(qū)號也沒有,則將以循環(huán)的方式分配一個分區(qū)。選好分區(qū)后,生產(chǎn)者就知道向哪個主題和分區(qū)發(fā)送數(shù)據(jù)了。ProducerRecord 還有關(guān)聯(lián)的時間戳,如果用戶沒有提供時間戳,那么生產(chǎn)者將會在記錄中使用當前的時間作為時間戳。Kafka 最終使用的時間戳取決于 topic 主題配置的時間戳類型。然后,這條消息被存放在一個記錄批次里,這個批次里的所有消息會被發(fā)送到相同的主題和分區(qū)上。由一個獨立的線程負責把它們發(fā)到 Kafka Broker 上。
Kafka Broker 在收到消息時會返回一個響應(yīng),如果寫入成功,會返回一個 RecordMetaData 對象,它包含了主題和分區(qū)信息,以及記錄在分區(qū)里的偏移量,上面兩種的時間戳類型也會返回給用戶。如果寫入失敗,會返回一個錯誤。生產(chǎn)者在收到錯誤之后會嘗試重新發(fā)送消息,幾次之后如果還是失敗的話,就返回錯誤消息。
上面寫的有點多,總結(jié)一下流程:創(chuàng)建對象(主題、分區(qū)、key/value)-> 序列化數(shù)據(jù) -> 到達分區(qū)(可自己指定,也可以通過key hash)-> 放入批次(相同主題和分區(qū)) -> 獨立線程發(fā)送 -> 返回主題/分區(qū)/分區(qū)偏移量/時間戳。
分區(qū)策略 Kafka 對于數(shù)據(jù)的讀寫是以分區(qū)為粒度的,分區(qū)可以分布在多個主機(Broker)中,這樣每個節(jié)點能夠?qū)崿F(xiàn)獨立的數(shù)據(jù)寫入和讀取,并且能夠通過增加新的節(jié)點來增加 Kafka 集群的吞吐量,通過分區(qū)部署在多個 Broker 來實現(xiàn)負載均衡的效果,下面我們看看數(shù)據(jù)如何選擇分區(qū)。
方式1:順序輪詢 順序分配,消息是均勻的分配給每個 partition,即每個分區(qū)存儲一次消息,見下圖。輪訓策略是 Kafka Producer 提供的默認策略,如果你不使用指定的輪訓策略的話,Kafka 默認會使用順序輪訓策略的方式。
方式2:隨機輪詢 本質(zhì)上看隨機策略也是力求將數(shù)據(jù)均勻地打散到各個分區(qū),但從實際表現(xiàn)來看,它要遜于輪詢策略,所以如果追求數(shù)據(jù)的均勻分布,還是使用輪詢策略比較好。事實上,隨機策略是老版本生產(chǎn)者使用的分區(qū)策略,在新版本中已經(jīng)改為輪詢了。
方式3:key hash 這個策略也叫做 key-ordering 策略,Kafka 中每條消息都會有自己的key,一旦消息被定義了 Key,那么你就可以保證同一個 Key 的所有消息都進入到相同的分區(qū)里面,由于每個分區(qū)下的消息處理都是有順序的,故這個策略被稱為按消息鍵保序策略,如下圖所示
消費者 消費者群組 應(yīng)用程序使用 KafkaConsumer 從 Kafka 中訂閱主題并接收來自這些主題的消息,然后再把他們保存起來。應(yīng)用程序首先需要創(chuàng)建一個 KafkaConsumer 對象,訂閱主題并開始接受消息,驗證消息并保存結(jié)果。一段時間后,生產(chǎn)者往主題寫入的速度超過了應(yīng)用程序驗證數(shù)據(jù)的速度,這時候該如何處理?如果只使用單個消費者的話,應(yīng)用程序會跟不上消息生成的速度,就像多個生產(chǎn)者像相同的主題寫入消息一樣,這時候就需要多個消費者共同參與消費主題中的消息,對消息進行分流處理。Kafka 消費者從屬于消費者群組。一個群組中的消費者訂閱的都是相同的主題,每個消費者接收主題一部分分區(qū)的消息。下面是一個 Kafka 分區(qū)消費示意圖。
上圖中的主題 T1 有四個分區(qū),分別是分區(qū)0、分區(qū)1、分區(qū)2、分區(qū)3,我們創(chuàng)建一個消費者群組1,消費者群組中只有一個消費者,它訂閱主題T1,接收到 T1 中的全部消息。由于一個消費者處理四個生產(chǎn)者發(fā)送到分區(qū)的消息,壓力有些大,需要幫手來幫忙分擔任務(wù),于是就演變?yōu)橄聢D
這樣一來,消費者的消費能力就大大提高了,但是在某些環(huán)境下比如用戶產(chǎn)生消息特別多的時候,生產(chǎn)者產(chǎn)生的消息仍舊讓消費者吃不消,那就繼續(xù)增加消費者。
如上圖所示,每個分區(qū)所產(chǎn)生的消息能夠被每個消費者群組中的消費者消費,如果向消費者群組中增加更多的消費者,那么多余的消費者將會閑置,如下圖所示。
向群組中增加消費者是橫向伸縮消費能力的主要方式。總而言之,我們可以通過增加消費組的消費者來進行水平擴展提升消費能力。這也是為什么建議創(chuàng)建主題時使用比較多的分區(qū)數(shù),這樣可以在消費負載高的情況下增加消費者來提升性能。另外,消費者的數(shù)量不應(yīng)該比分區(qū)數(shù)多,因為多出來的消費者是空閑的,沒有任何幫助。
Kafka 一個很重要的特性就是,只需寫入一次消息,可以支持任意多的應(yīng)用讀取這個消息。換句話說,每個應(yīng)用都可以讀到全量的消息。為了使得每個應(yīng)用都能讀到全量消息,應(yīng)用需要有不同的消費組。對于上面的例子,假如我們新增了一個新的消費組 G2,而這個消費組有兩個消費者,那么就演變?yōu)橄聢D這樣。在這個場景中,消費組 G1 和消費組 G2 都能收到 T1 主題的全量消息,在邏輯意義上來說它們屬于不同的應(yīng)用。
總結(jié)起來就是如果應(yīng)用需要讀取全量消息,那么請為該應(yīng)用設(shè)置一個消費組;如果該應(yīng)用消費能力不足,那么可以考慮在這個消費組里增加消費者。
消費者重平衡 我們從上面的消費者演變圖中可以知道這么一個過程:最初是一個消費者訂閱一個主題并消費其全部分區(qū)的消息,后來有一個消費者加入群組,隨后又有更多的消費者加入群組,而新加入的消費者實例分攤了最初消費者的部分消息,這種把分區(qū)的所有權(quán)通過一個消費者轉(zhuǎn)到其他消費者的行為稱為重平衡,英文名也叫做 Rebalance 。如下圖所示。
重平衡非常重要,它為消費者群組帶來了高可用性 和 伸縮性,我們可以放心的添加消費者或移除消費者,不過在正常情況下我們并不希望發(fā)生這樣的行為。在重平衡期間,消費者無法讀取消息,造成整個消費者組在重平衡的期間都不可用。另外,當分區(qū)被重新分配給另一個消費者時,消息當前的讀取狀態(tài)會丟失,它有可能還需要去刷新緩存,在它重新恢復(fù)狀態(tài)之前會拖慢應(yīng)用程序。
消費者通過向組織協(xié)調(diào)者(Kafka Broker)發(fā)送心跳來維護自己是消費者組的一員并確認其擁有的分區(qū)。對于不同不的消費群體來說,其組織協(xié)調(diào)者可以是不同的。只要消費者定期發(fā)送心跳,就會認為消費者是存活的并處理其分區(qū)中的消息。當消費者檢索記錄或者提交它所消費的記錄時就會發(fā)送心跳。如果過了一段時間 Kafka 停止發(fā)送心跳了,會話(Session)就會過期,組織協(xié)調(diào)者就會認為這個 Consumer 已經(jīng)死亡,就會觸發(fā)一次重平衡。如果消費者宕機并且停止發(fā)送消息,組織協(xié)調(diào)者會等待幾秒鐘,確認它死亡了才會觸發(fā)重平衡。在這段時間里,死亡的消費者將不處理任何消息。在清理消費者時,消費者將通知協(xié)調(diào)者它要離開群組,組織協(xié)調(diào)者會觸發(fā)一次重平衡,盡量降低處理停頓。
重平衡是一把雙刃劍,它為消費者群組帶來高可用性和伸縮性的同時,還有有一些明顯的缺點(bug),而這些 bug 到現(xiàn)在社區(qū)還無法修改。重平衡的過程對消費者組有極大的影響。因為每次重平衡過程中都會導(dǎo)致萬物靜止,參考 JVM 中的垃圾回收機制,也就是 Stop The World ,STW。也就是說,在重平衡期間,消費者組中的消費者實例都會停止消費,等待重平衡的完成。而且重平衡這個過程很慢...
特性分析 這里才是內(nèi)容的重點,不僅需要知道Kafka的特性,還需要知道支持這些特性的原因:
消息路由(不支持):Kafka在處理消息之前是不允許消費者過濾一個主題中的消息。一個訂閱的消費者在沒有異常情況下會接受一個分區(qū)中的所有消息。 消息有序(支持):當消費消息時,如果消費失敗,消息不會被放回,所以整個消費過程都是有序進行; 消息時序(不支持):消息直接發(fā)送,不會延遲發(fā)送,或者指定消息的TTL。 容錯處理(集群支持/消息不支持):集群容錯能力高,因為是分布式部署,但是消息容錯處理弱,因為消息消費失敗,需要程序員手動處理,Kafka不支持消息重新進行消費。 伸縮(非常好):通過擴充分區(qū)和消費者數(shù)量,實現(xiàn)分區(qū)擴容,并提升消費速度。 持久化(非常好):數(shù)據(jù)存儲在磁盤,可以隨時訂閱消費,消費完后,數(shù)據(jù)仍然保留。 消息回溯(支持):因為消息支持持久化,就支持回溯,可以理解是附帶的功能。 高吞吐(非常好):因為Kafka內(nèi)部同一個主題包含多個分區(qū),所以實現(xiàn)分布式存儲,然后消費者數(shù)量可以擴充到和分區(qū)數(shù)量一致,保證了Kafka的高吞吐。 RocketMQ RocketMQ是一個純Java、分布式、隊列模型的開源消息中間件,前身是MetaQ,是阿里參考Kafka特點研發(fā)的一個隊列模型的消息中間件,后開源給apache基金會成為了apache的頂級開源項目,具有高性能、高可靠、高實時、分布式特點。
基本概念 先對常用的詞匯有個基本認識,相關(guān)詞匯后面會再詳細介紹:
NameServer:一個功能齊全的服務(wù)器,其角色類似Dubbo中的Zookeeper。 Producer:消息生產(chǎn)者,負責產(chǎn)生消息,一般由業(yè)務(wù)系統(tǒng)負責產(chǎn)生消息。 Consumer:消息消費者,負責消費消息,一般是后臺系統(tǒng)負責異步消費。 Broker:消息中轉(zhuǎn)角色,負責存儲消息,轉(zhuǎn)發(fā)消息。 Message:消息,一條消息必須有一個主題(Topic),主題可以看做是你的信件要郵寄的地址。(一條消息也可以擁有一個可選的標簽(Tag)和額處的鍵值對,它們可以用于設(shè)置一個業(yè)務(wù) Key 并在 Broker 上查找此消息以便在開發(fā)期間查找問題。) Topic:主題,可以看做消息的規(guī)類,它是消息的第一級類型。(比如一個電商系統(tǒng)可以分為:交易消息、物流消息等,一條消息必須有一個 Topic 。Topic 與生產(chǎn)者和消費者的關(guān)系非常松散,一個 Topic 可以有0個、1個、多個生產(chǎn)者向其發(fā)送消息,一個生產(chǎn)者也可以同時向不同的 Topic 發(fā)送消息。一個 Topic 也可以被 0個、1個、多個消費者訂閱。) Tag:子主題,它是消息的第二級類型,用于為用戶提供額外的靈活性。(使用標簽,同一業(yè)務(wù)模塊不同目的的消息就可以用相同 Topic 而不同的 Tag 來標識。比如交易消息又可以分為:交易創(chuàng)建消息、交易完成消息等,一條消息可以沒有 Tag 。標簽有助于保持您的代碼干凈和連貫,并且還可以為 RocketMQ 提供的查詢系統(tǒng)提供幫助。) Group:分組,一個組可以訂閱多個Topic。(分為ProducerGroup,ConsumerGroup,代表某一類的生產(chǎn)者和消費者,一般來說同一個服務(wù)可以作為Group,同一個Group一般來說發(fā)送和消費的消息都是一樣的。) Producer Group:生產(chǎn)者組,代表某一類的生產(chǎn)者,比如我們有多個秒殺系統(tǒng)作為生產(chǎn)者,這多個合在一起就是一個 Producer Group 生產(chǎn)者組,它們一般生產(chǎn)相同的消息。 Consumer Group:消費者組,代表某一類的消費者,比如我們有多個短信系統(tǒng)作為消費者,這多個合在一起就是一個 Consumer Group 消費者組,它們一般消費相同的消息。 Queue:隊列,在Kafka中叫Partition。(每個Queue內(nèi)部是有序的,在RocketMQ中分為讀和寫兩種隊列,一般來說讀寫隊列數(shù)量一致,如果不一致就會出現(xiàn)很多問題。) Message Queue:消息隊列,主題被劃分為一個或多個子主題,即消息隊列。(一個 Topic 下可以設(shè)置多個消息隊列,發(fā)送消息時執(zhí)行該消息的 Topic ,RocketMQ 會輪詢該 Topic 下的所有隊列將消息發(fā)出去。消息的物理管理單位。一個Topic下可以有多個Queue,Queue的引入使得消息的存儲可以分布式集群化,具有了水平擴展能力。) 消息模型 RockerMQ 中的消息模型就是按照主題模型所實現(xiàn)的,在主題模型中,消息的生產(chǎn)者稱為發(fā)布者(Publisher),消息的消費者稱為訂閱者(Subscriber),存放消息的容器稱為主題(Topic)。RocketMQ 中的主題模型到底是如何實現(xiàn)的呢?
我們可以看到在整個圖中有 Producer Group、Topic、Consumer Group 三個角色,你可以看到圖中生產(chǎn)者組中的生產(chǎn)者會向主題發(fā)送消息,而主題中存在多個隊列,生產(chǎn)者每次生產(chǎn)消息之后是指定主題中的某個隊列發(fā)送消息的。
每個主題中都有多個隊列(這里還不涉及到 Broker),集群消費模式下,一個消費者集群多臺機器共同消費一個 topic 的多個隊列,一個隊列只會被一個消費者消費。如果某個消費者掛掉,分組內(nèi)其它消費者會接替掛掉的消費者繼續(xù)消費。就像上圖中 Consumer1 和 Consumer2 分別對應(yīng)著兩個隊列,而 Consuer3 是沒有隊列對應(yīng)的,所以一般來講要控制消費者組中的消費者個數(shù)和主題中隊列個數(shù)相同。這個簡直和kafak一毛一樣?。?/p>
當然也可以消費者個數(shù)小于隊列個數(shù),只不過不太建議。如下圖:
每個消費組在每個隊列上維護一個消費位置,為什么呢?因為我們剛剛畫的僅僅是一個消費者組,我們知道在發(fā)布訂閱模式中一般會涉及到多個消費者組,而每個消費者組在每個隊列中的消費位置都是不同的。如果此時有多個消費者組,那么消息被一個消費者組消費完之后是不會刪除的(因為其它消費者組也需要呀),它僅僅是為每個消費者組維護一個消費位移(offset),每次消費者組消費完會返回一個成功的響應(yīng),然后隊列再把維護的消費位移加一,這樣就不會出現(xiàn)剛剛消費過的消息再一次被消費了。
可能你還有一個問題,為什么一個主題中需要維護多個隊列?答案是提高并發(fā)能力。的確,每個主題中只存在一個隊列也是可行的。你想一下,如果每個主題中只存在一個隊列,這個隊列中也維護著每個消費者組的消費位置,這樣也可以做到發(fā)布訂閱模式。如下圖:
但是,這樣我生產(chǎn)者是不是只能向一個隊列發(fā)送消息?又因為需要維護消費位置所以一個隊列只能對應(yīng)一個消費者組中的消費者,這樣是不是其他的 Consumer 就沒有用武之地了?從這兩個角度來講,并發(fā)度一下子就小了很多。
所以總結(jié)來說,RocketMQ 通過使用在一個 Topic 中配置多個隊列,并且每個隊列維護每個消費者組的消費位置,實現(xiàn)了主題模式/發(fā)布訂閱模式。
系統(tǒng)架構(gòu) 講完了消息模型,我們理解起 RocketMQ 的技術(shù)架構(gòu)起來就容易多了。RocketMQ 技術(shù)架構(gòu)中有四大角色 NameServer、Broker、Producer、Consumer。這4大角色,已經(jīng)在基本概念中簡單解釋過,對于相關(guān)詞匯,這里再重點解釋一下。
Broker:主要負責消息的存儲、投遞和查詢以及服務(wù)高可用保證。說白了就是消息隊列服務(wù)器嘛,生產(chǎn)者生產(chǎn)消息到 Broker,消費者從 Broker 拉取消息并消費。這里,我還得普及一下關(guān)于 Broker、Topic 和隊列的關(guān)系。上面我講解了 Topic 和隊列的關(guān)系——一個 Topic 中存在多個隊列,那么這個 Topic 和隊列存放在哪呢?一個 Topic 分布在多個 Broker 上,一個 Broker 可以配置多個 Topic,它們是多對多的關(guān)系。如果某個 Topic 消息量很大,應(yīng)該給它多配置幾個隊列,并且盡量多分布在不同 Broker 上,以減輕某個 Broker 的壓力。Topic 消息量都比較均勻的情況下,如果某個 broker 上的隊列越多,則該 broker 壓力越大。 NameServer:不知道你們有沒有接觸過 ZooKeeper 和 Spring Cloud 中的 Eureka,它其實也是一個注冊中心,主要提供兩個功能:Broker 管理和路由信息管理。說白了就是 Broker 會將自己的信息注冊到 NameServer 中,此時 NameServer 就存放了很多 Broker 的信息(Broker的路由表),消費者和生產(chǎn)者就從 NameServer 中獲取路由表然后照著路由表的信息和對應(yīng)的 Broker 進行通信(生產(chǎn)者和消費者定期會向 NameServer 去查詢相關(guān)的 Broker 的信息)。 Producer:消息發(fā)布的角色,支持分布式集群方式部署。 Consumer:消息消費的角色,支持分布式集群方式部署。支持以 push 推,pull 拉兩種模式對消息進行消費,同時也支持集群方式和廣播方式的消費,它提供實時消息訂閱機制。 聽完了上面的解釋你可能會覺得,這玩意好簡單。不就是這樣的么?
嗯?你可能會發(fā)現(xiàn)一個問題,這老家伙 NameServer 干啥用的,這不多余嗎?直接 Producer、Consumer 和 Broker 直接進行生產(chǎn)消息,消費消息不就好了么?但是,我們上文提到過 Broker 是需要保證高可用的,如果整個系統(tǒng)僅僅靠著一個 Broker 來維持的話,那么這個 Broker 的壓力會不會很大?所以我們需要使用多個 Broker 來保證負載均衡。如果說,我們的消費者和生產(chǎn)者直接和多個 Broker 相連,那么當 Broker 修改的時候必定會牽連著每個生產(chǎn)者和消費者,這樣就會產(chǎn)生耦合問題,而 NameServer 注冊中心就是用來解決這個問題的。
當然,RocketMQ 中的技術(shù)架構(gòu)肯定不止前面那么簡單,因為上面圖中的四個角色都是需要做集群的。我給出一張官網(wǎng)的架構(gòu)圖,大家嘗試理解一下。
其實和我們最開始畫的那張乞丐版的架構(gòu)圖也沒什么區(qū)別,主要是一些細節(jié)上的差別,聽我細細道來。
第一、我們的 Broker 做了集群并且還進行了主從部署,由于消息分布在各個 Broker 上,一旦某個 Broker 宕機,則該 Broker 上的消息讀寫都會受到影響。所以 RocketMQ 提供了 master/slave 的結(jié)構(gòu),salve 定時從 master 同步數(shù)據(jù)(同步刷盤或者異步刷盤),如果 master 宕機,則 slave 提供消費服務(wù),但是不能寫入消息(后面我還會提到)。 第二、為了保證 HA,我們的 NameServer 也做了集群部署,但是請注意它是去中心化的。也就意味著它沒有主節(jié)點,你可以很明顯地看出 NameServer 的所有節(jié)點是沒有進行 Info Replicate 的,在 RocketMQ 中是通過單個 Broker 和所有 NameServer 保持長連接,并且在每隔 30 秒 Broker 會向所有 Nameserver 發(fā)送心跳,心跳包含了自身的 Topic 配置信息,這個步驟就對應(yīng)這上面的 Routing Info。 第三、在生產(chǎn)者需要向 Broker 發(fā)送消息的時候,需要先從 NameServer 獲取關(guān)于 Broker 的路由信息,然后通過輪詢的方法去向每個隊列中生產(chǎn)數(shù)據(jù)以達到負載均衡的效果。 第四、消費者通過 NameServer 獲取所有 Broker 的路由信息后,向 Broker 發(fā)送 Pull 請求來獲取消息數(shù)據(jù)。Consumer 可以以兩種模式啟動—— 廣播(Broadcast)和集群(Cluster)。廣播模式下,一條消息會發(fā)送給同一個消費組中的所有消費者,集群模式下消息只會發(fā)送給一個消費者。 高級特性&常見問題 順序消費 在上面的技術(shù)架構(gòu)介紹中,我們已經(jīng)知道了 RocketMQ 在主題上是無序的、它只有在隊列層面才是保證有序的。這又扯到兩個概念——普通順序和嚴格順序。所謂普通順序是指消費者通過同一個消費隊列收到的消息是有順序的,不同消息隊列收到的消息則可能是無順序的。普通順序消息在 Broker 重啟情況下不會保證消息順序性(短暫時間)。
所謂嚴格順序是指消費者收到的所有消息均是有順序的。嚴格順序消息即使在異常情況下也會保證消息的順序性。但是,嚴格順序看起來雖好,實現(xiàn)它可會付出巨大的代價。如果你使用嚴格順序模式,Broker 集群中只要有一臺機器不可用,則整個集群都不可用。你還用啥?現(xiàn)在主要場景也就在 binlog 同步。一般而言,我們的 MQ 都是能容忍短暫的亂序,所以推薦使用普通順序模式。(這個嚴格順序,感覺沒太懂,后面再查一下相關(guān)資料。。。)
那么,我們現(xiàn)在使用了普通順序模式,我們從上面學習知道了在 Producer 生產(chǎn)消息的時候會進行輪詢(取決你的負載均衡策略)來向同一主題的不同消息隊列發(fā)送消息。那么如果此時我有幾個消息分別是同一個訂單的創(chuàng)建、支付、發(fā)貨,在輪詢的策略下這三個消息會被發(fā)送到不同隊列,因為在不同的隊列此時就無法使用 RocketMQ 帶來的隊列有序特性來保證消息有序性了。
那么,怎么解決呢?其實很簡單,我們需要處理的僅僅是將同一語義下的消息放入同一個隊列(比如這里是同一個訂單),那我們就可以使用 Hash 取模法來保證同一個訂單在同一個隊列中就行了。
重復(fù)消費 就兩個字——冪等。在編程中一個冪等操作的特點是其任意多次執(zhí)行所產(chǎn)生的影響均與一次執(zhí)行的影響相同。比如說,這個時候我們有一個訂單的處理積分的系統(tǒng),每當來一個消息的時候它就負責為創(chuàng)建這個訂單的用戶的積分加上相應(yīng)的數(shù)值??墒怯幸淮危㈥犃邪l(fā)送給訂單系統(tǒng) FrancisQ 的訂單信息,其要求是給 FrancisQ 的積分加上 500。但是積分系統(tǒng)在收到 FrancisQ 的訂單信息處理完成之后返回給消息隊列處理成功的信息的時候出現(xiàn)了網(wǎng)絡(luò)波動(當然還有很多種情況,比如 Broker 意外重啟等等),這條回應(yīng)沒有發(fā)送成功。
那么,消息隊列沒收到積分系統(tǒng)的回應(yīng)會不會嘗試重發(fā)這個消息?問題就來了,我再發(fā)這個消息,萬一它又給 FrancisQ 的賬戶加上 500 積分怎么辦呢?所以我們需要給我們的消費者實現(xiàn)冪等,也就是對同一個消息的處理結(jié)果,執(zhí)行多少次都不變。
那么如何給業(yè)務(wù)實現(xiàn)冪等呢?這個還是需要結(jié)合具體的業(yè)務(wù)的。你可以使用寫入 Redis 來保證,因為 Redis 的 key 和 value 就是天然支持冪等的。當然還有使用數(shù)據(jù)庫插入法,基于數(shù)據(jù)庫的唯一鍵來保證重復(fù)數(shù)據(jù)不會被插入多條。不過最主要的還是需要根據(jù)特定場景使用特定的解決方案,你要知道你的消息消費是否是完全不可重復(fù)消費還是可以忍受重復(fù)消費的,然后再選擇強校驗和弱校驗的方式。畢竟在 CS 領(lǐng)域還是很少有技術(shù)銀彈的說法。
簡單了來說,冪等的校驗,還是需要業(yè)務(wù)方來支持,因為你解決不了網(wǎng)絡(luò)抖動問題哈~~
分布式事務(wù) 如何解釋分布式事務(wù)呢?事務(wù)大家都知道吧?要么都執(zhí)行要么都不執(zhí)行。在同一個系統(tǒng)中我們可以輕松地實現(xiàn)事務(wù),但是在分布式架構(gòu)中,我們有很多服務(wù)是部署在不同系統(tǒng)之間的,而不同服務(wù)之間又需要進行調(diào)用。比如此時我下訂單然后增加積分,如果保證不了分布式事務(wù)的話,就會出現(xiàn)A系統(tǒng)下了訂單,但是B系統(tǒng)增加積分失敗或者A系統(tǒng)沒有下訂單,B系統(tǒng)卻增加了積分。前者對用戶不友好,后者對運營商不利,這是我們都不愿意見到的。那么,如何去解決這個問題呢?
如今比較常見的分布式事務(wù)實現(xiàn)有 2PC、TCC 和事務(wù)消息(half 半消息機制)。每一種實現(xiàn)都有其特定的使用場景,但是也有各自的問題,都不是完美的解決方案。在 RocketMQ 中使用的是事務(wù)消息加上事務(wù)反查機制來解決分布式事務(wù)問題的。
下面是上圖的執(zhí)行流程:
A服務(wù)先發(fā)送個Half Message給Brock端,消息中攜帶 B服務(wù) 即將要+100元的信息。 當A服務(wù)知道Half Message發(fā)送成功后,那么開始第3步執(zhí)行本地事務(wù)。 執(zhí)行本地事務(wù)(會有三種情況1、執(zhí)行成功。2、執(zhí)行失敗。3、網(wǎng)絡(luò)等原因?qū)е聸]有響應(yīng)) 如果本地事務(wù)成功,那么Product像Brock服務(wù)器發(fā)送Commit,這樣B服務(wù)就可以消費該message。 如果本地事務(wù)失敗,那么Product像Brock服務(wù)器發(fā)送Rollback,那么就會直接刪除上面這條半消息。 如果因為網(wǎng)絡(luò)等原因遲遲沒有返回失敗還是成功,那么會執(zhí)行RocketMQ的回調(diào)接口,來進行事務(wù)的回查。 消息堆積 消息中間件的主要功能是異步解耦,還有個重要功能是擋住前端的數(shù)據(jù)洪峰,保證后端系統(tǒng)的穩(wěn)定性,這就要求消息中間件具有一定的消息堆積能力,消息堆積分以下兩種情況:
消息堆積在內(nèi)存Buffer,一旦超過內(nèi)存Buffer,可以根據(jù)一定的丟棄策略來丟棄消息,如CORBA Notification規(guī)范中描述。適合能容忍丟棄消息的業(yè)務(wù),這種情況消息的堆積能力主要在于內(nèi)存Buffer大小,而且消息堆積后,性能下降不會太大,因為內(nèi)存中數(shù)據(jù)多少對于對外提供的訪問能力影響有限。 消息堆積到持久化存儲系統(tǒng)中,例如DB,KV存儲,文件記錄形式。當消息不能在內(nèi)存Cache命中時,要不可避免的訪問磁盤,會產(chǎn)生大量讀IO,讀IO的吞吐量直接決定了消息堆積后的訪問能力。 評估消息堆積能力主要有以下四點:
消息能堆積多少條,多少字節(jié)?即消息的堆積容量。 消息堆積后,發(fā)消息的吞吐量大小,是否會受堆積影響? 消息堆積后,正常消費的Consumer是否會受影響? 消息堆積后,訪問堆積在磁盤的消息時,吞吐量有多大? 簡單來說,RocketMQ支持大量消息堆積,消息會存在內(nèi)存,超出內(nèi)存的消息會持久化到磁盤中。
定時消息 定時消息是指消息發(fā)到Broker后,不能立刻被Consumer消費,要到特定的時間點或者等待特定的時間后才能被消費。如果要支持任意的時間精度,在Broker層面,必須要做消息排序,如果再涉及到持久化,那么消息排序要不可避免的產(chǎn)生巨大性能開銷。RocketMQ支持定時消息,但是不支持任意時間精度,支持特定的level,例如定時5s,10s,1m等。
簡單來說,RocketMQ支持定時消息,但是只支持固定時間,不支持任意精度時間。
回溯消費 同步刷盤和異步刷盤 上面我講了那么多的 RocketMQ 的架構(gòu)和設(shè)計原理,你有沒有好奇,在 Topic 中的隊列是以什么樣的形式存在的?隊列中的消息又是如何進行存儲持久化的呢?我在上文中提到的同步刷盤和異步刷盤又是什么呢?它們會給持久化帶來什么樣的影響呢?下面我將給你們一一解釋。
如上圖所示,在同步刷盤中需要等待一個刷盤成功的 ACK,同步刷盤對 MQ 消息可靠性來說是一種不錯的保障,但是性能上會有較大影響,一般地適用于金融等特定業(yè)務(wù)場景。而異步刷盤往往是開啟一個線程去異步地執(zhí)行刷盤操作。消息刷盤采用后臺異步線程提交的方式進行,降低了讀寫延遲,提高了 MQ 的性能和吞吐量,一般適用于如發(fā)驗證碼等對于消息保證要求不太高的業(yè)務(wù)場景。一般地,異步刷盤只有在 Broker 意外宕機的時候會丟失部分數(shù)據(jù),你可以設(shè)置 Broker 的參數(shù) FlushDiskType 來調(diào)整你的刷盤策略(ASYNC_FLUSH 或者 SYNC_FLUSH)。
簡單來說,同步刷盤是刷盤后請求再返回,異步刷盤是直接返回請求,再去慢慢刷盤,可能會導(dǎo)致數(shù)據(jù)丟失。
同步復(fù)制和異步復(fù)制 上面的同步刷盤和異步刷盤是在單個結(jié)點層面的,而同步復(fù)制和異步復(fù)制主要是指的 Borker 主從模式下,主節(jié)點返回消息給客戶端的時候是否需要同步從節(jié)點。
同步復(fù)制:也叫 “同步雙寫”,也就是說,只有消息同步雙寫到主從結(jié)點上時才返回寫入成功。 異步復(fù)制:消息寫入主節(jié)點之后就直接返回寫入成功。異步復(fù)制會不會也像異步刷盤那樣影響消息的可靠性呢?答案是不會的,因為兩者就是不同的概念,對于消息可靠性是通過不同的刷盤策略保證的,而像異步同步復(fù)制策略僅僅是影響到了可用性。為什么呢?其主要原因是 RocketMQ 是不支持自動主從切換的,當主節(jié)點掛掉之后,生產(chǎn)者就不能再給這個主節(jié)點生產(chǎn)消息了。比如這個時候采用異步復(fù)制的方式,在主節(jié)點還未發(fā)送完需要同步的消息的時候主節(jié)點掛掉了,這個時候從節(jié)點就少了一部分消息。但是此時生產(chǎn)者無法再給主節(jié)點生產(chǎn)消息了,消費者可以自動切換到從節(jié)點進行消費(僅僅是消費),所以在主節(jié)點掛掉的時間只會產(chǎn)生主從結(jié)點短暫的消息不一致的情況,降低了可用性,而當主節(jié)點重啟之后,從節(jié)點那部分未來得及復(fù)制的消息還會繼續(xù)復(fù)制。 擴展知識1:在單主從架構(gòu)中,如果一個主節(jié)點掛掉了,那么也就意味著整個系統(tǒng)不能再生產(chǎn)了。那么這個可用性的問題能否解決呢?一個主從不行那就多個主從的唄,別忘了在我們最初的架構(gòu)圖中,每個 Topic 是分布在不同 Broker 中的。但是這種復(fù)制方式同樣也會帶來一個問題,那就是無法保證嚴格順序。在上文中我們提到了如何保證的消息順序性是通過將一個語義的消息發(fā)送在同一個隊列中,使用 Topic 下的隊列來保證順序性的。如果此時我們主節(jié)點 A 負責的是訂單 A 的一系列語義消息,然后它掛了,這樣其他節(jié)點是無法代替主節(jié)點A的,如果我們?nèi)我夤?jié)點都可以存入任何消息,那就沒有順序性可言了。(這點我并不認同,我理解主從的對列信息應(yīng)該是一樣的,我從主節(jié)點讀到哪里,如果主節(jié)點掛掉,應(yīng)該是可以到從結(jié)點去讀取的,如果不能這樣,搞個主從就完全沒有意義了。因為主從的信息是一樣的,對隊列的順序是內(nèi)有影響的,我不可能把不同的信息,搞兩個隊列,分別放到主從機器。)
擴展知識2:在 RocketMQ 中采用了 Dledger 解決主從數(shù)據(jù)同步問題。他要求在寫入消息的時候,要求至少消息復(fù)制到半數(shù)以上的節(jié)點之后,才給客?端返回寫?成功,并且它是?持通過選舉來動態(tài)切換主節(jié)點的。這里我就不展開說明了,讀者可以自己去了解。也不是說 Dledger 是個完美的方案,至少在 Dledger 選舉過程中是無法提供服務(wù)的,而且他必須要使用三個節(jié)點或以上,如果多數(shù)節(jié)點同時掛掉他也是無法保證可用性的,而且要求消息復(fù)制板書以上節(jié)點的效率和直接異步復(fù)制還是有一定的差距的。
這個機制,感覺就像大眾化的版本,基本思路都一樣,為了保證數(shù)據(jù)可用性,我還是推薦同步復(fù)制,當大多數(shù)節(jié)點復(fù)制成功,就認為復(fù)制完畢,和ETCD的Raft協(xié)議的日志同步原理一樣。
容錯機制 在實際使用RocketMQ的時候我們并不能保證每次發(fā)送的消息都剛好能被消費者一次性正常消費成功,可能會存在需要多次消費才能成功或者一直消費失敗的情況,那作為發(fā)送者該做如何處理呢?
RocketMQ提供了ack機制,以保證消息能夠被正常消費。發(fā)送者為了保證消息肯定消費成功,只有使用方明確表示消費成功,RocketMQ才會認為消息消費成功。中途斷電,拋出異常等都不會認為成功——即都會重新投遞。當然,如果消費者不告知發(fā)送者我這邊消費信息異常,那么發(fā)送者是不會知道的,所以消費者在設(shè)置監(jiān)聽的時候需要給個回調(diào)。
為了保證消息是肯定被至少消費成功一次,RocketMQ會把這批消息重發(fā)回Broker(topic不是原topic而是這個消費租的RETRY topic),在延遲的某個時間點(默認是10秒,業(yè)務(wù)可設(shè)置)后,再次投遞到這個ConsumerGroup。而如果一直這樣重復(fù)消費都持續(xù)失敗到一定次數(shù)(默認16次),就會投遞到DLQ死信隊列。應(yīng)用可以監(jiān)控死信隊列來做人工干預(yù)。
簡單來說,通過ACK保證消息一定能正常消費,對于異常消息,會重新放回Broker,但是這樣就會打亂消息的順序,所以容錯機制和消息嚴格順序,魚和熊掌不可兼得。
特性分析 這里才是內(nèi)容的重點,不僅需要知道RocketMQ的特性,還需要知道支持這些特性的原因:
消息路由(不支持):RocketMQ在處理消息之前是不允許消費者過濾一個主題中的消息。一個訂閱的消費者在沒有異常情況下會接受一個隊列中的所有消息; 消息有序(部分支持):需要將同一類的消息hash到同一個隊列Queue中,才能支持消息的順序,如果同一類消息散落到不同的Queue中,就不能支持消息的順序,如果設(shè)定消息一定要正常消費,那么就不能保證消息順序。 消息時序(可以支持):可以發(fā)送定時消息,但是只能制定系統(tǒng)定義好的時間,不支持自定義時間; 容錯處理(支持):通過ACK機制,保證消息一定能正常消費,這個和RabbitMQ很像; 伸縮(支持):整體架構(gòu)其實和kafaka很像,可以擴容broker和內(nèi)部隊列數(shù),或者增加消費組中的消費組數(shù)量,提高消費能力。 持久化(支持):消息可以持久化到磁盤中,所以支持消息的回溯,和kafaka很像。 消息回溯(支持):因為消息支持持久化,就支持回溯,可以理解是附帶的功能。 高吞吐(非常好):借鑒kafaka的設(shè)計,不會出現(xiàn)rabbitMQ的單Master抗壓力問題,可以從多個borker寫入和消費消息。 RabbitMQ 我們也不能天天去背八股,還需要實踐,RabbitMQ的實操實例,直接看這篇《入門RabbitMQ,這一篇絕對夠!》