幾年前,爲何我擼了一套RabbitMQ客戶端?

以前的文章說過,若是使用 RabbitMQ,儘量使用框架,而不要去使用 RabbitMQ 提供的 Java 版客戶端。程序員

細提及來,其實仍是由於 RabbitMQ 客戶端的使用有不少的注意事項,稍微不注意,就容易翻車。緩存

我是 2013 年就開始用起了 RabbitMQ,一路使用,一路和它一塊兒成長。當時,因爲用的早,市面上也沒有特別成熟的 RabbitMQ 客戶端框架。因此,不得已之下,只好本身作了一套客戶端。安全

在這其中,正好也有了許多獨特的經驗也和你們分享一下,以避免後來者陷入「後人哀之而不鑑之,亦使後人而復哀後人也」的套娃中。服務器

1、那麼,就先從網絡鏈接開始吧

1. 應該長久生存的鏈接

在 RabbitMQ 中,因爲須要客戶端和服務器端進行握手,因此致使客戶端和服務器端的鏈接若是要成功建立,須要很高的成本。網絡

每個鏈接的建立至少須要 7 個 TCP 包,這還只是普通鏈接。若是須要 TLS 的參與,則 TCP 包會更多。框架

並且,RabbitMQ 中主要是以 Channel 方式通訊,因此,每次建立完 Connection 網絡鏈接,還得建立 Channel,這又須要 2 個 TCP 包。異步

若是,每次用完,再把鏈接關閉,首先還要關閉已經建立的 Channel,這也須要 2 個 TCP 包。oop

而後,再關閉已經創建好的 Connection 鏈接,又須要 2 個 TCP 包。性能

我們算算,若是一個鏈接從建立到關閉,一共須要多少個 TCP 包?url

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 的數量。

2、消息很寶貴,千萬別亂拋棄哦

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

由於每條消息均可能攜帶了關鍵的數據和信息。因此,保證消息不丟失,須要根據消息的重要性,採起不少的措施。

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%。

這種能夠根據實際狀況進行抉擇。

3、對於收消息這件事,別由着性子來

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

不少時候,一些 RabbitMQ 的新手,以爲若是在一個 mainloop 相似的無限循環裏,去主動獲取消息,會更加及時的獲取到消息,也會擁有更加出色的性能。因此,他們會使用 get 這種行爲去取代 consume 這種行爲。

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

爲了能主動 get 服務器消息,不少新手會去寫一個無限循環,而後不斷嘗試去 RabbitMQ 服務器端獲取消息。可是,get 方法,實際上是隻去獲取了隊列中的第一條消息。

而採用 consume 方式呢,它的默認方式是隻要有消息,就會批量的拿,直到拿光全部還沒消費過的消息。

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

因此,儘可能採用 consume 方式獲取消息。

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

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

一次咱們到底要取多少條消息?

對於 RabbitMQ 來說,若是咱們不對消費行爲作限制,他會有多少消息就獲取多少消息。這就形成了一個問題:

若是消息過多,咱們一次性把消息讀取到內存,極可能就會把應用的內存擠崩掉。

因此,咱們要對這種狀況作一些限制。

這時候,須要限制一次獲取消息的數量,通常來說,當咱們的業務是異步發送,異步消費,不須要實時給迴響應的時候,經驗數據是一次獲取 1000 條。

固然,系統和系統不同,硬件條件也不同,你們能夠根據實際的狀況來設置一次性獲取的消息數量。

重點要說說同步。

在不少時候,咱們須要經過 RabbitMQ 傳送消息,並能經過臨時隊列等技巧去實時返回處理結果。這時候,就沒辦法一次抓多條數據進行處理了,由於,有發送端在等處理結果,依次處理,再依次返回,黃花菜都涼了。

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

最後

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

因此,若是你以爲 Spring 之類的 AMQP 客戶端框架合你心意,那麼你就使用它。

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


你好,我是四猿外,一家上市公司的技術總監,管理的技術團隊一百餘人。

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

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

最後給各位程序員分享一些技術資料: 這三本資料,或許能幫你進大廠拿高薪

歡迎關注個人公衆號

相關文章
相關標籤/搜索