幾年前,我擼了一套RabbitMQ的客戶端
不好意思,又好多天沒更文章了……
眼看著離過年越來越近了,很多工作都要在年前沖刺、收個尾。比如:工作總結、績效考核、獎金、確定今年 KPI……
由于我負責的部門一百多人,雖然有下面的各位 Leader 幫忙,但是我的工作量還是很大的,每天一腦門子雜七雜八的事情,還有大大小小的各種會議……真沒時間輸出文章。
這不,在我的讀者群里,都被大家催更了。
在此感謝:
阿德、enjoy.day、Genos等等(不一一列舉了,我都記在心里了)各位老鐵催更。
RabbitMQ 的新文章總算寫好了。
我在上篇文章說過,如果使用 RabbitMQ,盡可能使用框架,而不要去使用 RabbitMQ 提供的 Java 版客戶端。
細說起來,其實還是因為 RabbitMQ 客戶端的使用有很多的注意事項,稍微不注意,就容易翻車。
我是 2013 年就開始用起了 RabbitMQ,一路使用,一路和它一起成長。當時,由于用的早,市面上也沒有特別成熟的 RabbitMQ 客戶端框架。所以,不得已之下,只好自己做了一套客戶端。
在這其中,正好也有了許多獨特的經(jīng)驗也和大家分享一下,以免后來者陷入“后人哀之而不鑒之,亦使后人而復哀后人也”的套娃中。
一、那么,就先從網(wǎng)絡連接開始吧
1. 應該長久生存的連接
在 RabbitMQ 中,由于需要客戶端和服務器端進行握手,所以導致客戶端和服務器端的連接如果要成功創(chuàng)建,需要很高的成本。
每一個連接的創(chuàng)建至少需要 7 個 TCP 包,這還只是普通連接。如果需要 TLS 的參與,則 TCP 包會更多。
而且,RabbitMQ 中主要是以 Channel 方式通信,所以,每次創(chuàng)建完 Connection 網(wǎng)絡連接,還得創(chuàng)建 Channel,這又需要 2 個 TCP 包。
如果,每次用完,再把連接關閉,首先還要關閉已經(jīng)創(chuàng)建的 Channel,這也需要 2 個 TCP 包。
然后,再關閉已經(jīng)建立好的 Connection 連接,又需要 2 個 TCP 包。
咱們算算,如果一個連接從創(chuàng)建到關閉,一共需要多少個 TCP 包?
7 + 2 + 2 + 2 = 13
一共需要 13 個包。這個成本是很昂貴的。
所以,在 RabbitMQ 中,連接最好緩存起來,重復使用更好。
2. Channel 還是獨占好
在 RabbitMQ 自己的客戶端中,Channel 出于性能原因,并不是線程安全的。
而如果咱們?yōu)榱司€程共用,給 Channel 人為的在外部加上鎖,本身就和 RabbitMQ 的 Channel 設計意圖是沖突的。
所以,最好的辦法就是一個線程一個 Channel。
3. Channel 最好也別關
就像連接應該緩存起來那樣,Channel 的打開和關閉也需要時間成本,而且沒有必要去重新創(chuàng)建 Channel,所以,Channel 也應該緩存起來重用。
4. 別把消費和發(fā)送的連接搞在一起
把消費和發(fā)送的連接搞在一起,這是個很容易犯的錯誤!
我們用 RabbitMQ 的時候,我們自己的系統(tǒng)本身大部分都是既要發(fā)消息也要收消息的。對于這種情況,有很多程序員走了極端:
他們覺得 RabbitMQ 連接成本高,所以省著用。于是就把發(fā)消息和收消息的連接混在一起,使用同一個 TCP 連接。
這很可能會埋一個大雷。
因為,當我們發(fā)消息很頻繁的時候,我們收消息也是走的同一個 TCP 通道,收完了消息,客戶端還要給 RabbitMQ 服務器端一個 ACK。
RabbitMQ 服務器端,對于每個 TCP 連接都會分配專門的進程,如果遇到這個進程繁忙,這個 ACK 很可能被丟棄,又或者等待處理的時間過長。而這種情況又會導致 RabbitMQ 中的未確認消息會被堆積的越來越多,影響到整套系統(tǒng)。
所以,消費和發(fā)送的連接必須分開,各干各的事情。
5. 別搞太多連接和 Channel,RabbitMQ 的 Web 受不了
RabbitMQ 的 Web 插件會收集很多連接,和其對應 Channel 的相關數(shù)據(jù)。
如果連接和 Channel 堆積太多了,整個 Web 打開會非常慢,幾乎無法對 RabbitMQ 進行管理。所以,要注意限制連接和 Channel 的數(shù)量。
二、消息很寶貴,千萬別亂拋棄哦
用來通信的消息是很寶貴的。
因為每條消息都可能攜帶了關鍵的數(shù)據(jù)和信息。所以,保證消息不丟失,需要根據(jù)消息的重要性,采取很多的措施。
1. 小心,Queue 存在再發(fā)消息
一條消息,在 RabbitMQ 中會先發(fā)到 Exchange,再由 Exchange 交給對應的 Queue。
而當 Queue 不存在,或者沒匹配到合適的 Queue 的時候,默認就會把消息發(fā)到系統(tǒng)中的 /dev/null 中。
而且還不會報錯。
這個坑當年把我坑慘了!我猜這個坑無數(shù)人踩過吧。
所以,在發(fā)送消息的時候,最好通過 declare passive 這種方法去探測下隊列是否存在,保證消息發(fā)送不會丟的莫名其妙。
2. 收到消息請告訴我
在使用 RabbitMQ 客戶端的時候,發(fā)送消息,一定要考慮使用 confirm 機制。
這個機制就是當消息收到了,RabbitMQ 會往客戶端發(fā)送一個通知,客戶端收到這個通知后,如果存在一個 confirm 處理器,那么就會回調(diào)這個處理器處理。這時候,我們就能確保消息是被中間件收到了。
所以,一定要考慮使用 confirm 處理器去確保消息被 RabbitMQ 服務器收到。
3. 有時候消息出了問題我也需要知道
在某些業(yè)務里,可能需要知道消息發(fā)送失敗的場景,以便執(zhí)行失敗的處理邏輯。這時候,就要考慮 RabbitMQ 客戶端的 return 機制。
這個機制就是當消息在服務器端路由的時候出現(xiàn)了錯誤,比如沒有 Exchange、或者 RoutingKey 不存在,則 RabbitMQ 會返回一個響應給客戶端??蛻舳耸盏胶髸卣{(diào) return 的處理器。這時候,客戶端所在系統(tǒng)就能感知到這種錯誤了,從而進行對應的處理。
4. 為了一定不丟消息我也是拼了
還有的時候,消息需要處理強一致性這種事務性質(zhì)的業(yè)務。這時候,就必須開啟 RabbitMQ 的事務模式。但是,這個模式會導致整體 RabbitMQ 的性能下降 250 倍。
一般沒有必要,不建議開啟。
5. 把消息寫到磁盤上
一般來說,為了防止消息丟失,需要在 RabbitMQ 服務器收到消息的時候,先持久化消息到磁盤上,防止服務器狀態(tài)出現(xiàn)問題,消息丟失。
但是,持久化消息,必須先持久化隊列,持久化隊列完還不行,還必須把消息的 delivery mode 設置為 2,這樣才能把消息存到磁盤。但是,這種行為會讓整個 RabbitMQ 的性能下降 60%。
這種可以根據(jù)實際情況進行抉擇。
三、對于收消息這件事,別由著性子來
1. 能一次拿多個干嘛要一次只拿一個
很多時候,一些 RabbitMQ 的新手,覺得如果在一個 mainloop 類似的無限循環(huán)里,去主動獲取消息,會更加及時的獲取到消息,也會擁有更加出色的性能。所以,他們會使用 get 這種行為去取代 consume 這種行為。
這時候,他們其實已經(jīng)踩進了大坑。
為了能主動 get 服務器消息,很多新手會去寫一個無限循環(huán),然后不斷嘗試去 RabbitMQ 服務器端獲取消息。但是,get 方法,其實是只去獲取了隊列中的第一條消息。
而采用 consume 方式呢,它的默認方式是只要有消息,就會批量的拿,直到拿光所有還沒消費過的消息。
一個是一條條拿,一個是批量拿,哪個效率更高一目了然。
所以,盡量采用 consume 方式獲取消息。
2. 拿消息也要講方法論的
消費消息的時候,其實最難掌握的就是:
一次我們到底要取多少條消息?
對于 RabbitMQ 來講,如果我們不對消費行為做限制,他會有多少消息就獲取多少消息。這就造成了一個問題:
如果消息過多,我們一次性把消息讀取到內(nèi)存,很可能就會把應用的內(nèi)存擠崩掉。
所以,我們要對這種情況做一些限制。
這時候,需要限制一次獲取消息的數(shù)量,一般來講,當我們的業(yè)務是異步發(fā)送,異步消費,不需要實時給回響應的時候,經(jīng)驗數(shù)據(jù)是一次獲取 1000 條。
當然,系統(tǒng)和系統(tǒng)不一樣,硬件條件也不一樣,大家可以根據(jù)實際的情況來設置一次性獲取的消息數(shù)量。
重點要說說同步。
在很多時候,我們需要通過 RabbitMQ 傳送消息,并能通過臨時隊列等技巧去實時返回處理結果。這時候,就沒辦法一次抓多條數(shù)據(jù)進行處理了,因為,有發(fā)送端在等處理結果,依次處理,再依次返回,黃花菜都涼了。
而且大部分時候,這種同步等待響應的業(yè)務是有順序要求的。所以,也不能并行同時抓出多條信息處理。那么,彼時,設置每次只消費一條消息就是理所應當?shù)牧恕?/span>
最后
從上面的內(nèi)容中,你也看到了,RabbitMQ 客戶端如果要使用,對新手是多可惡的一件事情,各種坑,各種復雜性。
所以,如果你覺得 Spring 之類的 AMQP 客戶端框架合你心意,那么你就使用它。
但是,Spring 的東西有個毛病,如果你要用它,你的應用必須也都要用 Spring。有些時候,也沒有這種必要。這時候,你就可以根據(jù)我說的這些注意事項和經(jīng)驗,自己開發(fā)一套 RabbitMQ 的封裝框架,去降低 RabbitMQ 的使用門檻。
免責聲明:本文內(nèi)容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!