10分鐘搞懂!消息隊(duì)列選型全方位對比

導(dǎo)語?|?消息隊(duì)列是分布式系統(tǒng)中重要的中間件,在高性能、高可用、低耦合等系統(tǒng)架構(gòu)中扮演著重要作用。本文對Kafka、Pulsar、RocketMQ、RabbitMQ、NSQ這幾個(gè)消息隊(duì)列組件進(jìn)行了一些調(diào)研,并整理了相關(guān)資料,為業(yè)務(wù)對MQ中間件選型提供參考。
一、概述
消息隊(duì)列是分布式系統(tǒng)中重要的中間件,在高性能、高可用、低耦合等系統(tǒng)架構(gòu)中扮演著重要作用。分布式系統(tǒng)可以借助消息隊(duì)列的能力,輕松實(shí)現(xiàn)以下功能:
解耦,將一個(gè)流程的上游和下游拆開,上游專注生產(chǎn)消息,下游專注處理消息。
廣播,一個(gè)上游生產(chǎn)的消息輕松被多個(gè)下游服務(wù)處理。
緩沖,應(yīng)對流量突然上漲,消息隊(duì)列可以扮演一個(gè)緩沖器的作用,保護(hù)下游服務(wù)使其可以根據(jù)實(shí)際的消費(fèi)能力處理消息。
異步,上游發(fā)送消息后可以馬上返回,下游可以異步處理消息。
冗余,保留歷史消息,處理失敗或當(dāng)出現(xiàn)異常時(shí)可以進(jìn)行重試或者回溯防止丟失。
近幾年出現(xiàn)了一些關(guān)注度較高的消息隊(duì)列中間件選型,如Kafka、Pulsar、RocketMQ等,首先從宏觀上做一些對比:

結(jié)論:
日志處理、大數(shù)據(jù)處理等場景,高吞吐量、低延遲的特性考慮,Kafka依舊是一個(gè)較好的選型。
針對業(yè)務(wù)交易數(shù)據(jù),有延遲消息、隊(duì)列模式消費(fèi)、異地容災(zāi),多消息主題等場景,可以選用TDMQ/Pulsar。
其他一些業(yè)務(wù)自定義的使用場景,由于后臺技術(shù)棧是Golang,可以考慮采用NSQ進(jìn)行定制開發(fā)或研究學(xué)習(xí)。
消息中間件性能跟服務(wù)端、客戶端參數(shù)、使用場景等方面上有很大關(guān)系,在系統(tǒng)上線前,還需要根據(jù)實(shí)際應(yīng)用場景進(jìn)行壓測調(diào)優(yōu)。
二、架構(gòu)簡介
(一)Kafka

(來源:https://zhuanlan.zhihu.com/p/38269875)
一個(gè)Kafka集群由多個(gè)Broker和一個(gè)ZooKeeper集群組成,Broker作為Kafka節(jié)點(diǎn)的服務(wù)器。同一個(gè)消息主題Topic可以由多個(gè)分區(qū)Partition組成,分區(qū)物理存儲在Broker上。負(fù)載均衡考慮,同一個(gè)Topic的多個(gè)分區(qū)存儲在多個(gè)不同的Broker上,為了提高可靠性,每個(gè)分區(qū)在不同的Broker會存在副本。
ZooKeeper是一個(gè)分布式開源的應(yīng)用程序協(xié)調(diào)服務(wù),可以實(shí)現(xiàn)統(tǒng)一命名服務(wù)、狀態(tài)同步服務(wù)、集群管理、分布式應(yīng)用配置項(xiàng)的管理等工作。Kafka里的ZooKeeper主要有一下幾個(gè)作用:
Broker注冊,當(dāng)有Broker故障的時(shí)候能及時(shí)感知。
Topic注冊,維護(hù)Topic各分區(qū)的個(gè)副本所在的Broker節(jié)點(diǎn),以及對應(yīng)leader/follower的角色。
Consumer注冊,維護(hù)消費(fèi)者組的offset以及消費(fèi)者與分區(qū)的對應(yīng)關(guān)系,實(shí)現(xiàn)負(fù)載均衡。
(二)Pulsar

(來源:https://cloud.tencent.com/developer/article/1845616)
Pulsar有三個(gè)重要的組件,Broker、BookKeeper和ZooKeeper,Broker是無狀態(tài)服務(wù),客戶端需要連接到Broker上進(jìn)行消息的傳遞。BookKeeper與ZooKeeper是有狀態(tài)服務(wù)。BookKeeper的節(jié)點(diǎn)叫Bookie,負(fù)責(zé)存儲消息和游標(biāo),ZooKeeper存儲Broker和Bookie的元數(shù)據(jù)。Pulsar以這種架構(gòu),實(shí)現(xiàn)存儲和計(jì)算分離,Broker負(fù)責(zé)計(jì)算,Bookie負(fù)責(zé)有狀態(tài)存儲。

Pulsar的多層架構(gòu)影響了存儲數(shù)據(jù)的方式。Pulsar將Topic分區(qū)劃分為分片(Segment),然后將這些分片存儲在Apache BookKeeper的存儲節(jié)點(diǎn)上,以提高性能、可伸縮性和可用性。Pulsar的分布式日志以分片為中心,借助擴(kuò)展日志存儲(通過Apache BookKeeper)實(shí)現(xiàn),內(nèi)置分層存儲支持,因此分片可以均勻地分布在存儲節(jié)點(diǎn)上。由于與任一給定Topic相關(guān)的數(shù)據(jù)都不會與特定存儲節(jié)點(diǎn)進(jìn)行捆綁,因此很容易替換存儲節(jié)點(diǎn)或縮擴(kuò)容。另外,集群中最小或最慢的節(jié)點(diǎn)也不會成為存儲或帶寬的短板。
(三)RocketMQ

(來源:https://rocketmq.apache.org/docs/rmq-arc/)
RocketMQ是阿里開源的消息中間件,它是一個(gè)開源的分布式消息傳遞和流式數(shù)據(jù)平臺。總共有四大部分:NameServer,Broker,Producer,Consumer。
NameServer主要用來管理brokers以及路由信息。broker服務(wù)器啟動時(shí)會注冊到NameServer上,并且兩者之間保持心跳監(jiān)測機(jī)制,以此來保證NameServer知道broker的存活狀態(tài)。而且,每一臺NameServer都存有全部的broker集群信息和生產(chǎn)者/消費(fèi)者客戶端的請求信息。
Broker負(fù)責(zé)管理消息存儲分發(fā),主從數(shù)據(jù)同步,為消息建立索引,提供消息查詢等能力。
(四)RabbitMQ

(來源:https://www.cxymm.net/article/Super_RD/70238869)
RabbitMQ基于AMQP協(xié)議來實(shí)現(xiàn),主要由Exchange和Queue兩部分組成,然后通過RoutingKey關(guān)聯(lián)起來,消息投遞到Exchange然后通過Queue接收。
(五)NSQ

(來源:https://zhuanlan.zhihu.com/p/37081073)
NSQ主要有nsqlookup、nsqd兩部分組成:
Nsqlookup為守護(hù)進(jìn)程,負(fù)責(zé)管理拓?fù)湫畔⒉⑻峁┌l(fā)現(xiàn)服務(wù)。客戶端通過查詢nsqlookupd獲取指定Topic所在的nsqd節(jié)點(diǎn)。nsqd往nsqlookup上注冊和廣播自身topic和channel的信息。
nsqd在服務(wù)端運(yùn)行的守護(hù)進(jìn)程,負(fù)責(zé)接收,排隊(duì),投遞消息給客戶端。
二、選型要點(diǎn)

先來個(gè)匯總,接下來會對消息隊(duì)列中間件的各項(xiàng)功能進(jìn)行逐個(gè)分析。
(一)功能
消費(fèi)推拉模式
客戶端消費(fèi)者獲取消息的方式,Kafka和RocketMQ是通過長輪詢Pull的方式拉取消息,RabbitMQ、Pulsar、NSQ都是通過Push的方式。
pull類型的消息隊(duì)列更適合高吞吐量的場景,允許消費(fèi)者自己進(jìn)行流量控制,根據(jù)消費(fèi)者實(shí)際的消費(fèi)能力去獲取消息。而push類型的消息隊(duì)列,實(shí)時(shí)性更好,但需要有一套良好的流控策略(backpressure)當(dāng)消費(fèi)者消費(fèi)能力不足時(shí),減少push的消費(fèi)數(shù)量,避免壓垮消費(fèi)端。
延遲隊(duì)列
消息延遲投遞,當(dāng)消息產(chǎn)生送達(dá)消息隊(duì)列時(shí),有些業(yè)務(wù)場景并不希望消費(fèi)者立刻收到消息,而是等待特定時(shí)間后,消費(fèi)者才能拿到這個(gè)消息進(jìn)行消費(fèi)。延遲隊(duì)列一般分為兩種,基于消息的延遲和基于隊(duì)列的延遲。基于消息的延遲指為每條消息設(shè)置不同的延遲時(shí)間,當(dāng)隊(duì)列有新消息進(jìn)入的時(shí)候根據(jù)延遲時(shí)間排序,當(dāng)然這樣會對性能造成較大影響。另一種基于隊(duì)列的延遲指的是設(shè)置不同延遲級別的隊(duì)列,隊(duì)列中每個(gè)消息的延遲時(shí)間都是相同的,這樣免去了基于延遲時(shí)間排序?qū)π阅軒淼膿p耗,通過一定的掃描策略即可投遞超時(shí)的消息。
延遲消息的使用場景比如異常檢測重試,訂單超時(shí)取消等,例如:
服務(wù)請求異常,需要將異常請求放到單獨(dú)的隊(duì)列,隔5分鐘后進(jìn)行重試;
用戶購買商品,但一直處于未支付狀態(tài),需要定期提醒用戶支付,超時(shí)則關(guān)閉訂單;
面試或者會議預(yù)約,在面試或者會議開始前半小時(shí),發(fā)送通知再次提醒。
Kafka不支持延遲消息。Pulsar支持秒級的延遲消息,所有延遲投遞的消息會被Delayed Message Tracker記錄對應(yīng)的index,consumer在消費(fèi)時(shí),會先去Delayed Message Tracker檢查,是否有到期需要投遞的消息,如果有到期的消息,則從Tracker中拿出對應(yīng)的index,找到對應(yīng)的消息進(jìn)行消費(fèi),如果沒有到期的消息,則直接消費(fèi)正常的消息。對于長時(shí)間的延遲消息,會被存儲在磁盤中,當(dāng)快到延遲間隔時(shí)才被加載到內(nèi)存里。

RocketMQ開源版本延遲消息臨時(shí)存儲在一個(gè)內(nèi)部主題中,不支持任意時(shí)間精度,支持特定的level,例如定時(shí)5s,10s,1m等。
RabbitMQ需要安裝一個(gè)rabbitmq_delayed_message_exchange插件。
NSQ通過內(nèi)存中的優(yōu)先級隊(duì)列來保存延遲消息,支持秒級精度,最多2個(gè)小時(shí)延遲。
死信隊(duì)列
由于某些原因消息無法被正確的投遞,為了確保消息不會被無故的丟棄,一般將其置于一個(gè)特殊角色的隊(duì)列,這個(gè)隊(duì)列一般稱之為死信隊(duì)列。與此對應(yīng)的還有一個(gè)“回退隊(duì)列”的概念,試想如果消費(fèi)者在消費(fèi)時(shí)發(fā)生了異常,那么就不會對這一次消費(fèi)進(jìn)行確認(rèn)(Ack), 進(jìn)而發(fā)生回滾消息的操作之后消息始終會放在隊(duì)列的頂部,然后不斷被處理和回滾,導(dǎo)致隊(duì)列陷入死循環(huán)。為了解決這個(gè)問題,可以為每個(gè)隊(duì)列設(shè)置一個(gè)回退隊(duì)列,它和死信隊(duì)列都是為異常的處理提供的一種機(jī)制保障。實(shí)際情況下,回退隊(duì)列的角色可以由死信隊(duì)列和重試隊(duì)列來扮演。
Kafka沒有死信隊(duì)列,通過Offset的方式記錄當(dāng)前消費(fèi)的偏移量。
Pulsar有重試機(jī)制,當(dāng)某些消息第一次被消費(fèi)者消費(fèi)后,沒有得到正常的回應(yīng),則會進(jìn)入重試Topic中,當(dāng)重試達(dá)到一定次數(shù)后,停止重試,投遞到死信Topic中。
RocketMQ通過DLQ來記錄所有消費(fèi)失敗的消息。
RabbitMQ是利用類似于延遲隊(duì)列的形式實(shí)現(xiàn)死信隊(duì)列。
NSQ沒有死信隊(duì)列。
優(yōu)先級隊(duì)列
優(yōu)先級隊(duì)列不同于先進(jìn)先出隊(duì)列,優(yōu)先級高的消息具備優(yōu)先被消費(fèi)的特權(quán),這樣可以為下游提供不同消息級別的保證。不過這個(gè)優(yōu)先級也是需要有一個(gè)前提的:如果消費(fèi)者的消費(fèi)速度大于生產(chǎn)者的速度,并且消息中間件服務(wù)器(一般簡單的稱之為Broker)中沒有消息堆積,那么對于發(fā)送的消息設(shè)置優(yōu)先級也就沒有什么實(shí)質(zhì)性的意義了,因?yàn)樯a(chǎn)者剛發(fā)送完一條消息就被消費(fèi)者消費(fèi)了,那么就相當(dāng)于Broker中至多只有一條消息,對于單條消息來說優(yōu)先級是沒有什么意義的。
Kafka、RocketMQ、Pulsar、NSQ不支持優(yōu)先級隊(duì)列,可以通過不同的隊(duì)列來實(shí)現(xiàn)消息優(yōu)先級。
RabbitMQ支持優(yōu)先級消息。
消息回溯
一般消息在消費(fèi)完成之后就被處理了,之后再也不能消費(fèi)到該條消息。消息回溯正好相反,是指消息在消費(fèi)完成之后,還能消費(fèi)到之前被消費(fèi)掉的消息。對于消息而言,經(jīng)常面臨的問題是“消息丟失”,至于是真正由于消息中間件的缺陷丟失還是由于使用方的誤用而丟失一般很難追查,如果消息中間件本身具備消息回溯功能的話,可以通過回溯消費(fèi)復(fù)現(xiàn)“丟失的”消息進(jìn)而查出問題的源頭之所在。消息回溯的作用遠(yuǎn)不止與此,比如還有索引恢復(fù)、本地緩存重建,有些業(yè)務(wù)補(bǔ)償方案也可以采用回溯的方式來實(shí)現(xiàn)。
Kafka支持消息回溯,可以根據(jù)時(shí)間戳或指定Offset,重置Consumer的Offset使其可以重復(fù)消費(fèi)。
Pulsar支持按時(shí)間對消息進(jìn)行回溯。
RocketMQ支持按時(shí)間回溯,實(shí)現(xiàn)的原理跟Kafka一致。
RabbitMQ不支持回溯,消息一旦標(biāo)記確認(rèn)就會被標(biāo)記刪除。
NSQ一般消息是不可回溯的,但可以通過nsq_to_file工具,將消息寫入到文件,然后從文件里重放消息。
消息持久化
流量削峰是消息中間件的一個(gè)非常重要的功能,而這個(gè)功能其實(shí)得益于其消息堆積能力。從某種意義上來講,如果一個(gè)消息中間件不具備消息堆積的能力,那么就不能把它看做是一個(gè)合格的消息中間件。消息堆積分內(nèi)存式堆積和磁盤式堆積。一般來說,磁盤的容量會比內(nèi)存的容量要大得多,對于磁盤式的堆積其堆積能力就是整個(gè)磁盤的大小。從另外一個(gè)角度講,消息堆積也為消息中間件提供了冗余存儲的功能。
Kafka和RocketMQ直接將消息刷入磁盤文件中進(jìn)行持久化,所有的消息都存儲在磁盤中。只要磁盤容量夠,可以做到無限消息堆積。
RabbitMQ 是典型的內(nèi)存式堆積,但這并非絕對,在某些條件觸發(fā)后會有換頁動作來將內(nèi)存中的消息換頁到磁盤(換頁動作會影響吞吐),或者直接使用惰性隊(duì)列來將消息直接持久化至磁盤中。
Pulsar消息是存儲在BookKeeper存儲集群上,也是磁盤文件。
NSQ通過nsq_to_file工具,將消息寫入到文件。
消息確認(rèn)機(jī)制
消息隊(duì)列需要管理消費(fèi)進(jìn)度,確認(rèn)消費(fèi)者是否成功處理消息,使用push的方式的消息隊(duì)列組件往往是對單條消息進(jìn)行確認(rèn),對于未確認(rèn)的消息,進(jìn)行延遲重新投遞或者進(jìn)入死信隊(duì)列。
Kafka通過Offset的方式確認(rèn)消息。
RocketMQ與Kafka類似也會提交Offset,區(qū)別在于消費(fèi)者對于消費(fèi)失敗的消息,可以標(biāo)記為消息消費(fèi)失敗,Broker會重試投遞,如果累計(jì)多次消費(fèi)失敗,會投遞到死信隊(duì)列。
RabbitMQ和NSQ類似,消費(fèi)者確認(rèn)單條消息,否則會重新放回隊(duì)列中等待下次投遞。
Pulsar使用專門的Cursor管理。累積確認(rèn)和Kafka效果一樣;提供單條或選擇性確認(rèn)。
消息TTL
消息TTL表示一條消息的生存時(shí)間,如果消息發(fā)出來后,在TTL的時(shí)間內(nèi)沒有消費(fèi)者進(jìn)行消費(fèi),消息隊(duì)列會將消息刪除或者放入死信隊(duì)列中。
Kafka根據(jù)設(shè)置的保留期來刪除消息。有可能消息沒被消費(fèi),過期后被刪除。不支持TTL。
Pulsar支持TTL,如果消息未在配置的TTL時(shí)間段內(nèi)被任何消費(fèi)者使用,則消息將自動標(biāo)記為已確認(rèn)。消息保留期與消息TTL之間的區(qū)別在于:消息保留期作用于標(biāo)記為已確認(rèn)并設(shè)置為已刪除的消息,而TTL作用于未ack的消息。上面的圖例中說明了Pulsar中的TTL。例如,如果訂閱B沒有活動消費(fèi)者,則在配置的TTL時(shí)間段過后,消息M10將自動標(biāo)記為已確認(rèn),即使沒有消費(fèi)者實(shí)際讀取該消息。
RocketMQ提及到消息TTL的資料比較少,不過看接口似乎是支持的。
RabbitMQ有兩種方式,一個(gè)是聲明隊(duì)列的時(shí)候在隊(duì)列屬性中設(shè)置,整個(gè)隊(duì)列中的消息都有相同的有效期。還可以發(fā)送消息的時(shí)候給消息設(shè)置屬性,可以位每條消息都設(shè)置不同的TTL。
NSQ似乎還沒支持,有一個(gè)Feature Request的Issue處于Open狀態(tài)。
多租戶隔離
多租戶是指通過一個(gè)軟件實(shí)例為多個(gè)租戶提供服務(wù)的能力。租戶是指對系統(tǒng)有著相同“視圖”的一組用戶。不支持多租戶的系統(tǒng)里邊,往往要為不同用戶或者不同集群創(chuàng)建多個(gè)消息隊(duì)列實(shí)例實(shí)現(xiàn)物理隔離,這樣會帶來較高的運(yùn)維成本。作為一種企業(yè)級的消息系統(tǒng),Pulsar的多租戶能力按照設(shè)計(jì)可滿足下列需求:
?
確保嚴(yán)苛的SLA可順利滿足。
保證不同租戶之間的隔離。
針對資源利用率強(qiáng)制實(shí)施配額。
提供每租戶和系統(tǒng)級的安全性。
確保低成本運(yùn)維以及盡可能簡單的管理。
Pulsar通過下列方式滿足了上述需求:
通過為每個(gè)租戶進(jìn)行身份驗(yàn)證、授權(quán)和ACL(訪問控制列表)獲得所需安全性。
為每個(gè)租戶強(qiáng)制實(shí)施存儲配額。
以策略的方式定義所有隔離機(jī)制,策略可在運(yùn)行過程中更改,借此降低運(yùn)維成本并簡化管理工作。
消息順序性
消息順序性是指保證消息有序。消息消費(fèi)順序跟生產(chǎn)的順序保持一致。
Kafka保證了分區(qū)內(nèi)的消息有序。
Pulsar支持兩種消費(fèi)模式,獨(dú)占訂閱的流模式只保證了消息的順序性,共享訂閱隊(duì)列模型不保證有序性。
RocketMQ需要用到鎖來保證一個(gè)隊(duì)列同時(shí)只有一個(gè)消費(fèi)者線程進(jìn)行消費(fèi),保證消息的有序性。
RabbitMQ順序性的條件比較苛刻,需要單線程發(fā)送、單線程消費(fèi),并且不采用延遲隊(duì)列、優(yōu)先級隊(duì)列等高級功能。
NSQ是利用了golang自身的case/select實(shí)現(xiàn)的消息分發(fā),本身不提供有序性保障,不能夠把特性消息和消費(fèi)者對應(yīng)起來,無法實(shí)現(xiàn)消息的有序性。
消息查詢
在實(shí)際開發(fā)中,經(jīng)常要查看MQ中消息的內(nèi)容,比如通過某個(gè)MessageKey/ID,查詢到MQ的具體消息。或者是對消息進(jìn)行鏈路追蹤,知道消息從哪里來,發(fā)送到哪里去,進(jìn)而快速對問題進(jìn)行排查定位。
Kafka存儲層是以分布式提交日志的形式實(shí)現(xiàn),每次寫操作都順序追加到日志的末尾。讀也是順序讀。不支持檢索功能。
Pulsar可以通過消息ID,查詢到具體某條消息的消息內(nèi)容、消息參數(shù)和消息軌跡。
RocketMQ支持按Message Key、Unique Key、Message Id對消息進(jìn)行查詢。
RabbitMQ使用基于索引的存儲系統(tǒng)。這些將數(shù)據(jù)保存在樹結(jié)構(gòu)中,以提供確認(rèn)單個(gè)消息所需的快速訪問。由于RabbitMQ的消息在確認(rèn)后會被刪除,因此只能查詢未確認(rèn)的消息。
NSQ自身不支持消息持久化和消息檢索,不過可以使用nsq_to_http等工具將消息寫入可支持索引的存儲里。
消費(fèi)模式
Kafka有兩種消費(fèi)模式,最終都會保證一個(gè)分區(qū)只有1個(gè)消費(fèi)者在消費(fèi):
subscribe方式:當(dāng)主題分區(qū)數(shù)量變化或者consumer數(shù)量變化時(shí),會進(jìn)行rebalance;注冊rebalance監(jiān)聽器,可以手動管理offset不注冊監(jiān)聽器,kafka自動管理。
assign方式:手動將consumer與partition進(jìn)行對應(yīng),kafka不會進(jìn)行rebanlance。
Pulsar有以下四種消費(fèi)模式,其中獨(dú)占模式和災(zāi)備模式跟Kafka類似,為流模型,每個(gè)分區(qū)只有1個(gè)消費(fèi)者消費(fèi),能保證消息有序性。共享模式和Key共享模式為隊(duì)列模型,多個(gè)消費(fèi)者能提高消費(fèi)速度,但不能保證有序性。

Exclusive獨(dú)占模式(默認(rèn)模式):一個(gè)Subscription只能與一個(gè)Consumer關(guān)聯(lián),只有這個(gè)Consumer可以接收到Topic的全部消息,如果該Consumer出現(xiàn)故障了就會停止消費(fèi)。
災(zāi)備模式(Failover):當(dāng)存在多個(gè)consumer時(shí),將會按字典順序排序,第一個(gè)consumer被初始化為唯一接受消息的消費(fèi)者。當(dāng)?shù)谝粋€(gè)consumer斷開時(shí),所有的消息(未被確認(rèn)和后續(xù)進(jìn)入的)將會被分發(fā)給隊(duì)列中的下一個(gè)consumer。
共享模式(Shared):消息通過round robin輪詢機(jī)制(也可以自定義)分發(fā)給不同的消費(fèi)者,并且每個(gè)消息僅會被分發(fā)給一個(gè)消費(fèi)者。當(dāng)消費(fèi)者斷開連接,所有被發(fā)送給他,但沒有被確認(rèn)的消息將被重新安排,分發(fā)給其它存活的消費(fèi)者。
KEY共享模式(Key_Shared):當(dāng)存在多個(gè)consumer時(shí),將根據(jù)消息的 key進(jìn)行分發(fā),key相同的消息只會被分發(fā)到同一個(gè)消費(fèi)者。
RocketMQ有兩種消費(fèi)模式,BROADCASTING廣播模式,CLUSTERING集群模式。
廣播消費(fèi)指的是:一條消息被多個(gè)consumer消費(fèi),即使這些consumer屬于同一個(gè)ConsumerGroup,消息也會被ConsumerGroup中的每個(gè)Consumer都消費(fèi)一次,廣播消費(fèi)中ConsumerGroup概念可以認(rèn)為在消息劃分方面無意義。
集群消費(fèi)模式:一個(gè)ConsumerGroup中的Consumer實(shí)例平均分?jǐn)傁M(fèi)消息。例如某個(gè)Topic有9條消息,其中一個(gè)ConsumerGroup有3個(gè)實(shí)例(可能是3個(gè)進(jìn)程,或者3臺機(jī)器),那么每個(gè)實(shí)例只消費(fèi)其中部分,消費(fèi)完的消息不能被其他實(shí)例消費(fèi)。
RabbitMQ和NSQ的消費(fèi)比較類似,都是跟Pulsar共享模式類似的,隊(duì)列的形式,增加一個(gè)消費(fèi)者組里的消費(fèi)者數(shù)量能提高消費(fèi)速度。
消息可靠性
消息丟失是使用消息中間件時(shí)所不得不面對的一個(gè)同點(diǎn),其背后消息可靠性也是衡量消息中間件好壞的一個(gè)關(guān)鍵因素。尤其是在金融支付領(lǐng)域,消息可靠性尤為重要。比如當(dāng)服務(wù)出現(xiàn)故障時(shí),一些對于生產(chǎn)者來說已經(jīng)生產(chǎn)成功的消息,是否會在高可用切換時(shí)丟失。同步刷盤是增強(qiáng)一個(gè)組件可靠性的有效方式,消息中間件也不例外,Kafka和RabbitMQ都可以支持同步刷盤,但絕大多數(shù)情景下,一個(gè)組件的可靠性不應(yīng)該由同步刷盤這種極其損耗性能的操作來保障,而是采用多副本的機(jī)制來保證。
Kafka可以通過配置request.required.acks參數(shù)設(shè)置可靠級別,表示一條消息有多少個(gè)副本確認(rèn)接收成功后,才被任務(wù)發(fā)送成功。
request.required.acks=-1 (全量同步確認(rèn),強(qiáng)可靠性保證)
request.required.acks=1(leader確認(rèn)收到,默認(rèn))
request.required.acks=0 (不確認(rèn),但是吞吐量大)
Pulsar有跟Kafka類似的概念,叫Ack Quorum Size(Qa),Qa是每次寫請求發(fā)送完畢后需要回復(fù)確認(rèn)的Bookie的個(gè)數(shù),其數(shù)值越大則需要確認(rèn)寫成功的時(shí)間越長,其值上限是副本數(shù)Qw。為了一致性,Qa應(yīng)該是:(Qw+1)/2或者更,即為了確保數(shù)據(jù)安全性,Qa下限是?(Qw+1)/2。
RocketMQ與Kafka類似。
RabbitMQ是主從架構(gòu),通過鏡像環(huán)形隊(duì)列實(shí)現(xiàn)多副本及強(qiáng)一致性語義的。多副本可以保證在master節(jié)點(diǎn)宕機(jī)異常之后可以提升slave作為新的master而繼續(xù)提供服務(wù)來保障可用性。
NSQ會通過go-diskqueue組件將消息落盤到本地文件中,通過mem-queue-size參數(shù)控制內(nèi)存中隊(duì)列大小,如果mem-queue-size=0每條消息都會存儲到磁盤里,不用擔(dān)心節(jié)點(diǎn)重啟引起的消息丟失。但由于是存儲在本地磁盤中,如果節(jié)點(diǎn)離線,堆積在節(jié)點(diǎn)磁盤里的消息會丟失。
(二)性能
Kafka的公司Confluent在2020年8月發(fā)了一篇Benchmarking Apache Kafka, Apache Pulsar, and RabbitMQ: Which is the Fastest?文章,并且提出了一個(gè)開源的MQ Benchmark框架THE OPENMESSAGING BENCHMARK FRAMEWORK,在這個(gè)文檔里,對比了Kafka、Pulsar、RabbitMQ的吞吐量、端到端延遲等性能數(shù)據(jù)。最后得出結(jié)論Kafka相對來說性能最好。

但接下來StreamNative在2020年12月指出了Confluence的基準(zhǔn)測試的一些問題,并對Pulsar進(jìn)行了參數(shù)調(diào)優(yōu)之后重新執(zhí)行了一遍結(jié)果,測試報(bào)告展示Pulsar能達(dá)到跟Kafka同樣的吞吐量,在某些場景下,Pulsar的延遲顯著低于Kafka。

而且在性能測試上,有很多客戶端、服務(wù)端參數(shù)設(shè)置、機(jī)器性能配置等影響,比如消息可靠性級別,壓縮算法等,很難做到“完全”控制變量公平的測試。而且OpenMessaging Benchmark的開源Github的Readme上也提到了。
不過有幾個(gè)關(guān)注點(diǎn):
RabbitMQ的延遲是微秒級的,其他組件的延遲都是毫秒級,RabbitMQ應(yīng)該是MQ組件里相對來說較低的。
Kafka單實(shí)例在主題/分區(qū)數(shù)比較多的情況下,性能會明顯降低。
kafka是一個(gè)分區(qū)一個(gè)文件,當(dāng)topic過多,分區(qū)的總量也會增加,kafka中存在過多的文件,當(dāng)對消息刷盤時(shí),就會出現(xiàn)文件競爭磁盤,出現(xiàn)性能的下降。
還有Kafka每個(gè)消費(fèi)者加入或退出都會進(jìn)行重平衡,當(dāng)分區(qū)數(shù)比較多時(shí)重平衡可能耗時(shí)較久,在重平衡的階段消費(fèi)者是不能消費(fèi)消息的。
而Pulsar由于存儲與計(jì)算分離的架構(gòu),使得它可以支持百萬級別的Topic數(shù)量。
Pulsar和Kafka都被廣泛用于各個(gè)企業(yè),也各有優(yōu)勢,都能通過數(shù)量基本相同的硬件處理大流量。部分用戶誤以為Pulsar使用了很多組件,因此需要很多服務(wù)器來實(shí)現(xiàn)與Kafka相匹敵的性能。這種想法適用于一些特定硬件配置,但在多數(shù)資源配置相同的情況中,Pulsar的優(yōu)勢更加明顯,可以用相同的資源實(shí)現(xiàn)更好的性能。舉例來說,Splunk最近分享了他們選擇Pulsar放棄Kafka的原因,其中提到“由于分層架構(gòu),Pulsar幫助他們將成本降低了30%-50%,延遲降低了80%-98%,運(yùn)營成本降低了33%-50%”。Splunk 團(tuán)隊(duì)發(fā)現(xiàn)Pulsar可以更好地利用磁盤IO,降低CPU利用率,同時(shí)更好地控制內(nèi)存。
在分布式系統(tǒng)里,單機(jī)性能指標(biāo)雖然也很重要,分布式系統(tǒng)整體的性能以及靈活擴(kuò)縮容、高可用容災(zāi)等能力也會是評估的一個(gè)重要參考。MQ中間件具體的性能指標(biāo),也需要我們自己根據(jù)實(shí)際的情況,根據(jù)實(shí)際購買的集群配置和客戶端參數(shù),進(jìn)行壓測調(diào)優(yōu)來評估。
(三)運(yùn)維
在使用過程中難免會出現(xiàn)各種異常情況,比如宕機(jī)、網(wǎng)絡(luò)抖動、擴(kuò)容等。消息隊(duì)列具備異地容災(zāi),高可用架構(gòu)等能力,能避免一些計(jì)算節(jié)點(diǎn)、網(wǎng)絡(luò)等基礎(chǔ)設(shè)施不可用導(dǎo)致的故障。
高可用
Kafka通過分區(qū)多副本的方式解決高可用問題。
Pulsar的計(jì)算集群Broker是無狀態(tài)的,可以靈活擴(kuò)縮容,存儲節(jié)點(diǎn)Bookie上通過消息分區(qū)分片副本的方式,每個(gè)分片都有一個(gè)或多個(gè)副本,保證在某一個(gè)Bookie掛掉后,有其他分片可以提供服務(wù)。
RocketMQ和RabbitMQ都是主從架構(gòu),當(dāng)master掛掉后,由原來的從節(jié)點(diǎn)繼續(xù)提供服務(wù)。備機(jī)提供消費(fèi)服務(wù),保證消息不丟,但不提供寫服務(wù)。
NSQ是類似分布式架構(gòu),不過由于消息存儲是在節(jié)點(diǎn)本地磁盤上,如果一個(gè)節(jié)點(diǎn)離線,堆積在節(jié)點(diǎn)磁盤上的消息會丟失。
跨地域容災(zāi)
Pulsar原生支持跨地域容災(zāi)功能,在這個(gè)圖中,每當(dāng)P1、P2和P3的生產(chǎn)者分別向Cluster-A、Cluster-B和Cluster-C中的T1 topic發(fā)送消息時(shí),這些消息很快在不同的集群中復(fù)制。一旦消息完成復(fù)制,消費(fèi)者C1和C2會從各自的集群消費(fèi)到這個(gè)消息。
在這個(gè)跨地域容災(zāi)的設(shè)計(jì)支撐下,其一,我們可以比較容易的將服務(wù)分散到多個(gè)機(jī)房;其二,可以應(yīng)對機(jī)房級別的故障,即在一個(gè)機(jī)房不可用的情況下,服務(wù)可以轉(zhuǎn)接到其它的機(jī)房來繼續(xù)對外提供服務(wù)。
一句話概括,Pulsar的跨地域復(fù)制,其實(shí)就是在一個(gè)本地集群中創(chuàng)建一個(gè) Producer,把異地的集群作為這個(gè)Producer的發(fā)送地址,將本地集群的消息發(fā)送過去,并且在本地維護(hù)一個(gè)Cusor來保證消息可靠性和冪等性。

集群擴(kuò)容
當(dāng)消息量突然上漲,消息隊(duì)列集群到達(dá)瓶頸的時(shí)候,需要對集群進(jìn)行擴(kuò)容,擴(kuò)容一般分為水平擴(kuò)容和垂直擴(kuò)容兩種方式,水平擴(kuò)容指的是往往集群中增加節(jié)點(diǎn),垂直擴(kuò)容指的是把集群中部分節(jié)點(diǎn)的配置調(diào)高,增加處理能力。
Kafka集群由于主題分區(qū)是物理存儲在Broker節(jié)點(diǎn)上的,新加入的集群的節(jié)點(diǎn)并沒有存儲分區(qū)分片,也就無法提供馬上提供服務(wù),因此需要把一些Topic的分區(qū)分配到新加入的節(jié)點(diǎn)里,這里會涉及到一個(gè)分區(qū)數(shù)據(jù)均衡的過程,將某些分區(qū)的數(shù)據(jù)復(fù)制到新節(jié)點(diǎn)上。這個(gè)過程跟分區(qū)當(dāng)前堆積的數(shù)據(jù)量、Broker性能有關(guān),有可能會出現(xiàn)由于源Broker負(fù)載過高,堆積數(shù)據(jù)過大,導(dǎo)致數(shù)據(jù)均衡的時(shí)間變長。
Pulsar的無限分布式日志以分片為中心,借助擴(kuò)展日志存儲(通過Apache BookKeeper)實(shí)現(xiàn),內(nèi)置分層存儲支持,因此分片可以均勻地分布在存儲節(jié)點(diǎn)上。由于與任一給定topic相關(guān)的數(shù)據(jù)都不會與特定存儲節(jié)點(diǎn)進(jìn)行捆綁,因此很容易替換存儲節(jié)點(diǎn)或縮擴(kuò)容。另外,集群中最小或最慢的節(jié)點(diǎn)也不會成為存儲或帶寬的短板。
RocketMQ新節(jié)點(diǎn)直接加入到集群中,在新的broker創(chuàng)建新topic并且分配隊(duì)列,或者在已有topic基礎(chǔ)上分配隊(duì)列。與Kafka的區(qū)別是,Kafka的分區(qū)是在不同的物理機(jī)器上,而Rocketmq是邏輯分區(qū),用的隊(duì)列形式,因此不存在出現(xiàn)數(shù)據(jù)不均衡的情況。
RabbitMQ和NSQ類似,由于不涉及過多的消息持久化,直接往集群中增加節(jié)點(diǎn)。
使用成本
Kafka/Pulsar/RocketMQ/RabbitMQ在騰訊云上都上線了標(biāo)準(zhǔn)產(chǎn)品,可以直接購買創(chuàng)建實(shí)例,能大大降低部署運(yùn)維成本。而NSQ目前暫時(shí)還沒有上線,需要自行部署。
CKafka在騰訊云上是以實(shí)例的形式售賣,專業(yè)版最低配1494元/月,500G SSD,40MB/s,TDMQ Pulsar是以類似無服務(wù)的方式按量計(jì)費(fèi),按調(diào)用次數(shù)/消息大小/存儲大小等計(jì)費(fèi),調(diào)用次數(shù)2.00元/百萬次。在用量較少的情況下,比如一些小型快速上線的業(yè)務(wù),TDMQ Pulsar的成本會比CKafka低很多。
RocketMQ和RabbitMQ都是最近推出的產(chǎn)品,目前仍在公測階段,暫時(shí)還沒有定價(jià)。
三、總結(jié)
Kafka與Pulsar都是騰訊云主打的消息隊(duì)列中間件,都具有高性能,高可靠,支持多種場景。Kafka推出的時(shí)間較早,各種場景比如日志、大數(shù)據(jù)處理等都有較成熟的解決方案。而Pulsar作為一個(gè)新秀,支持的功能比CKafka更豐富,而且跨地域容災(zāi),多租戶等功能,解決了很多Kafka設(shè)計(jì)缺陷和運(yùn)維成本問題,整體穩(wěn)定性更強(qiáng)。很多國內(nèi)外大公司也有很多Pulsar的實(shí)踐案例。因此,一些傳統(tǒng)的日志、大數(shù)據(jù)處理等場景,對高吞吐量有要求的,對消息可靠性的要求沒那么高的,可以選用Kafka,有很多優(yōu)秀的文檔說明怎么參數(shù)調(diào)優(yōu)提高性能。而一些對消息可靠性、容災(zāi)要求更好,或者有高分區(qū)、延遲隊(duì)列等需求的場景,可以選用Pulsar。
我們后臺的技術(shù)棧是基于Golang的,在上文的對比中,還挑了一個(gè)基于Golang開發(fā)的消息隊(duì)列NSQ,如果有一些定制化需求或者需要二次開發(fā)的,可以選用NSQ。也可以通過閱讀NSQ的源碼,學(xué)習(xí)一些優(yōu)秀高性能消息隊(duì)列中間件的實(shí)現(xiàn)方式,比如里邊diskqueue組件,一個(gè)基于磁盤的消息隊(duì)列,在某些場景下可能也可以進(jìn)行二次利用。
參考資料:
1.Kafka vs. Pulsar vs. RabbitMQ: Performance, Architecture, and Features Compared
2.消息中間件選型分析:從Kafka與RabbitMQ的對比看全局
3.RabbitMQ的TTL(消息有效期)和DLX(死信交換機(jī)/隊(duì)列)
4.Apache Pulsar延遲消息投遞解析
5.深入理解RocketMQ延遲消息
6.三分鐘了解RocketMQ與Kafka的異同
7.個(gè)推基于Apache Pulsar的優(yōu)先級隊(duì)列方案
8.消息中間件選型分析:從Kafka與RabbitMQ的對比看全局
9.RocketMQ中消息的優(yōu)先級
10.消息隊(duì)列--NSQ&Kafka
11.三分鐘了解RocketMQ與Kafka的異同
12.告別傳統(tǒng)金融消息架構(gòu):Apache Pulsar在平安證券的實(shí)踐
13.【知識積累】MQ消息堆積和TTL過期
14.Pulsar官方文檔-租戶
15.Apache Pulsar的多租戶消息系統(tǒng)
?作者簡介
李明寬
騰訊教育后臺開發(fā)工程師
騰訊教育后臺開發(fā)工程師,畢業(yè)于中山大學(xué)。目前負(fù)責(zé)騰訊教育相關(guān)產(chǎn)品的后臺研發(fā)工作。
?推薦閱讀
在線教程!C++如何在云應(yīng)用中快速實(shí)現(xiàn)編譯優(yōu)化?
從C++轉(zhuǎn)向Rust:兩大主題值得關(guān)注!


