幾年前,爲什麼我擼了一套RabbitMQ客戶端? 一、那麼,就先從網絡連接開始吧 二、消息很寶貴,千萬別亂拋棄哦 三、對於收消息這件事,別由着性子來 最後

推薦閱讀:

如果使用 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 的使用門檻。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章