Kafka和RabbitMQ有哪些區別,各自適合什麼場景?

四猿外發表於2022-01-06

經常有人問我

有個 xx 需求,我應該用 Kafka 還是 RabbitMQ ?

這個問題很常見,而且很多人對二者的選擇也把握不好。

所以我決定寫篇文章來詳細說一下:Kafka 和 RabbitMQ 的區別,適用於什麼場景?

同時,這個問題在面試中也經常問到。

下面我會通過 6 個場景,來對比分析一下 Kafka 和 RabbitMQ 的優劣。

一、訊息的順序

有這樣一個需求:當訂單狀態變化的時候,把訂單狀態變化的訊息傳送給所有關心訂單變化的系統。

訂單會有建立成功、待付款、已支付、已發貨的狀態,狀態之間是單向流動的。

好,現在我們把訂單狀態變化訊息要傳送給所有關心訂單狀態的系統上去,實現方式就是用訊息佇列。

在這種業務下,我們最想要的是什麼?

  1. 訊息的順序:對於同一筆訂單來說,狀態的變化都是有嚴格的先後順序的。

  2. 吞吐量:像訂單的業務,我們自然希望訂單越多越好。訂單越多,吞吐量就越大。

在這種情況下,我們先看看 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,出現了三個問題:

  1. 為了實現釋出訂閱功能,從而使用的訊息複製,會降低效能並耗費更多資源
  2. 多個消費者無法嚴格保證訊息順序
  3. 大量的訂單集中在一個佇列,吞吐量受到了限制

那麼 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 好,就無腦引入。

總結

可以看到,如果我們要做訊息佇列選型,有兩件事是必須要做好的:

  1. 列出業務最重要的幾個特點

  2. 深入到訊息佇列的細節中去比較

等我們對這些中介軟體的特點非常熟悉之後,甚至可以把業務分解成不同的子業務,再根據不同的子業務的特徵,引入不同的訊息佇列,即訊息佇列混用。這樣,我們就可能會最大化我們的獲益,最小化我們的成本。

說了這麼多,其實還有很多 Kafka 和 RabbitMQ 的比較沒有說,比如二者叢集的區別,佔用資源多少的比較等。以後有機會可以再提提。

總之,期待大家看完這篇文章後,能對 Kafka 和 RabbitMQ 的區別有了更細節性的瞭解。

最後,分享一個網上的比較全的對比圖:



你好,我是四猿外。

一家上市公司的技術總監,管理的技術團隊一百餘人。

我從一名非計算機專業的畢業生,轉行到程式設計師,一路打拼,一路成長。

我會把自己的成長故事寫成文章,把枯燥的技術文章寫成故事。

歡迎關注我的公眾號,關注後可以領取高併發、演算法刷題筆記、計算機高分書單等學習資料。

相關文章