17 個(gè)方面,綜合對(duì)比 Kafka、RabbitMQ、RocketMQ、ActiveMQ 四個(gè)分布式消息隊(duì)列
?大家好,我是不才陳某~
本文將從,Kafka、RabbitMQ、ZeroMQ、RocketMQ、ActiveMQ 17 個(gè)方面綜合對(duì)比作為消息隊(duì)列使用時(shí)的差異。
1. 資料文檔
Kafka:中,有 kafka 作者自己寫的書(shū),網(wǎng)上資料也有一些。
rabbitmq:多,有一些不錯(cuò)的書(shū),網(wǎng)上資料多。
zeromq:少,沒(méi)有專門寫 zeromq 的書(shū),網(wǎng)上的資料多是一些代碼的實(shí)現(xiàn)和簡(jiǎn)單介紹。
rocketmq:少,沒(méi)有專門寫 rocketmq 的書(shū),網(wǎng)上的資料良莠不齊,官方文檔很簡(jiǎn)潔,但是對(duì)技術(shù)細(xì)節(jié)沒(méi)有過(guò)多的描述。
activemq:多,沒(méi)有專門寫 activemq 的書(shū),網(wǎng)上資料多。
2. 開(kāi)發(fā)語(yǔ)言
Kafka:Scala
rabbitmq:Erlang
zeromq:c
rocketmq:java
activemq:java
3. 支持的協(xié)議
Kafka:自己定義的一套...(基于 TCP)
rabbitmq:AMQP
zeromq:TCP、UDP
rocketmq:自己定義的一套...
activemq:OpenWire、STOMP、REST、XMPP、AMQP
4. 消息存儲(chǔ)
Kafka:內(nèi)存、磁盤、數(shù)據(jù)庫(kù)。支持大量堆積。
kafka 的最小存儲(chǔ)單元是分區(qū),一個(gè) topic 包含多個(gè)分區(qū),kafka 創(chuàng)建主題時(shí),這些分區(qū)會(huì)被分配在多個(gè)服務(wù)器上,通常一個(gè) broker 一臺(tái)服務(wù)器。分區(qū)首領(lǐng)會(huì)均勻地分布在不同的服務(wù)器上,分區(qū)副本也會(huì)均勻的分布在不同的服務(wù)器上,確保負(fù)載均衡和高可用性,當(dāng)新的 broker 加入集群的時(shí)候,部分副本會(huì)被移動(dòng)到新的 broker 上。根據(jù)配置文件中的目錄清單,kafka 會(huì)把新的分區(qū)分配給目錄清單里分區(qū)數(shù)最少的目錄。默認(rèn)情況下,分區(qū)器使用輪詢算法把消息均衡地分布在同一個(gè)主題的不同分區(qū)中,對(duì)于發(fā)送時(shí)指定了 key 的情況,會(huì)根據(jù) key 的 hashcode 取模后的值存到對(duì)應(yīng)的分區(qū)中。
rabbitmq:內(nèi)存、磁盤。支持少量堆積。
rabbitmq 的消息分為持久化的消息和非持久化消息,不管是持久化的消息還是非持久化的消息都可以寫入到磁盤。持久化的消息在到達(dá)隊(duì)列時(shí)就寫入到磁盤,并且如果可以,持久化的消息也會(huì)在內(nèi)存中保存一份備份,這樣可以提高一定的性能,當(dāng)內(nèi)存吃緊的時(shí)候會(huì)從內(nèi)存中清除。非持久化的消息一般只存在于內(nèi)存中,在內(nèi)存吃緊的時(shí)候會(huì)被換入到磁盤中,以節(jié)省內(nèi)存。
引入鏡像隊(duì)列機(jī)制,可將重要隊(duì)列“復(fù)制”到集群中的其他 broker 上,保證這些隊(duì)列的消息不會(huì)丟失。配置鏡像的隊(duì)列,都包含一個(gè)主節(jié)點(diǎn) master 和多個(gè)從節(jié)點(diǎn) slave,如果 master 失效,加入時(shí)間最長(zhǎng)的 slave 會(huì)被提升為新的 master,除發(fā)送消息外的所有動(dòng)作都向 master 發(fā)送,然后由 master 將命令執(zhí)行結(jié)果廣播給各個(gè) slave,rabbitmq 會(huì)讓 master 均勻地分布在不同的服務(wù)器上,而同一個(gè)隊(duì)列的 slave 也會(huì)均勻地分布在不同的服務(wù)器上,保證負(fù)載均衡和高可用性。
zeromq:消息發(fā)送端的內(nèi)存或者磁盤中。不支持持久化。
rocketmq:磁盤。支持大量堆積。
commitLog 文件存放實(shí)際的消息數(shù)據(jù),每個(gè) commitLog 上限是 1G,滿了之后會(huì)自動(dòng)新建一個(gè) commitLog 文件保存數(shù)據(jù)。ConsumeQueue 隊(duì)列只存放 offset、size、tagcode,非常小,分布在多個(gè) broker 上。ConsumeQueue 相當(dāng)于 CommitLog 的索引文件,消費(fèi)者消費(fèi)時(shí)會(huì)從 consumeQueue 中查找消息在 commitLog 中的 offset,再去 commitLog 中查找元數(shù)據(jù)。關(guān)注工眾號(hào):碼猿技術(shù)專欄,回復(fù)關(guān)鍵詞:1111 獲取阿里內(nèi)部Java性能調(diào)優(yōu)手冊(cè)!ConsumeQueue 存儲(chǔ)格式的特性,保證了寫過(guò)程的順序?qū)懕P(寫 CommitLog 文件),大量數(shù)據(jù) IO 都在順序?qū)懲粋€(gè) commitLog,滿 1G 了再寫新的。加上 rocketmq 是累計(jì) 4K 才強(qiáng)制從 PageCache 中刷到磁盤(緩存),所以高并發(fā)寫性能突出。
activemq:內(nèi)存、磁盤、數(shù)據(jù)庫(kù)。支持少量堆積。
5. 消息事務(wù)
Kafka:支持
rabbitmq:支持??蛻舳藢⑿诺涝O(shè)置為事務(wù)模式,只有當(dāng)消息被 rabbitMq 接收,事務(wù)才能提交成功,否則在捕獲異常后進(jìn)行回滾。使用事務(wù)會(huì)使得性能有所下降
zeromq:不支持
rocketmq:支持
activemq:支持
6. 負(fù)載均衡
Kafka:支持負(fù)載均衡。
1、一個(gè) broker 通常就是一臺(tái)服務(wù)器節(jié)點(diǎn)。對(duì)于同一個(gè) Topic 的不同分區(qū),Kafka 會(huì)盡力將這些分區(qū)分布到不同的 Broker 服務(wù)器上,zookeeper 保存了 broker、主題和分區(qū)的元數(shù)據(jù)信息。分區(qū)首領(lǐng)會(huì)處理來(lái)自客戶端的生產(chǎn)請(qǐng)求,kafka 分區(qū)首領(lǐng)會(huì)被分配到不同的 broker 服務(wù)器上,讓不同的 broker 服務(wù)器共同分擔(dān)任務(wù)。
每一個(gè) broker 都緩存了元數(shù)據(jù)信息,客戶端可以從任意一個(gè) broker 獲取元數(shù)據(jù)信息并緩存起來(lái),根據(jù)元數(shù)據(jù)信息知道要往哪里發(fā)送請(qǐng)求。
2、kafka 的消費(fèi)者組訂閱同一個(gè) topic,會(huì)盡可能地使得每一個(gè)消費(fèi)者分配到相同數(shù)量的分區(qū),分?jǐn)傌?fù)載。
3、當(dāng)消費(fèi)者加入或者退出消費(fèi)者組的時(shí)候,還會(huì)觸發(fā)再均衡,為每一個(gè)消費(fèi)者重新分配分區(qū),分?jǐn)傌?fù)載。
kafka 的負(fù)載均衡大部分是自動(dòng)完成的,分區(qū)的創(chuàng)建也是 kafka 完成的,隱藏了很多細(xì)節(jié),避免了繁瑣的配置和人為疏忽造成的負(fù)載問(wèn)題。
4、發(fā)送端由 topic 和 key 來(lái)決定消息發(fā)往哪個(gè)分區(qū),如果 key 為 null,那么會(huì)使用輪詢算法將消息均衡地發(fā)送到同一個(gè) topic 的不同分區(qū)中。如果 key 不為 null,那么會(huì)根據(jù) key 的 hashcode 取模計(jì)算出要發(fā)往的分區(qū)。
rabbitmq:對(duì)負(fù)載均衡的支持不好。
1、消息被投遞到哪個(gè)隊(duì)列是由交換器和 key 決定的,交換器、路由鍵、隊(duì)列都需要手動(dòng)創(chuàng)建。
rabbitmq 客戶端發(fā)送消息要和 broker 建立連接,需要事先知道 broker 上有哪些交換器,有哪些隊(duì)列。通常要聲明要發(fā)送的目標(biāo)隊(duì)列,如果沒(méi)有目標(biāo)隊(duì)列,會(huì)在 broker 上創(chuàng)建一個(gè)隊(duì)列,如果有,就什么都不處理,接著往這個(gè)隊(duì)列發(fā)送消息。假設(shè)大部分繁重任務(wù)的隊(duì)列都創(chuàng)建在同一個(gè) broker 上,那么這個(gè) broker 的負(fù)載就會(huì)過(guò)大。(可以在上線前預(yù)先創(chuàng)建隊(duì)列,無(wú)需聲明要發(fā)送的隊(duì)列,但是發(fā)送時(shí)不會(huì)嘗試創(chuàng)建隊(duì)列,可能出現(xiàn)找不到隊(duì)列的問(wèn)題,rabbitmq 的備份交換器會(huì)把找不到隊(duì)列的消息保存到一個(gè)專門的隊(duì)列中,以便以后查詢使用)
使用鏡像隊(duì)列機(jī)制建立 rabbitmq 集群可以解決這個(gè)問(wèn)題,形成 master-slave 的架構(gòu),master 節(jié)點(diǎn)會(huì)均勻分布在不同的服務(wù)器上,讓每一臺(tái)服務(wù)器分?jǐn)傌?fù)載。slave 節(jié)點(diǎn)只是負(fù)責(zé)轉(zhuǎn)發(fā),在 master 失效時(shí)會(huì)選擇加入時(shí)間最長(zhǎng)的 slave 成為 master。
當(dāng)新節(jié)點(diǎn)加入鏡像隊(duì)列的時(shí)候,隊(duì)列中的消息不會(huì)同步到新的 slave 中,除非調(diào)用同步命令,但是調(diào)用命令后,隊(duì)列會(huì)阻塞,不能在生產(chǎn)環(huán)境中調(diào)用同步命令。
2、當(dāng) rabbitmq 隊(duì)列擁有多個(gè)消費(fèi)者的時(shí)候,隊(duì)列收到的消息將以輪詢的分發(fā)方式發(fā)送給消費(fèi)者。每條消息只會(huì)發(fā)送給訂閱列表里的一個(gè)消費(fèi)者,不會(huì)重復(fù)。
這種方式非常適合擴(kuò)展,而且是專門為并發(fā)程序設(shè)計(jì)的。
如果某些消費(fèi)者的任務(wù)比較繁重,那么可以設(shè)置 basicQos 限制信道上消費(fèi)者能保持的最大未確認(rèn)消息的數(shù)量,在達(dá)到上限時(shí),rabbitmq 不再向這個(gè)消費(fèi)者發(fā)送任何消息。
3、對(duì)于 rabbitmq 而言,客戶端與集群建立的 TCP 連接不是與集群中所有的節(jié)點(diǎn)建立連接,而是挑選其中一個(gè)節(jié)點(diǎn)建立連接。但是 rabbitmq 集群可以借助 HAProxy、LVS 技術(shù),或者在客戶端使用算法實(shí)現(xiàn)負(fù)載均衡,引入負(fù)載均衡之后,各個(gè)客戶端的連接可以分?jǐn)偟郊旱母鱾€(gè)節(jié)點(diǎn)之中。
客戶端均衡算法
- 輪詢法。按順序返回下一個(gè)服務(wù)器的連接地址。
- 加權(quán)輪詢法。給配置高、負(fù)載低的機(jī)器配置更高的權(quán)重,讓其處理更多的請(qǐng)求;而配置低、負(fù)載高的機(jī)器,給其分配較低的權(quán)重,降低其系統(tǒng)負(fù)載。
- 隨機(jī)法。隨機(jī)選取一個(gè)服務(wù)器的連接地址。
- 加權(quán)隨機(jī)法。按照概率隨機(jī)選取連接地址。
- 地址哈希法。通過(guò)哈希函數(shù)計(jì)算得到的一個(gè)數(shù)值,用該數(shù)值對(duì)服務(wù)器列表的大小進(jìn)行取模運(yùn)算。
- 最小連接數(shù)法。動(dòng)態(tài)選擇當(dāng)前連接數(shù)最少的一臺(tái)服務(wù)器的連接地址。
- zeromq:去中心化,不支持負(fù)載均衡。本身只是一個(gè)多線程網(wǎng)絡(luò)庫(kù)。
- rocketmq:支持負(fù)載均衡。
一個(gè) broker 通常是一個(gè)服務(wù)器節(jié)點(diǎn),broker 分為 master 和 slave,master 和 slave 存儲(chǔ)的數(shù)據(jù)一樣,slave 從 master 同步數(shù)據(jù)。
- nameserver 與每個(gè)集群成員保持心跳,保存著 Topic-Broker 路由信息,同一個(gè) topic 的隊(duì)列會(huì)分布在不同的服務(wù)器上。
- 發(fā)送消息通過(guò)輪詢隊(duì)列的方式發(fā)送,每個(gè)隊(duì)列接收平均的消息量。發(fā)送消息指定 topic、tags、keys,無(wú)法指定投遞到哪個(gè)隊(duì)列(沒(méi)有意義,集群消費(fèi)和廣播消費(fèi)跟消息存放在哪個(gè)隊(duì)列沒(méi)有關(guān)系)。
tags 選填,類似于 Gmail 為每封郵件設(shè)置的標(biāo)簽,方便服務(wù)器過(guò)濾使用。目前只支 持每個(gè)消息設(shè)置一個(gè) tag,所以也可以類比為 Notify 的 MessageType 概念。
keys 選填,代表這條消息的業(yè)務(wù)關(guān)鍵詞,服務(wù)器會(huì)根據(jù) keys 創(chuàng)建哈希索引,設(shè)置后, 可以在 Console 系統(tǒng)根據(jù) Topic、Keys 來(lái)查詢消息,由于是哈希索引,請(qǐng)盡可能 保證 key 唯一,例如訂單號(hào),商品 Id 等。
- rocketmq 的負(fù)載均衡策略規(guī)定:Consumer 數(shù)量應(yīng)該小于等于 Queue 數(shù)量,如果 Consumer 超過(guò) Queue 數(shù)量,那么多余的 Consumer 將不能消費(fèi)消息。這一點(diǎn)和 kafka 是一致的,rocketmq 會(huì)盡可能地為每一個(gè) Consumer 分配相同數(shù)量的隊(duì)列,分?jǐn)傌?fù)載。
activemq:支持負(fù)載均衡??梢曰?zookeeper 實(shí)現(xiàn)負(fù)載均衡。
7. 集群方式
Kafka:天然的‘Leader-Slave’無(wú)狀態(tài)集群,每臺(tái)服務(wù)器既是 Master 也是 Slave。
分區(qū)首領(lǐng)均勻地分布在不同的 kafka 服務(wù)器上,分區(qū)副本也均勻地分布在不同的 kafka 服務(wù)器上,所以每一臺(tái) kafka 服務(wù)器既含有分區(qū)首領(lǐng),同時(shí)又含有分區(qū)副本,每一臺(tái) kafka 服務(wù)器是某一臺(tái) kafka 服務(wù)器的 Slave,同時(shí)也是某一臺(tái) kafka 服務(wù)器的 leader。
kafka 的集群依賴于 zookeeper,zookeeper 支持熱擴(kuò)展,所有的 broker、消費(fèi)者、分區(qū)都可以動(dòng)態(tài)加入移除,而無(wú)需關(guān)閉服務(wù),與不依靠 zookeeper 集群的 mq 相比,這是最大的優(yōu)勢(shì)。
rabbitmq:支持簡(jiǎn)單集群,'復(fù)制'模式,對(duì)高級(jí)集群模式支持不好。
rabbitmq 的每一個(gè)節(jié)點(diǎn),不管是單一節(jié)點(diǎn)系統(tǒng)或者是集群中的一部分,要么是內(nèi)存節(jié)點(diǎn),要么是磁盤節(jié)點(diǎn),集群中至少要有一個(gè)是磁盤節(jié)點(diǎn)。
在 rabbitmq 集群中創(chuàng)建隊(duì)列,集群只會(huì)在單個(gè)節(jié)點(diǎn)創(chuàng)建隊(duì)列進(jìn)程和完整的隊(duì)列信息(元數(shù)據(jù)、狀態(tài)、內(nèi)容),而不是在所有節(jié)點(diǎn)上創(chuàng)建。引入鏡像隊(duì)列,可以避免單點(diǎn)故障,確保服務(wù)的可用性,但是需要人為地為某些重要的隊(duì)列配置鏡像。
zeromq:去中心化,不支持集群。
rocketmq:常用 多對(duì)'Master-Slave' 模式,開(kāi)源版本需手動(dòng)切換 Slave 變成 Master
Name Server 是一個(gè)幾乎無(wú)狀態(tài)節(jié)點(diǎn),可集群部署,節(jié)點(diǎn)之間無(wú)任何信息同步。
Broker 部署相對(duì)復(fù)雜,Broker 分為 Master 與 Slave,一個(gè) Master 可以對(duì)應(yīng)多個(gè) Slave,但是一個(gè) Slave 只能對(duì)應(yīng)一個(gè) Master,Master 與 Slave 的對(duì)應(yīng)關(guān)系通過(guò)指定相同的 BrokerName,不同的 BrokerId 來(lái)定義,BrokerId 為 0 表示 Master,非 0 表示 Slave。Master 也可以部署多個(gè)。每個(gè) Broker 與 Name Server 集群中的所有節(jié)點(diǎn)建立長(zhǎng)連接,定時(shí)注冊(cè) Topic 信息到所有 Name Server。
Producer 與 Name Server 集群中的其中一個(gè)節(jié)點(diǎn)(隨機(jī)選擇)建立長(zhǎng)連接,定期從 Name Server 取 Topic 路由信息,并向提供 Topic 服務(wù)的 Master 建立長(zhǎng)連接,且定時(shí)向 Master 發(fā)送心跳。Producer 完全無(wú)狀態(tài),可集群部署。
Consumer 與 Name Server 集群中的其中一個(gè)節(jié)點(diǎn)(隨機(jī)選擇)建立長(zhǎng)連接,定期從 Name Server 取 Topic 路由信息,并向提供 Topic 服務(wù)的 Master、Slave 建立長(zhǎng)連接,且定時(shí)向 Master、Slave 發(fā)送心跳。Consumer 既可以從 Master 訂閱消息,也可以從 Slave 訂閱消息,訂閱規(guī)則由 Broker 配置決定。
客戶端先找到 NameServer, 然后通過(guò) NameServer 再找到 Broker。
一個(gè) topic 有多個(gè)隊(duì)列,這些隊(duì)列會(huì)均勻地分布在不同的 broker 服務(wù)器上。rocketmq 隊(duì)列的概念和 kafka 的分區(qū)概念是基本一致的,kafka 同一個(gè) topic 的分區(qū)盡可能地分布在不同的 broker 上,分區(qū)副本也會(huì)分布在不同的 broker 上。
rocketmq 集群的 slave 會(huì)從 master 拉取數(shù)據(jù)備份,master 分布在不同的 broker 上。
activemq:支持簡(jiǎn)單集群模式,比如'主-備',對(duì)高級(jí)集群模式支持不好。
8. 管理界面
Kafka:一般
rabbitmq:好
zeromq:無(wú)
rocketmq:無(wú)
activemq:一般
9. 可用性
Kafka:非常高(分布式)
rabbitmq:高(主從)
zeromq:高
rocketmq:非常高(分布式)
activemq:高(主從)
10. 消息重復(fù)
Kafka:支持 at least once、at most once
rabbitmq:支持 at least once、at most once
zeromq:只有重傳機(jī)制,但是沒(méi)有持久化,消息丟了重傳也沒(méi)有用。既不是 at least once、也不是 at most once、更不是 exactly only once
rocketmq:支持 at least once
activemq:支持 at least once
11. 吞吐量 TPS
Kafka:極大 Kafka 按批次發(fā)送消息和消費(fèi)消息。發(fā)送端將多個(gè)小消息合并,批量發(fā)向 Broker,消費(fèi)端每次取出一個(gè)批次的消息批量處理。
rabbitmq:比較大
zeromq:極大
rocketmq:大
rocketMQ :接收端可以批量消費(fèi)消息,可以配置每次消費(fèi)的消息數(shù),但是發(fā)送端不是批量發(fā)送。
activemq:比較大
12. 訂閱形式和消息分發(fā)
Kafka:基于 topic 以及按照 topic 進(jìn)行正則匹配的發(fā)布訂閱模式。
【發(fā)送】
發(fā)送端由 topic 和 key 來(lái)決定消息發(fā)往哪個(gè)分區(qū),如果 key 為 null,那么會(huì)使用輪詢算法將消息均衡地發(fā)送到同一個(gè) topic 的不同分區(qū)中。如果 key 不為 null,那么會(huì)根據(jù) key 的 hashcode 取模計(jì)算出要發(fā)往的分區(qū)。
【接收】
- consumer 向群組協(xié)調(diào)器 broker 發(fā)送心跳來(lái)維持他們和群組的從屬關(guān)系以及他們對(duì)分區(qū)的所有權(quán)關(guān)系,所有權(quán)關(guān)系一旦被分配就不會(huì)改變除非發(fā)生再均衡(比如有一個(gè) consumer 加入或者離開(kāi) consumer group),consumer 只會(huì)從對(duì)應(yīng)的分區(qū)讀取消息。
- kafka 限制 consumer 個(gè)數(shù)要少于分區(qū)個(gè)數(shù),每個(gè)消息只會(huì)被同一個(gè) Consumer Group 的一個(gè) consumer 消費(fèi)(非廣播)。
- kafka 的 Consumer Group 訂閱同一個(gè) topic,會(huì)盡可能地使得每一個(gè) consumer 分配到相同數(shù)量的分區(qū),不同 Consumer Group 訂閱同一個(gè)主題相互獨(dú)立,同一個(gè)消息會(huì)被不同的 Consumer Group 處理。
rabbitmq:提供了 4 種:direct, topic ,Headers 和 fanout。
【發(fā)送】
先要聲明一個(gè)隊(duì)列,這個(gè)隊(duì)列會(huì)被創(chuàng)建或者已經(jīng)被創(chuàng)建,隊(duì)列是基本存儲(chǔ)單元。
由 exchange 和 key 決定消息存儲(chǔ)在哪個(gè)隊(duì)列。
direct:發(fā)送到和 bindingKey 完全匹配的隊(duì)列。
topic:路由 key 是含有"."的字符串,會(huì)發(fā)送到含有“*”、“#”進(jìn)行模糊匹配的 bingKey 對(duì)應(yīng)的隊(duì)列。
fanout:與 key 無(wú)關(guān),會(huì)發(fā)送到所有和 exchange 綁定的隊(duì)列
headers:與 key 無(wú)關(guān),消息內(nèi)容的 headers 屬性(一個(gè)鍵值對(duì))和綁定鍵值對(duì)完全匹配時(shí),會(huì)發(fā)送到此隊(duì)列。此方式性能低一般不用
【接收】
rabbitmq 的隊(duì)列是基本存儲(chǔ)單元,不再被分區(qū)或者分片,對(duì)于我們已經(jīng)創(chuàng)建了的隊(duì)列,消費(fèi)端要指定從哪一個(gè)隊(duì)列接收消息。
當(dāng) rabbitmq 隊(duì)列擁有多個(gè)消費(fèi)者的時(shí)候,隊(duì)列收到的消息將以輪詢的分發(fā)方式發(fā)送給消費(fèi)者。每條消息只會(huì)發(fā)送給訂閱列表里的一個(gè)消費(fèi)者,不會(huì)重復(fù)。
這種方式非常適合擴(kuò)展,而且是專門為并發(fā)程序設(shè)計(jì)的。
如果某些消費(fèi)者的任務(wù)比較繁重,那么可以設(shè)置 basicQos 限制信道上消費(fèi)者能保持的最大未確認(rèn)消息的數(shù)量,在達(dá)到上限時(shí),rabbitmq 不再向這個(gè)消費(fèi)者發(fā)送任何消息。
zeromq:點(diǎn)對(duì)點(diǎn)(p2p)
rocketmq:基于 topic/messageTag 以及按照消息類型、屬性進(jìn)行正則匹配的發(fā)布訂閱模式
【發(fā)送】
發(fā)送消息通過(guò)輪詢隊(duì)列的方式發(fā)送,每個(gè)隊(duì)列接收平均的消息量。發(fā)送消息指定 topic、tags、keys,無(wú)法指定投遞到哪個(gè)隊(duì)列(沒(méi)有意義,集群消費(fèi)和廣播消費(fèi)跟消息存放在哪個(gè)隊(duì)列沒(méi)有關(guān)系)。
tags 選填,類似于 Gmail 為每封郵件設(shè)置的標(biāo)簽,方便服務(wù)器過(guò)濾使用。目前只支 持每個(gè)消息設(shè)置一個(gè) tag,所以也可以類比為 Notify 的 MessageType 概念。
keys 選填,代表這條消息的業(yè)務(wù)關(guān)鍵詞,服務(wù)器會(huì)根據(jù) keys 創(chuàng)建哈希索引,設(shè)置后, 可以在 Console 系統(tǒng)根據(jù) Topic、Keys 來(lái)查詢消息,由于是哈希索引,請(qǐng)盡可能 保證 key 唯一,例如訂單號(hào),商品 Id 等。
【接收】
- 廣播消費(fèi)。一條消息被多個(gè) Consumer 消費(fèi),即使 Consumer 屬于同一個(gè) ConsumerGroup,消息也會(huì)被 ConsumerGroup 中的每個(gè) Consumer 都消費(fèi)一次。
- 集群消費(fèi)。一個(gè) Consumer Group 中的 Consumer 實(shí)例平均分?jǐn)傁M(fèi)消息。例如某個(gè) Topic 有 9 條消息,其中一個(gè) Consumer Group 有 3 個(gè)實(shí)例,那么每個(gè)實(shí)例只消費(fèi)其中的 3 條消息。即每一個(gè)隊(duì)列都把消息輪流分發(fā)給每個(gè) consumer。
activemq:點(diǎn)對(duì)點(diǎn)(p2p)、廣播(發(fā)布-訂閱)
點(diǎn)對(duì)點(diǎn)模式,每個(gè)消息只有 1 個(gè)消費(fèi)者;
發(fā)布/訂閱模式,每個(gè)消息可以有多個(gè)消費(fèi)者。
【發(fā)送】
點(diǎn)對(duì)點(diǎn)模式:先要指定一個(gè)隊(duì)列,這個(gè)隊(duì)列會(huì)被創(chuàng)建或者已經(jīng)被創(chuàng)建。
發(fā)布/訂閱模式:先要指定一個(gè) topic,這個(gè) topic 會(huì)被創(chuàng)建或者已經(jīng)被創(chuàng)建。
【接收】
點(diǎn)對(duì)點(diǎn)模式:對(duì)于已經(jīng)創(chuàng)建了的隊(duì)列,消費(fèi)端要指定從哪一個(gè)隊(duì)列接收消息。
發(fā)布/訂閱模式:對(duì)于已經(jīng)創(chuàng)建了的 topic,消費(fèi)端要指定訂閱哪一個(gè) topic 的消息。
13. 順序消息
Kafka:支持。
設(shè)置生產(chǎn)者的 max.in.flight.requests.per.connection 為 1,可以保證消息是按照發(fā)送順序?qū)懭敕?wù)器的,即使發(fā)生了重試。kafka 保證同一個(gè)分區(qū)里的消息是有序的,但是這種有序分兩種情況
- key 為 null,消息逐個(gè)被寫入不同主機(jī)的分區(qū)中,但是對(duì)于每個(gè)分區(qū)依然是有序的
- key 不為 null , 消息被寫入到同一個(gè)分區(qū),這個(gè)分區(qū)的消息都是有序。
rabbitmq:不支持
zeromq:不支持
rocketmq:支持
activemq:不支持
14. 消息確認(rèn)
Kafka:支持。
- 發(fā)送方確認(rèn)機(jī)制
ack=0,不管消息是否成功寫入分區(qū)
ack=1,消息成功寫入首領(lǐng)分區(qū)后,返回成功
ack=all,消息成功寫入所有分區(qū)后,返回成功。
- 接收方確認(rèn)機(jī)制
自動(dòng)或者手動(dòng)提交分區(qū)偏移量,早期版本的 kafka 偏移量是提交給 Zookeeper 的,這樣使得 zookeeper 的壓力比較大,更新版本的 kafka 的偏移量是提交給 kafka 服務(wù)器的,不再依賴于 zookeeper 群組,集群的性能更加穩(wěn)定。
rabbitmq:支持。
- 發(fā)送方確認(rèn)機(jī)制,消息被投遞到所有匹配的隊(duì)列后,返回成功。如果消息和隊(duì)列是可持久化的,那么在寫入磁盤后,返回成功。支持批量確認(rèn)和異步確認(rèn)。
- 接收方確認(rèn)機(jī)制,設(shè)置 autoAck 為 false,需要顯式確認(rèn),設(shè)置 autoAck 為 true,自動(dòng)確認(rèn)。
當(dāng) autoAck 為 false 的時(shí)候,rabbitmq 隊(duì)列會(huì)分成兩部分,一部分是等待投遞給 consumer 的消息,一部分是已經(jīng)投遞但是沒(méi)收到確認(rèn)的消息。如果一直沒(méi)有收到確認(rèn)信號(hào),并且 consumer 已經(jīng)斷開(kāi)連接,rabbitmq 會(huì)安排這個(gè)消息重新進(jìn)入隊(duì)列,投遞給原來(lái)的消費(fèi)者或者下一個(gè)消費(fèi)者。
未確認(rèn)的消息不會(huì)有過(guò)期時(shí)間,如果一直沒(méi)有確認(rèn),并且沒(méi)有斷開(kāi)連接,rabbitmq 會(huì)一直等待,rabbitmq 允許一條消息處理的時(shí)間可以很久很久。
zeromq:支持。
rocketmq:支持。
activemq:支持。
15. 消息回溯
Kafka:支持指定分區(qū) offset 位置的回溯
rabbitmq:不支持
zeromq:不支持
rocketmq:支持指定時(shí)間點(diǎn)的回溯
activemq:不支持
16. 消息重試
Kafka:不支持,但是可以實(shí)現(xiàn)。
kafka 支持指定分區(qū) offset 位置的回溯,可以實(shí)現(xiàn)消息重試。
rabbitmq:不支持,但是可以利用消息確認(rèn)機(jī)制實(shí)現(xiàn)
rabbitmq 接收方確認(rèn)機(jī)制,設(shè)置 autoAck 為 false
當(dāng) autoAck 為 false 的時(shí)候,rabbitmq 隊(duì)列會(huì)分成兩部分,一部分是等待投遞給 consumer 的消息,一部分是已經(jīng)投遞但是沒(méi)收到確認(rèn)的消息。如果一直沒(méi)有收到確認(rèn)信號(hào),并且 consumer 已經(jīng)斷開(kāi)連接,rabbitmq 會(huì)安排這個(gè)消息重新進(jìn)入隊(duì)列,投遞給原來(lái)的消費(fèi)者或者下一個(gè)消費(fèi)者。
zeromq:不支持
rocketmq:支持
消息消費(fèi)失敗的大部分場(chǎng)景下,立即重試 99%都會(huì)失敗,所以 rocketmq 的策略是在消費(fèi)失敗時(shí)定時(shí)重試,每次時(shí)間間隔相同。
- 發(fā)送端的 send 方法本身支持內(nèi)部重試,重試邏輯如下:
a)至多重試 3 次;
b)如果發(fā)送失敗,則輪轉(zhuǎn)到下一個(gè) broker;
c)這個(gè)方法的總耗時(shí)不超過(guò) sendMsgTimeout 設(shè)置的值,默認(rèn) 10s,超過(guò)時(shí)間不在重試。
- 接收端。
Consumer 消費(fèi)消息失敗后,要提供一種重試機(jī)制,令消息再消費(fèi)一次。Consumer 消費(fèi)消息失敗通??梢苑譃橐韵聝煞N情況:
由于消息本身的原因,例如反序列化失敗,消息數(shù)據(jù)本身無(wú)法處理(例如話費(fèi)充值,當(dāng)前消息的手機(jī)號(hào)被注銷,無(wú)法充值)等。定時(shí)重試機(jī)制,比如過(guò) 10s 秒后再重試。
由于依賴的下游應(yīng)用服務(wù)不可用,例如 db 連接不可用,外系統(tǒng)網(wǎng)絡(luò)不可達(dá)等。
即使跳過(guò)當(dāng)前失敗的消息,消費(fèi)其他消息同樣也會(huì)報(bào)錯(cuò)。這種情況可以 sleep 30s,再消費(fèi)下一條消息,減輕 Broker 重試消息的壓力。
activemq:不支持
17. 并發(fā)度
Kafka:高
一個(gè)線程一個(gè)消費(fèi)者,kafka 限制消費(fèi)者的個(gè)數(shù)要小于等于分區(qū)數(shù),如果要提高并行度,可以在消費(fèi)者中再開(kāi)啟多線程,或者增加 consumer 實(shí)例數(shù)量。
rabbitmq:極高
本身是用 Erlang 語(yǔ)言寫的,并發(fā)性能高。
可在消費(fèi)者中開(kāi)啟多線程,最常用的做法是一個(gè) channel 對(duì)應(yīng)一個(gè)消費(fèi)者,每一個(gè)線程把持一個(gè) channel,多個(gè)線程復(fù)用 connection 的 tcp 連接,減少性能開(kāi)銷。
當(dāng) rabbitmq 隊(duì)列擁有多個(gè)消費(fèi)者的時(shí)候,隊(duì)列收到的消息將以輪詢的分發(fā)方式發(fā)送給消費(fèi)者。每條消息只會(huì)發(fā)送給訂閱列表里的一個(gè)消費(fèi)者,不會(huì)重復(fù)。
這種方式非常適合擴(kuò)展,而且是專門為并發(fā)程序設(shè)計(jì)的。
如果某些消費(fèi)者的任務(wù)比較繁重,那么可以設(shè)置 basicQos 限制信道上消費(fèi)者能保持的最大未確認(rèn)消息的數(shù)量,在達(dá)到上限時(shí),rabbitmq 不再向這個(gè)消費(fèi)者發(fā)送任何消息。
zeromq:高
rocketmq:高
- rocketmq 限制消費(fèi)者的個(gè)數(shù)少于等于隊(duì)列數(shù),但是可以在消費(fèi)者中再開(kāi)啟多線程,這一點(diǎn)和 kafka 是一致的,提高并行度的方法相同。
修改消費(fèi)并行度方法
a) 同一個(gè) ConsumerGroup 下,通過(guò)增加 Consumer 實(shí)例數(shù)量來(lái)提高并行度,超過(guò)訂閱隊(duì)列數(shù)的 Consumer 實(shí)例無(wú)效。
b) 提高單個(gè) Consumer 的消費(fèi)并行線程,通過(guò)修改參數(shù) consumeThreadMin、consumeThreadMax
- 同一個(gè)網(wǎng)絡(luò)連接 connection,客戶端多個(gè)線程可以同時(shí)發(fā)送請(qǐng)求,連接會(huì)被復(fù)用,減少性能開(kāi)銷。
activemq:高
單個(gè) ActiveMQ 的接收和消費(fèi)消息的速度在 1 萬(wàn)筆/秒(持久化 一般為 1-2 萬(wàn), 非持久化 2 萬(wàn)以上),在生產(chǎn)環(huán)境中部署 10 個(gè) Activemq 就能達(dá)到 10 萬(wàn)筆/秒以上的性能,部署越多的 activemq broker 在 MQ 上 latency 也就越低,系統(tǒng)吞吐量也就越高。