Kafka | RabbitMQ | RocketMQ | ActiveMQ | |
---|---|---|---|---|
資料文檔 | 中。有kafka作者自己寫的書,網上資料也有一些 | 稍微長一點的文本 | 多。有一些不錯的書,網上資料多 | 少。沒有專門寫rocketmq的書,網上的資料良莠不齊,官方文檔很簡潔,但是對技術細節沒有過多的描述 |
開發語言 | Scala | Erlang | java | java |
支持的協議 | 自己定義的一套…(基於TCP) | AMQP | 自己定義的一套… | OpenWire、STOMP、REST、XMPP、AMQP |
消息存儲 | Kafka:內存、磁盤、數據庫。支持大量堆積。kafka的最小存儲單元是分區,一個topic包含多個分區,kafka創建主題時,這些分區會被分配在多個服務器上,通常一個broker一臺服務器。 分區首領會均勻地分佈在不同的服務器上,分區副本也會均勻的分佈在不同的服務器上,確保負載均衡和高可用性,當新的broker加入集羣的時候,部分副本會被移動到新的broker上。 根據配置文件中的目錄清單,kafka會把新的分區分配給目錄清單裏分區數最少的目錄。 默認情況下,分區器使用輪詢算法把消息均衡地分佈在同一個主題的不同分區中,對於發送時指定了key的情況,會根據key的hashcode取模後的值存到對應的分區中。 | **內存、磁盤。支持少量堆積。**rabbitmq的消息分爲持久化的消息和非持久化消息,不管是持久化的消息還是非持久化的消息都可以寫入到磁盤。持久化的消息在到達隊列時就寫入到磁盤,並且如果可以,持久化的消息也會在內存中保存一份備份,這樣可以提高一定的性能,當內存喫緊的時候會從內存中清除。非持久化的消息一般只存在於內存中,在內存喫緊的時候會被換入到磁盤中,以節省內存。引入鏡像隊列機制,可將重要隊列“複製”到集羣中的其他broker上,保證這些隊列的消息不會丟失。配置鏡像的隊列,都包含一個主節點master和多個從節點slave,如果master失效,加入時間最長的slave會被提升爲新的master,除發送消息外的所有動作都向master發送,然後由master將命令執行結果廣播給各個slave,rabbitmq會讓master均勻地分佈在不同的服務器上,而同一個隊列的slave也會均勻地分佈在不同的服務器上,保證負載均衡和高可用性。 | 磁盤。支持大量堆積 commitLog文件存放實際的消息數據,每個commitLog上限是1G,滿了之後會自動新建一個commitLog文件保存數據。ConsumeQueue隊列只存放offset、size、tagcode,非常小,分佈在多個broker上。ConsumeQueue相當於CommitLog的索引文件,消費者消費時會從consumeQueue中查找消息在commitLog中的offset,再去commitLog中查找元數據。ConsumeQueue存儲格式的特性,保證了寫過程的順序寫盤(寫CommitLog文件),大量數據IO都在順序寫同一個commitLog,滿1G了再寫新的。加上rocketmq是累計4K才強制從PageCache中刷到磁盤(緩存),所以高併發寫性能突出。 | 內存、磁盤、數據庫。支持少量堆積 |
消息事務 | 支持 | 支持。客戶端將信道設置爲事務模式,只有當消息被rabbitMq接收,事務才能提交成功,否則在捕獲異常後進行回滾。使用事務會使得性能有所下降 | 支持 | 支持 |
負載均衡 | ||||
集羣方式 | ||||
管理界面 | 一般 | 好 | 無 | 一般 |
可用性 | 非常高(分佈式) | 高(主從) | 非常高(分佈式) | 高(主從) |
消息重複 | 支持at least once、at most once | 支持at least once、at most once | 支持at least once | 支持at least once |
吞吐量TPS | (十萬級,17萬)極大Kafka按批次發送消息和消費消息。發送端將多個小消息合併,批量發向Broker,消費端每次取出一個批次的消息批量處理 | (萬級別,5萬)比較大 | (十萬級別)大 rocketMQ接收端可以批量消費消息,可以配置每次消費的消息數,但是發送端不是批量發送 | (萬級別)比較大 |
訂閱形式和消息分發 | ||||
順序消息 | 設置生產者的max.in.flight.requests.per.connection爲1, 可以保證消息是按照發送順序寫入服務器的,即使發生了重試. Kafka保證同一個分區的消息是有序的, 但是這種分兩種情況: key爲null, 消息逐個寫入不同的主機分區中, 但是對於每個分區依然是有序的. key不爲null, 消息被寫入同一個分區,這個分區的消息都是有序的 | 不支持 | 不支持 | 不支持 |
消息確認 | 支持 | 支持 | 支持 | 支持 |
消息回溯 | 支持指定分區offset位置的回溯 | 不支持 | 支持指定時間點的回溯 | 不支持 |
消息重試 | ||||
併發度 |
負載均衡
Kafka: 支持負載均衡
-
一個broker通常就是一臺服務器節點. 對於同一個Topic的不同分區, Kafka會盡力將這些分區分佈刀不同的Broker服務器上, zookeeper保存了broker, 主題和分區的元數據信息. 分區首領會處理來自客戶端的生產請求, Kafka分區首領會被分配到不同的zookeeper服務器上, 讓不同的broker服務器共同分擔任務.
每一個broker都緩存元數據信息, 客戶端可以從任意一個broker獲取元數據信息並緩存起來, 根據元數據信息知道要往哪裏發送請求.
-
Kafka的消費者組訂閱同一個topic, 會盡可能地使得每一個消費者分配到相同數量的分區, 分攤負載.
-
當消費者組訂閱了同一個topic, 還會促發再均衡, 爲每一個消費者重新分配分區, 分攤負責。
Kafka的負載均衡大部分是自動完成的, 分區的創建也是Kafka完成的, 隱藏了許多細節, 避免了繁瑣的配置和人爲疏忽造成的負載問題。
4.發送端由topic和key來決定消息發送到那個分區,如果key爲null,那麼會使用輪詢算法將消息負載發送到同一個topic的不同分區中。 如果key不爲null,那麼會根據key的hashcode取模計算要發往的分區。
-
rabbitmq:對負載均衡的支持不好
1.消息被投遞到那個隊列是由交換機和key決定的, 交換器,路由鍵, 隊列都需要手動創建
rabbitMQ客戶端發送消息要和broker建立連接, 需要事先知道broker有那些交換器, 有那些隊列。通常要聲明發送的目標隊列, 如果沒有目標隊列, 會在broker上創建一個隊列,如果有,就什麼都不處理, 接着往這個隊列發送消息。假設大部分繁瑣的任務都在同一個broker上,那這個broker的負載會過大。(可以在上線前預先創建隊列,無需聲明要發送的隊列,但是發送時不會嘗試去創建隊列,可能出現找不到隊列的問題, rabbitmq的備份交換器會把找不到隊列的消息保存到一個專門的隊列中,以便以後查詢用)
使用鏡像隊列機制建立rabbitmq集羣可以解決這個問題, 形成master-slave的架構, master節點會均勻分佈在不同的服務器上, 讓每一臺服務器分攤負載. slave節點只是負責轉發, 在master失效時會選擇加入時間最長的slaver成爲master
當新節點加入鏡像隊列的時候, 隊列中的消息不會同步到新的slaver中,除非調用同步命令, 但是調用命令後, 隊列會阻塞, 不能在生產環境中調用同步命令
2.當rabbitmq隊列擁有多個消費者的時候,隊列收到的消息將以輪詢的分發方式發送給消費者。每條消息只會發送給訂閱列表裏的一個消費者,不會重複。
這種方式非常適合擴展,而且是專門爲併發程序設計的。
如果某些消費者的任務比較繁重,那麼可以設置basicQos限制信道上消費者能保持的最大未確認消息的數量,在達到上限時,rabbitmq不再向這個消費者發送任何消息。
3.對於rabbitmq而言,客戶端與集羣建立的TCP連接不是與集羣中所有的節點建立連接,而是挑選其中一個節點建立連接。
但是rabbitmq集羣可以藉助HAProxy、LVS技術,或者在客戶端使用算法實現負載均衡,引入負載均衡之後,各個客戶端的連接可以分攤到集羣的各個節點之中。
rocketmq:支持負載均衡
一個broker通常是一個服務器節點,broker分爲master和slave,master和slave存儲的數據一樣,slave從master同步數據。
1.nameserver與每個集羣成員保持心跳,保存着Topic-Broker路由信息,同一個topic的隊列會分佈在不同的服務器上。
2.發送消息通過輪詢隊列的方式發送,每個隊列接收平均的消息量。發送消息指定topic、tags、keys,無法指定投遞到哪個隊列(沒有意義,集羣消費和廣播消費跟消息存放在哪個隊列沒有關係)。
tags選填,類似於 Gmail 爲每封郵件設置的標籤,方便服務器過濾使用。目前只支 持每個消息設置一個 tag,所以也可以類比爲 Notify 的 MessageType 概念。
keys選填,代表這條消息的業務關鍵詞,服務器會根據 keys 創建哈希索引,設置後, 可以在 Console 系統根據 Topic、Keys 來查詢消息,由於是哈希索引,請儘可能 保證 key 唯一,例如訂單號,商品 Id 等。
3.rocketmq的負載均衡策略規定:Consumer數量應該小於等於Queue數量,如果Consumer超過Queue數量,那麼多餘的Consumer 將不能消費消息。這一點和kafka是一致的,rocketmq會盡可能地爲每一個Consumer分配相同數量的隊列,分攤負載。
activemq:支持負載均衡。可以基於zookeeper實現負載均衡
集羣方式
Kafka:天然的’Leader-Slave’無狀態集羣, 每臺服務既是Master也是Slave
分區首領均勻地分佈在不同的Kafka服務器上, 分區副本業均勻地分佈在不同的Kafka服務器上, 所以每臺kafka服務既含分區首領, 同時又含有分區副本, 每一臺kafka服務器是某一臺kafka服務器的Slave, 同時也是某一臺kafka服務器的leader
kafka的集羣依賴與zookeeper, zookeeper支持熱拓展, 所有的broker, 消費者, 分區都可以動態移除, 而無需關閉服務, 與不依賴zookeeper集羣的mq相比, 這是最大的優勢.
rabbitmq:支持簡單集羣,'複製’模式,對高級集羣模式支持不好
rabbitmq的每一個節點,不管是單一節點系統或者是集羣中的一部分,要麼是內存節點,要麼是磁盤節點,集羣中至少要有一個是磁盤節點。
在rabbitmq集羣中創建隊列,集羣只會在單個節點創建隊列進程和完整的隊列信息(元數據、狀態、內容),而不是在所有節點上創建。沒有拓展性可言
引入鏡像隊列,可以避免單點故障,確保服務的可用性,但是需要人爲地爲某些重要的隊列配置鏡像。
rocketmq:常用 多對’Master-Slave’ 模式,開源版本需手動切換Slave變成Master
Name Server是一個幾乎無狀態節點,可集羣部署,節點之間無任何信息同步。
Broker部署相對複雜,Broker分爲Master與Slave,一個Master可以對應多個Slave,但是一個Slave只能對應一個Master,Master與Slave的對應關係通過指定相同的BrokerName,不同的BrokerId來定義,BrokerId爲0表示Master,非0表示Slave。Master也可以部署多個。每個Broker與Name Server集羣中的所有節點建立長連接,定時註冊Topic信息到所有Name Server。
Producer與Name Server集羣中的其中一個節點(隨機選擇)建立長連接,定期從Name Server取Topic路由信息,並向提供Topic服務的Master建立長連接,且定時向Master發送心跳。Producer完全無狀態,可集羣部署。
Consumer與Name Server集羣中的其中一個節點(隨機選擇)建立長連接,定期從Name Server取Topic路由信息,並向提供Topic服務的Master、Slave建立長連接,且定時向Master、Slave發送心跳。Consumer既可以從Master訂閱消息,也可以從Slave訂閱消息,訂閱規則由Broker配置決定。
客戶端先找到NameServer, 然後通過NameServer再找到 Broker。
一個topic有多個隊列,這些隊列會均勻地分佈在不同的broker服務器上。rocketmq隊列的概念和kafka的分區概念是基本一致的,kafka同一個topic的分區儘可能地分佈在不同的broker上,分區副本也會分佈在不同的broker上。
rocketmq集羣的slave會從master拉取數據備份,master分佈在不同的broker上。
activemq:支持簡單集羣模式,比如’主-備’,對高級集羣模式支持不好
訂閱形式和消息分發
Kafka:基於topic
以及按照topic進行正則匹配的發佈訂閱模式
[發送]
發送端由topic和key來決定消息將發往那個分區,如果key爲null, 那麼會使用輪詢算法將消息均衡地發送到同一個topic的不同分區中. 如果key不是null, 那麼會將key的hashcode取模計算出要發往的分區.
[接受]
1.consumer向羣組協調器broker發送心跳來維持他們和羣組的從屬關係以及他們對分區的從屬關係以及他們對分區的所有權關係,所有權關係一旦被分配到不會改變除非發生再均衡(比如有一個comsumer加入或者離開consumer group), consumer只會從對應的分區讀取消息.
2. Kafka限制consumer個數要**少於**分區個數, 每個消息只會被同一個consumer Group的一個消費者(非廣播) 3. Kafka的consumer group訂閱同一個topic, 會盡可能地使得每一個consumer分配到相同數量的分區,不同consumer group同一個主題互相獨立, 同一個消息會被不同的consumer group處理
rabbitmq:提供了4種:direct, topic ,Headers和fanout
[發送]
先聲明一個隊列, 這個隊列會被創建或者已經被創建, 隊列是基本的存儲單元.
有exchage和key決定消息隊列存儲到那個隊列
fanout>與key無關,會發送到所有和exchange綁定的隊列 (比如訂閱模式)
direct>發送到bindingKey完全匹配的隊列(比如路由模式)
topic>路由key是含有".“的字符串, 會發送到含有”*", "#"進行模糊匹配的bindingKey對應的隊列(比如topic模式, 也叫主題模式)
headers>與key無關, 消息內容的headers屬性(一個鍵值對)和綁定的鍵值對完全匹配時, 會發送此隊列. 此方式性能低一般不用.
[接受]
rabbitmq的隊列是基本存儲單元, 不在被分區或者分片, 對於我們已經創建了隊列,消費端要指定從那個隊列接收消息.
work模式, 路由模式, topic模式: 當rabbitmq隊列擁有多個consumer的時候, 每條消息只會發送給訂閱列表裏的一個consumer, 不會重複. 這種方式非常合適拓展, 而是專門爲併發程序設計的
PS: 如果某些消費者的任務比較繁重, 那麼可以設置basicQos
限制信道上消費者能保持的最大未確認消息
的數量,在達到上限時, rabbitmq不再
向這個消費發送
任何消息.
rocketmq:基於topic/messageTag以及按照消息類型、屬性進行正則匹配的發佈訂閱模式
activemq:點對點(p2p)、廣播(發佈-訂閱)
消息確認
Kafka:支持
發送機制:
ack=0, 不管消息是否成功寫入分區
ack=1, 消息成功寫入首領分區後,返回成功
ack=all, 消息成功寫入所有分區後, 返回成功
接收方確認機制:
自動或者手動提交分區偏移量,早期版本的kafka偏移量是提交給Zookeeper的,這樣使得zookeeper的壓力比較大,更新版本的kafka的偏移量是提交給kafka服務器的,不再依賴於zookeeper羣組,集羣的性能更加穩定
Rabbitmq:支持
發送方確認機制:
消息被投遞到所有匹配的隊列後,返回成功。如果消息和隊列是可持久化的,那麼在寫入磁盤後,返回成功。支持批量確認和異步確認
接收方確認機制
設置autoAck爲false,需要顯式確認,設置autoAck爲true,自動確認。
當autoAck爲false的時候,rabbitmq隊列會分成兩部分,一部分是等待投遞給consumer的消息,一部分是已經投遞但是沒收到確認的消息。如果一直沒有收到確認信號,並且consumer已經斷開連接,rabbitmq會安排這個消息重新進入隊列,投遞給原來的消費者或者下一個消費者。
未確認的消息不會有過期時間,如果一直沒有確認,並且沒有斷開連接,rabbitmq會一直等待,rabbitmq允許一條消息處理的時間可以很久很久,所以這裏有個疑問: 如果consumer設置autoAck=false, 然後在處理某消息的時候宕機了, 而MQ一直在等待怎麼辦? 還說說如果斷開連接了, MQ會將這個消息重發給consumer處理?
剛剛做了測試, 如果在consumer處理的時候, 手動殺死其進程, 好像rabbitmq會自動將這消息重新入隊
消息重試
Kafka: 不支持, 但是可以實現
Kafka支持指定分區offset位置的回溯, 可以實現消息重試.
rabbitmq, 不支持,但是可以利用消息確認機制實現
生產者:
等待補充!!!其實RabbitMQ幾乎可以保證99.99%發生成功, 但是偶爾也有失敗,
處理方式大概2種類: 1定時重發, 2記錄日誌人工處理, 如果是重要的消息可以發起系統預警
消費者:
當autoAck爲false的時候,rabbitmq隊列會分成兩部分,一部分是等待投遞給consumer的消息,一部分是已經投遞但是沒收到確認的消息。如果一直沒有收到確認信號,並且consumer已經斷開連接,rabbitmq會安排這個消息重新進入隊列,投遞給原來的消費者或者下一個消費者
如果消費者消費3次都失敗,可以根據業務情況進入死信隊列
rocketmq:支持
消息消費失敗的大部分場景下, 立即重試99%都會失敗, 所以rocketmq的策略是在消費失敗時定時重試,每次時間間隔相同.
生產者: (發送端的 send 方法本身支持內部重試)
a.最多重試3次
b.如果發送失敗, 則輪轉到下一個broker
c. 這個方法的總耗時不超過sendMsgTimeout 設置的值,默認 10s,超過時間不在重試
消費者: 消費消息失敗後,要提供一種重試機制,令消息再消費一次。Consumer 消費消息失敗通常可以分爲以下兩種情況:
1.由於消息本身的原因, 例如反序列化失敗, 消息數據本身無法處理(比如充話費的時候,當前手機號不存在). 定時重試機制,比如超過10s後再重試
2.由於依賴的下游應用服務器不可用, 例如db連接不可用, 外系統網絡不可達.
即使跳過當前失敗的消息,消費其他消息同樣也會報錯。這種情況可以 sleep 30s,再消費下一條消息,減輕 Broker 重試消息的壓力。
activemq:不支持
併發度
Kafka 高
一個線程一個消費者, Kafka限制消費者的個數要**<=**分區數,如果要提高並行度, 可以在消費者中開啓多線程, 或者增加consumer實例數量
RabbitMQ: 極高(下面這段文字上面有提起)
本身是用Erlang語言寫的,併發性能高。
可在消費者中開啓多線程,最常用的做法是一個channel對應一個消費者,每一個線程把持一個channel,多個線程複用connection的tcp連接,減少性能開銷。
當rabbitmq隊列擁有多個消費者的時候,隊列收到的消息將以輪詢的分發方式發送給消費者。每條消息只會發送給訂閱列表裏的一個消費者,不會重複。
這種方式非常適合擴展,而且是專門爲併發程序設計的。
如果某些消費者的任務比較繁重,那麼可以設置basicQos限制信道上消費者能保持的最大未確認消息的數量,在達到上限時,rabbitmq不再向這個消費者發送任何消息
rocketmq:高
-
rocketmq限制消費者個數**<=**隊列數,但是可以在消費者再開啓多線程, 這一點和Kafka是一致的,提高並行度的方法相同.
修改消費並行度方法:
a.同一個ConsumerGroup下, 通過增加Consumer實例數量來提高併發度, 超過訂閱隊列數的Consumer實例無效.
b.提高單個Consumer的消費並行線程, 通過修改參數consumeThreadMin, consumeThreadMax
2.同一個網絡連接connection, 客戶端多個線程連接可以同事發送請求,連接會被複用, 減少性能開銷.
activemq: 高
單個ActiveMQ的接收和消費消息的速度在1萬筆/秒(持久化 一般爲1-2萬, 非持久化 2 萬以上),在生產環境中部署10個Activemq就能達到10萬筆/秒以上的性能,部署越多的activemq broker 在MQ上latency也就越低,系統吞吐量也就越高