經常有人問我
有個 xx 需求,我應該用 Kafka 還是 RabbitMQ ?
這個問題很常見,而且很多人對二者的選擇也把握不好。
所以我決定寫篇文章來詳細說一下:Kafka 和 RabbitMQ 的區別,適用於什么場景?
同時,這個問題在面試中也經常問到。
下面我會通過 6 個場景,來對比分析一下 Kafka 和 RabbitMQ 的優劣。
一、消息的順序
有這樣一個需求:當訂單狀態變化的時候,把訂單狀態變化的消息發送給所有關心訂單變化的系統。
訂單會有創建成功、待付款、已支付、已發貨的狀態,狀態之間是單向流動的。
好,現在我們把訂單狀態變化消息要發送給所有關心訂單狀態的系統上去,實現方式就是用消息隊列。
在這種業務下,我們最想要的是什么?
-
消息的順序:對於同一筆訂單來說,狀態的變化都是有嚴格的先后順序的。
-
吞吐量:像訂單的業務,我們自然希望訂單越多越好。訂單越多,吞吐量就越大。
在這種情況下,我們先看看 RabbitMQ 是怎么做的。
首先,對於發消息,並廣播給多個消費者這種情況,RabbitMQ 會為每個消費者建立一個對應的隊列。也就是說,如果有 10 個消費者,RabbitMQ 會建立 10 個對應的隊列。然后,當一條消息被發出后,RabbitMQ 會把這條消息復制 10 份放到這 10 個隊列里。
當 RabbitMQ 把消息放入到對應的隊列后,我們緊接着面臨的問題就是,我們應該在系統內部啟動多少線程去從消息隊列中獲取消息。
如果只是單線程去獲取消息,那自然沒有什么好說的。但是多線程情況,可能就會有問題了……
RabbitMQ 有這么個特性,它在官方文檔就聲明了自己是不保證多線程消費同一個隊列的消息,一定保證順序的。而不保證的原因,是因為多線程時,當一個線程消費消息報錯的時候,RabbitMQ 會把消費失敗的消息再入隊,此時就可能出現亂序的情況。
T0 時刻,隊列中有四條消息 A1、B1、B2、A2。其中 A1、A2 表示訂單 A 的兩個狀態:待付款、已付款。B1、B2 也同理,是訂單 B 的待付款、已付款。
到了 T1 時刻,消息 A1 被線程 1 收到,消息 B1 被線程 2 收到。此時,一切都還正常。
到了 T3 時刻,B1 消費出錯了,同時呢,由於線程 1 處理速度快,又從消息隊列中獲取到了 B2。此時,問題開始出現。
到了 T4 時刻,由於 RabbitMQ 線程消費出錯,可以把消息重新入隊的特性,此時 B1 會被重新放到隊列頭部。所以,如果不湊巧,線程 1 獲取到了 B1,就出現了亂序情況,B2 狀態明明是 B1 的后續狀態,卻被提前處理了。
所以,可以看到了,這個場景用 RabbitMQ,出現了三個問題:
- 為了實現發布訂閱功能,從而使用的消息復制,會降低性能並耗費更多資源
- 多個消費者無法嚴格保證消息順序
- 大量的訂單集中在一個隊列,吞吐量受到了限制
那么 Kafka 怎么樣呢?Kafka 正好在這三個問題上,表現的要比 RabbitMQ 要好得多。
首先,Kafka 的發布訂閱並不會復制消息,因為 Kafka 的發布訂閱就是消費者直接去獲取被 Kafka 保存在日志文件中的消息就好。無論是多少消費者,他們只需要主動去找到消息在文件中的位置即可。
其次,Kafka 不會出現消費者出錯后,把消息重新入隊的現象。
最后,Kafka 可以對訂單進行分區,把不同訂單分到多個分區中保存,這樣,吞吐量能更好。
所以,對於這個需求 Kafka 更合適。
二、消息的匹配
我曾經做過一套營銷系統。這套系統中有個非常顯著的特點,就是非常復雜非常靈活地匹配規則。
比如,要根據推廣內容去匹配不同的方式做宣傳。又比如,要根據不同的活動去匹配不同的渠道去做分發。
總之,數不清的匹配規則是這套系統中非常重要的一個特點。
首先,先看看 RabbitMQ 的,你會發現 RabbitMQ 是允許在消息中添加 routing_key 或者自定義消息頭,然后通過一些特殊的 Exchange,很簡單的就實現了消息匹配分發。開發幾乎不用成本。
而 Kafka 呢?如果你要實現消息匹配,開發成本高多了。
首先,通過簡單的配置去自動匹配和分發到合適的消費者端這件事是不可能的。
其次,消費者端必須先把所有消息不管需要不需要,都取出來。然后,再根據業務需求,自己去實現各種精准和模糊匹配。可能因為過度的復雜性,還要引入規則引擎。
這個場景下 RabbitMQ 扳回一分。
三、消息的超時
在電商業務里,有個需求:下單之后,如果用戶在 15 分鍾內未支付,則自動取消訂單。
你可能奇怪,這種怎么也會用到消息隊列的?
我來先簡單解釋一下,在單一服務的系統,可以起個定時任務就搞定了。
但是,在 SOA 或者微服務架構下,這樣做就不行了。因為很多個服務都關心是否支付這件事,如果每種服務,都自己實現一套定時任務的邏輯,既重復,又難以維護。
在這種情況下,我們往往會做一層抽象:把要執行的任務封裝成消息。當時間到了,直接扔到消息隊列里,消息的訂閱者們獲取到消息后,直接執行即可。
希望把消息延遲一定時間再處理的,被稱為延遲隊列。
對於訂單取消的這種業務,我們就會在創建訂單的時候,同時扔一個包含了執行任務信息的消息到延遲隊列,指定15分鍾后,讓訂閱這個隊列的各個消費者,可以收到這個消息。隨后,各個消費者所在的系統就可以去執行相關的掃描訂單的任務了。
RabbitMQ 和 Kafka 消息隊列如何選?
先看下 RabbitMQ 的。
RabbitMQ 的消息自帶手表,消息中有個 TTL 字段,可以設置消息在 RabbitMQ 中的存放的時間,超時了會被移送到一個叫死信隊列的地方。
所以,延遲隊列 RabbitMQ 最簡單的實現方式就是設置 TTL,然后一個消費者去監聽死信隊列。當消息超時了,監聽死信隊列的消費者就收到消息了。
不過,這樣做有個大問題:假設,我們先往隊列放入一條過期時間是 10 秒的 A 消息,再放入一條過期時間是 5 秒的 B 消息。 那么問題來了,B 消息會先於 A 消息進入死信隊列嗎?
答案是否定的。B 消息會優先遵守隊列的先進先出規則,在 A 消息過期后,和其一起進入死信隊列被消費者消費。
在 RabbitMQ 的 3.5.8 版本以后,官方推薦的 rabbitmq delayed message exchange 插件可以解決這個問題。
- 用了這個插件,我們在發送消息的時候,把消息發往一個特殊的 Exchange。
- 同時,在消息頭里指定要延遲的時間。
- 收到消息的 Exchange 並不會立即把消息放到隊列里,而是在消息延遲時間到達后,才會把消息放入。
再看下 Kafka 的:
Kafka 要實現延遲隊列就很麻煩了。
- 你先需要把消息先放入一個臨時的 topic。
- 然后得自己開發一個做中轉的消費者。讓這個中間的消費者先去把消息從這個臨時的 topic 取出來。
- 取出來,這消息還不能馬上處理啊,因為沒到時間呢。也沒法保存在自己的內存里,怕崩潰了,消息沒了。所以,就得把沒有到時間的消息存入到數據庫里。
- 存入數據庫中的消息需要在時間到了之后再放入到 Kafka 里,以便真正的消費者去執行真正的業務邏輯。
- ……
想想就已經頭大了,這都快搞成調度平台了。再高級點,還要用時間輪算法才能更好更准確。
這次,RabbitMQ 上那一條條戴手表的消息,才是最好的選擇。
四、消息的保持
在微服務里,事件溯源模式是經常用到的。如果想用消息隊列實現,一般是把事件當成消息,依次發送到消息隊列中。
事件溯源有個最經典的場景,就是事件的重放。簡單來講就是把系統中某段時間發生的事件依次取出來再處理。而且,根據業務場景不同,這些事件重放很可能不是一次,更可能是重復 N 次。
假設,我們現在需要一批在線事件重放,去排查一些問題。
RabbitMQ 此時就真的不行了,因為消息被人取出來就被刪除了。想再次被重復消費?對不起。
而 Kafka 呢,消息會被持久化一個專門的日志文件里。不會因為被消費了就被刪除。
所以,對消息不離不棄的 Kafka 相對用過就拋的 RabbitMQ,請選擇 Kafka。
五、消息的錯誤處理
很多時候,在做記錄數據相關業務的時候,Kafka 一般是不二選擇。不過,有時候在記錄數據吞吐量不大時,我自己倒是更喜歡用 RabbitMQ。
原因就是 Kafka 有一個我很不喜歡的設計原則:
當單個分區中的消息一旦出現消費失敗,就只能停止而不是跳過這條失敗的消息繼續消費后面的消息。即不允許消息空洞。
只要消息出現失敗,不管是 Kafka 自身消息格式的損壞,還是消費者處理出現異常,是不允許跳過消費失敗的消息繼續往后消費的。
所以,在數據統計不要求十分精確的場景下選了 Kafka,一旦出現了消息消費問題,就會發生項目不可用的情況。這真是徒增煩惱。
而 RabbitMQ 呢,它由於會在消息出問題或者消費錯誤的時候,可以重新入隊或者移動消息到死信隊列,繼續消費后面的,會省心很多。
壞消息就像群眾中的壞蛋那樣,Kafka 處理這種壞蛋太過殘暴,非得把壞蛋揪出來不行。相對來說,RabbitMQ 就溫柔多了,群眾是群眾,壞蛋是壞蛋,分開處理嘛。
六、消息的吞吐量
Kafka 是每秒幾十萬條消息吞吐,而 RabbitMQ 的吞吐量是每秒幾萬條消息。
其實,在一家公司內部,有必須用到 Kafka 那么大吞吐量的項目真的很少。大部分項目,像 RabbitMQ 那樣每秒幾萬的消息吞吐,已經非常夠了。
在一些沒那么大吞吐量的項目中引入 Kafka,我覺得就不如引入 RabbitMQ。
為什么呢?
因為 Kafka 為了更好的吞吐量,很大程度上增加了自己的復雜度。而這些復雜度對項目來說,就是麻煩,主要體現在兩個方面:
1、配置復雜、維護復雜
Kafka 的參數配置相對 RabbitMQ 是很復雜的。比如:磁盤管理相關參數,集群管理相關參數,ZooKeeper 交互相關參數,Topic 級別相關參數等,都需要一些思考和調優。
另外,Kafka 本身集群和參與管理集群的 ZooKeeper,這就帶來了更多的維護成本。Kafka 要用好,你要考慮 JVM,消息持久化,集群本身交互,以及 ZooKeeper 本身和它與 Kafka 之間的可靠和效率。
2、用好,用對存在門檻
Kafka 的 Producer 和 Consumer 本身要用好用對也存在很高的門檻。
比如,Producer 消息可靠性保障、冪等性、事務消息等,都需要對 KafkaProducer 有深入的了解。
而 Consumer 更不用說了,光是一個日志偏移管理就讓一大堆人掉了不少頭發。
相對來說,RabbitMQ 就簡單得多。你可能都不用配置什么,直接啟動起來就能很穩定可靠地使用了。就算配置,也是寥寥幾個參數設置即可。
所以,大家在項目中引入消息隊列的時候,真的要好好考慮下,不要因為大家都鼓吹 Kafka 好,就無腦引入。
總結
可以看到,如果我們要做消息隊列選型,有兩件事是必須要做好的:
-
列出業務最重要的幾個特點
-
深入到消息隊列的細節中去比較
等我們對這些中間件的特點非常熟悉之后,甚至可以把業務分解成不同的子業務,再根據不同的子業務的特征,引入不同的消息隊列,即消息隊列混用。這樣,我們就可能會最大化我們的獲益,最小化我們的成本。
說了這么多,其實還有很多 Kafka 和 RabbitMQ 的比較沒有說,比如二者集群的區別,占用資源多少的比較等。以后有機會可以再提提。
總之,期待大家看完這篇文章后,能對 Kafka 和 RabbitMQ 的區別有了更細節性的了解。
最后,分享一個網上的比較全的對比圖:
你好,我是四猿外。
一家上市公司的技術總監,管理的技術團隊一百余人。
我從一名非計算機專業的畢業生,轉行到程序員,一路打拼,一路成長。
我會把自己的成長故事寫成文章,把枯燥的技術文章寫成故事。
歡迎關注我的公眾號,關注后可以領取高並發、算法刷題筆記、計算機高分書單等學習資料。