幾年前,為什麼我擼了一套RabbitMQ客戶端?

四猿外發表於2021-06-01

之前文章說過,如果使用 RabbitMQ,儘可能使用框架,而不要去使用 RabbitMQ 提供的 Java 版客戶端。

細說起來,其實還是因為 RabbitMQ 客戶端的使用有很多的注意事項,稍微不注意,就容易翻車。

我是 2013 年就開始用起了 RabbitMQ,一路使用,一路和它一起成長。當時,由於用的早,市面上也沒有特別成熟的 RabbitMQ 客戶端框架。所以,不得已之下,只好自己做了一套客戶端。

在這其中,正好也有了許多獨特的經驗也和大家分享一下,以免後來者陷入“後人哀之而不鑑之,亦使後人而復哀後人也”的套娃中。

一、那麼,就先從網路連線開始吧

1. 應該長久生存的連線

在 RabbitMQ 中,由於需要客戶端和伺服器端進行握手,所以導致客戶端和伺服器端的連線如果要成功建立,需要很高的成本。

每一個連線的建立至少需要 7 個 TCP 包,這還只是普通連線。如果需要 TLS 的參與,則 TCP 包會更多。

而且,RabbitMQ 中主要是以 Channel 方式通訊,所以,每次建立完 Connection 網路連線,還得建立 Channel,這又需要 2 個 TCP 包。

如果,每次用完,再把連線關閉,首先還要關閉已經建立的 Channel,這也需要 2 個 TCP 包。

然後,再關閉已經建立好的 Connection 連線,又需要 2 個 TCP 包。

我們們算算,如果一個連線從建立到關閉,一共需要多少個 TCP 包?

7 + 2 + 2 + 2 = 13

一共需要 13 個包。這個成本是很昂貴的。

所以,在 RabbitMQ 中,連線最好快取起來,重複使用更好。

2. Channel 還是獨佔好

在 RabbitMQ 自己的客戶端中,Channel 出於效能原因,並不是執行緒安全的。

而如果我們們為了執行緒共用,給 Channel 人為的在外部加上鎖,本身就和 RabbitMQ 的 Channel 設計意圖是衝突的。

所以,最好的辦法就是一個執行緒一個 Channel。

3. Channel 最好也別關

就像連線應該快取起來那樣,Channel 的開啟和關閉也需要時間成本,而且沒有必要去重新建立 Channel,所以,Channel 也應該快取起來重用。

4. 別把消費和傳送的連線搞在一起

把消費和傳送的連線搞在一起,這是個很容易犯的錯誤!

我們用 RabbitMQ 的時候,我們自己的系統本身大部分都是既要發訊息也要收訊息的。對於這種情況,有很多程式設計師走了極端:

他們覺得 RabbitMQ 連線成本高,所以省著用。於是就把發訊息和收訊息的連線混在一起,使用同一個 TCP 連線。

這很可能會埋一個大雷。

因為,當我們發訊息很頻繁的時候,我們收訊息也是走的同一個 TCP 通道,收完了訊息,客戶端還要給 RabbitMQ 伺服器端一個 ACK。

RabbitMQ 伺服器端,對於每個 TCP 連線都會分配專門的程式,如果遇到這個程式繁忙,這個 ACK 很可能被丟棄,又或者等待處理的時間過長。而這種情況又會導致 RabbitMQ 中的未確認訊息會被堆積的越來越多,影響到整套系統。

所以,消費和傳送的連線必須分開,各幹各的事情。

5. 別搞太多連線和 Channel,RabbitMQ 的 Web 受不了

RabbitMQ 的 Web 外掛會收集很多連線,和其對應 Channel 的相關資料。

如果連線和 Channel 堆積太多了,整個 Web 開啟會非常慢,幾乎無法對 RabbitMQ 進行管理。所以,要注意限制連線和 Channel 的數量。

二、訊息很寶貴,千萬別亂拋棄哦

用來通訊的訊息是很寶貴的。

因為每條訊息都可能攜帶了關鍵的資料和資訊。所以,保證訊息不丟失,需要根據訊息的重要性,採取很多的措施。

1. 小心,Queue 存在再發訊息

一條訊息,在 RabbitMQ 中會先發到 Exchange,再由 Exchange 交給對應的 Queue。

而當 Queue 不存在,或者沒匹配到合適的 Queue 的時候,預設就會把訊息發到系統中的 /dev/null 中。

而且還不會報錯。

這個坑當年把我坑慘了!我猜這個坑無數人踩過吧。

所以,在傳送訊息的時候,最好通過 declare passive 這種方法去探測下佇列是否存在,保證訊息傳送不會丟的莫名其妙。

2. 收到訊息請告訴我

在使用 RabbitMQ 客戶端的時候,傳送訊息,一定要考慮使用 confirm 機制。

這個機制就是當訊息收到了,RabbitMQ 會往客戶端傳送一個通知,客戶端收到這個通知後,如果存在一個 confirm 處理器,那麼就會回撥這個處理器處理。這時候,我們就能確保訊息是被中介軟體收到了。

所以,一定要考慮使用 confirm 處理器去確保訊息被 RabbitMQ 伺服器收到。

3. 有時候訊息出了問題我也需要知道

在某些業務裡,可能需要知道訊息傳送失敗的場景,以便執行失敗的處理邏輯。這時候,就要考慮 RabbitMQ 客戶端的 return 機制。

這個機制就是當訊息在伺服器端路由的時候出現了錯誤,比如沒有 Exchange、或者 RoutingKey 不存在,則 RabbitMQ 會返回一個響應給客戶端。客戶端收到後會回撥 return 的處理器。這時候,客戶端所在系統就能感知到這種錯誤了,從而進行對應的處理。

4. 為了一定不丟訊息我也是拼了

還有的時候,訊息需要處理強一致性這種事務性質的業務。這時候,就必須開啟 RabbitMQ 的事務模式。但是,這個模式會導致整體 RabbitMQ 的效能下降 250 倍。

一般沒有必要,不建議開啟。

5. 把訊息寫到磁碟上

一般來說,為了防止訊息丟失,需要在 RabbitMQ 伺服器收到訊息的時候,先持久化訊息到磁碟上,防止伺服器狀態出現問題,訊息丟失。

但是,持久化訊息,必須先持久化佇列,持久化佇列完還不行,還必須把訊息的 delivery mode 設定為 2,這樣才能把訊息存到磁碟。但是,這種行為會讓整個 RabbitMQ 的效能下降 60%。

這種可以根據實際情況進行抉擇。

三、對於收訊息這件事,別由著性子來

1. 能一次拿多個幹嘛要一次只拿一個

很多時候,一些 RabbitMQ 的新手,覺得如果在一個 mainloop 類似的無限迴圈裡,去主動獲取訊息,會更加及時的獲取到訊息,也會擁有更加出色的效能。所以,他們會使用 get 這種行為去取代 consume 這種行為。

這時候,他們其實已經踩進了大坑。

為了能主動 get 伺服器訊息,很多新手會去寫一個無限迴圈,然後不斷嘗試去 RabbitMQ 伺服器端獲取訊息。但是,get 方法,其實是隻去獲取了佇列中的第一條訊息。

而採用 consume 方式呢,它的預設方式是隻要有訊息,就會批量的拿,直到拿光所有還沒消費過的訊息。

一個是一條條拿,一個是批量拿,哪個效率更高一目瞭然。

所以,儘量採用 consume 方式獲取訊息。

2. 拿訊息也要講方法論的

消費訊息的時候,其實最難掌握的就是:

一次我們到底要取多少條訊息?

對於 RabbitMQ 來講,如果我們不對消費行為做限制,他會有多少訊息就獲取多少訊息。這就造成了一個問題:

如果訊息過多,我們一次性把訊息讀取到記憶體,很可能就會把應用的記憶體擠崩掉。

所以,我們要對這種情況做一些限制。

這時候,需要限制一次獲取訊息的數量,一般來講,當我們的業務是非同步傳送,非同步消費,不需要實時給迴響應的時候,經驗資料是一次獲取 1000 條。

當然,系統和系統不一樣,硬體條件也不一樣,大家可以根據實際的情況來設定一次性獲取的訊息數量。

重點要說說同步。

在很多時候,我們需要通過 RabbitMQ 傳送訊息,並能通過臨時佇列等技巧去實時返回處理結果。這時候,就沒辦法一次抓多條資料進行處理了,因為,有傳送端在等處理結果,依次處理,再依次返回,黃花菜都涼了。

而且大部分時候,這種同步等待響應的業務是有順序要求的。所以,也不能並行同時抓出多條資訊處理。那麼,彼時,設定每次只消費一條訊息就是理所應當的了。

最後

從上面的內容中,你也看到了,RabbitMQ 客戶端如果要使用,對新手是多可惡的一件事情,各種坑,各種複雜性。

所以,如果你覺得 Spring 之類的 AMQP 客戶端框架合你心意,那麼你就使用它。

但是,Spring 的東西有個毛病,如果你要用它,你的應用必須也都要用 Spring。有些時候,也沒有這種必要。這時候,你就可以根據我說的這些注意事項和經驗,自己開發一套 RabbitMQ 的封裝框架,去降低 RabbitMQ 的使用門檻。


你好,我是四猿外。

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

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

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

歡迎關注我的公眾號,關注之後還可以獲取演算法、高併發等乾貨學習資料。

我建了一個讀者交流群,裡面大部分是程式設計師,一起聊技術、工作、八卦。歡迎加我微信,拉你入群。

相關文章