又老性能又差,為什么好多公司依然選擇 RabbitMQ?
大家好,我是君哥。
RabbitMQ 這個消息隊列相信很多程序員都用過,我第一次使用是在 2016 年,確實是一個老牌的消息隊列了,但是為什么一直沒有被淘汰呢?今天來聊一聊這個話題。
老舊差
發(fā)布歷史
為什么說 RabbitMQ 老呢?下圖是 RabbitMQ 最早的發(fā)布記錄,可以看到 RabbitMQ 在 2007 年已經(jīng)發(fā)布,已經(jīng)有 16 年多的使用歷史了。
小眾
為什么說 RabbitMQ 比較小眾呢?
一方面 RabbitMQ 使用 Erlang 語言編寫,這是一個比較小眾的編程語言,學(xué)習(xí)成本非常高,不像 Java、Scala、C 等編程語言學(xué)起來簡單。所以雖然 RabbitMQ 也是開源的消息隊列,但基于 RabbitMQ 做擴展和二次開發(fā)的情況是很少。
另一方面從使用的協(xié)議來看,RabbitMQ 支持 AMQP(Advanced Message Queuing Protocol) 協(xié)議,這也是主流消息隊列不支持的。
AMQP 協(xié)議如下圖:
有幾個概念介紹一下:
- Connection:一個網(wǎng)絡(luò)連接,AMQP 協(xié)議通常使用長連接;
- Channel:網(wǎng)絡(luò)信道,建立在 Connection 之上的輕量級的連接,一個 Connection 可以有多個 Channel;
- Exchange:交換器,接收消息后將消息路由轉(zhuǎn)發(fā)給綁定(Binding)的 Queue;
- Binding:Exchange 和 Queue 之間的虛擬連接;
- Routing Key:這個概念在圖中沒有畫,是指路由規(guī)則,用來確定 Exchange 將消息路由到哪些 Queue。
可以看到,好多概念在主流的消息隊列比如 Kafka、RocketMQ 是沒有的,所以說 RabbitMQ 比較小眾。
性能差
在底層消息持久化的方式上,RabbitMQ 并沒有使用 MMAP、Sendfile 等零拷貝技術(shù),這是性能差的一個重要原因。
在架構(gòu)上,RabbitMQ 提供了鏡像隊列來做 Master 的備份。如下圖:
無論生產(chǎn)者發(fā)送消息,還是消費者拉取消息,如果請求發(fā)送到鏡像隊列,則鏡像隊列需要把請求轉(zhuǎn)發(fā)到 Master 進行處理,Master 處理后再把結(jié)果回復(fù)給鏡像節(jié)點,鏡像隊列回復(fù)給請求者。
在特定硬件環(huán)境下,RabbitMQ 支持的消息吞吐量在萬級~十萬級,相比 RocketMQ 的十萬級~百萬級和 Kafka 的百萬級以上,吞吐量還是差一些。
受歡迎
從我過往的公司、身邊的一些朋友、面試過的候選人簡歷可以看出,好多公司消息隊列技術(shù)選型時選擇了 RabbitMQ,這跟 RabbitMQ 老舊和性能差形成鮮明對比。
RabbitMQ 為什么這么受歡迎呢?
持續(xù)更新
雖然 RabbitMQ 老舊,但是并沒有停止更新,而且更新還挺頻繁,下圖是 2023 年最近發(fā)布的幾個版本:
從 2007 年開始,RabbitMQ 已經(jīng)有 16 年的使用歷史,可以稱得上是一個久經(jīng)考驗的戰(zhàn)士,各種問題已經(jīng)修復(fù),學(xué)習(xí)資料豐富,性能穩(wěn)定。
運維簡單
RabbitMQ 是一個非常輕量級的消息隊列,官方宣稱開箱即用。在 Docker 上部署 RabbitMQ,三個命令就可以。
拉取鏡像
docker pull rabbitmq:3.8.2-management
創(chuàng)建路徑
mkdir /var/lib/rabbitmq
啟動容器
docker run -d --name rabbitmq3.8.2 -p 5672:5672 -p 15672:15672 -v `pwd`/data:/var/lib/rabbitmq --hostname myRabbit -e RABBITMQ_DEFAULT_VHOST=my_vhost -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin --privileged=true
這種開箱即用的效果,大大降低了學(xué)習(xí)成本和運維成本。
靈活路由
依托于 AMQP 中的 Exchange,RabbitMQ 提供了靈活的路由配置,有 4 種。
Direct Exchange
生產(chǎn)者將消息發(fā)送給 Exchange 后,Exchange 通過 Routing Key 把消息路由到對應(yīng)的隊列。如下圖(來自官網(wǎng)):
Fanout Exchange
生產(chǎn)者將消息發(fā)送給 Exchange 后,Exchange 將消息路由到所有綁定的隊列,類似于廣播模式。如下圖(來自官網(wǎng)):
Topic Exchange
這種路由策略首先定義一個 Topic,topic 中可以包含 *
和 #
。*
可以代表一個單詞,#
可以代表 0 或多個單詞。如下圖(來自官網(wǎng)):
圖中 Topic 由三個單詞<celerity>.<colour>.<species>
組成,分別代表特征、顏色和物種,單詞之間用.
間隔。這樣 Q1 將接收顏色為 orange 的所有消息,Q2 將接收物種為 rabbit 的消息和特征為 lazy 的消息。
Headers Exchange
這種路由策略要求消息中需要攜帶 Headers(類似 Http 中的消息頭),隊列跟 Routing Key 綁定時也要定義一個 Headers,只有綁定中定義的 Headers 跟消息中的 Header 匹配,才會路由到相應(yīng)的隊列。匹配規(guī)則有兩種:
- ALL:要求兩個 Headers 中所有 key 和 value 匹配;
- ANY:要求兩個 Headers 任何一個 key 和 value 匹配。
如下圖:
這種路由方式在定義綁定關(guān)系的時候就需要定義 Headers,如下代碼:
@Bean
public Binding binding1(HeadersExchange headersExchange,Queue queue1){
HashMap<String, Object> headers = new HashMap<>();
headers.put("key1","aaa");
headers.put("key2","bbb");
return BindingBuilder.bind(queue1).to(headersExchange).whereAll(headers).match();
}
public Binding binding2(HeadersExchange headersExchange,Queue queue2){
HashMap<String, Object> headers = new HashMap<>();
headers.put("key1","aaa");
headers.put("key2","bbb");
return BindingBuilder.bind(queue2).to(headersExchange).whereAny(headers).match();
}
客戶端豐富
RabbitMQ 客戶端支持的編程語言是消息隊列中最多的,很容易兼容自己系統(tǒng)使用的編程語言。參考下圖(來自官網(wǎng)):
總結(jié)
RabbitMQ 雖然老舊,但具有運維簡單、靈活路由、客戶端豐富等特性。雖然吞吐量不高,但性能足夠滿足中小企業(yè)的使用需求。這讓 RabbitMQ 成為非常受歡迎的消息隊列。