首先說一點,企業中最常用的實際上既不是RocketMQ,也不是Kafka,而是RabbitMQ。
RocketMQ很強大,但主要是阿裡推廣自己的雲産品而開源出來的一款消息隊列,其實中小企業用RocketMQ的沒有想象中那麼多。
深層次的原因在于兔寶在中小企業普及更早,經受的考驗也更久,很容易産生「回頭客」,當初随RabbitMQ成長的一批人才如今大部分都已成為企業中的中堅骨幹,技術選型親睐RabbitMQ的幾率就更高。
至于Kafka,主要還是用在大數據和日志采集方面,除了一些公司有特定的需求會使用外,對消息收發準确率要求較高的公司依然是以RabbitMQ作為企業級消息隊列的首選。
工作這麼多年我自身的感受是,RabbitMQ經久不衰,除非後續其他消息中間件有與衆不同的使用體驗,否則依然是RabbitMQ的占有率更高。
所以準備進入軟件行業的小夥伴,我建議有必要系統的先把RabbitMQ學好,然後再學習其他消息中間件擴展視野,他們的原理大同小異,是可以觸類旁通的。
二、兩個概念RabbitMQ避免消息丢失的方法主要是利用消息确認機制和手動簽收機制,所以有必要把這兩個概念搞清楚。
1、消息确認機制主要是生産者使用的機制,用來确認消息是否被成功消費。
配置如下:
這樣,當你實現RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback這兩個接口的方法後,就可以針對性地進行消息确認的日志記錄,之後做進一步的消息發送補償,以達到接近100%投遞的目的。
僞代碼如下:
2、消息簽收機制
RabbitMQ的消息是自動簽收的,你可以理解為快遞簽收了,那麼這個快遞的狀态就從發送變為已簽收,唯一的區别是快遞公司會對物流軌迹有記錄,而MQ簽收後就從隊列中删除了。
企業級開發中,RabbitMQ我們基本都開啟手動簽收方式,這樣可以有效避免消息的丢失。
前文中已經在生産者開啟了手動簽收機制,那麼作為消費方,也要設置手動簽收。
配置如下:
消費監聽時,手動簽收就一行代碼,僞代碼如下:
三、消息丢失
兩個概念搞清楚後,就可以來學習消息丢失的問題和處理方案了。
1、出現原因消息丢失的原因無非有三種:
1)、消息發出後,中途網絡故障,服務器沒收到;
2)、消息發出後,服務器收到了,還沒持久化,服務器宕機;
3)、消息發出後,服務器收到了,消費方還未處理業務邏輯,服務卻挂掉了,而消息也自動簽收,等于啥也沒幹。
這三種情況,(1) 和 (2)是由于生産方未開啟消息确認機制導緻,(3)是由于消費方未開啟手動簽收機制導緻。
2、解決方案1)、生産方發送消息時,要try...catch,在catch中捕獲異常,并将MQ發送的關鍵内容記錄到日志表中,日志表中要有消息發送狀态,若發送失敗,由定時任務定期掃描重發并更新狀态;
2)、生産方publisher必須要加入确認回調機制,确認成功發送并簽收的消息,如果進入失敗回調方法,就修改數據庫消息的狀态,等待定時任務重發;
3)、消費方要開啟手動簽收ACK機制,消費成功才将消息移除,失敗或因異常情況而尚未處理,就重新入隊。
其實這就是前面闡述兩個概念時已經講過的内容,也是接近100%消息投遞的企業級方案之一,主要目的就是為了解決消息丢失的問題。
四、消息重複1、出現原因消息重複大體上有兩種情況會出現:
1)、消息消費成功,事務已提交,簽收時結果服務器宕機或網絡原因導緻簽收失敗,消息狀态會由unack轉變為ready,重新發送給其他消費方;
2)、消息消費失敗,由于retry重試機制,重新入隊又将消息發送出去。
2、解決方案網上大體上能搜羅到的方法有三種:
1)、消費方業務接口做好幂等;
2)、消息日志表保存MQ發送時的唯一消息ID,消費方可以根據這個唯一ID進行判斷避免消息重複;
3)、消費方的Message對象有個getRedelivered()方法返回Boolean,為TRUE就表示重複發送過來的。
我這裡隻推薦第一種,業務方法幂等這是最直接有效的方式,(2)還要和數據庫産生交互,(3)有可能導緻第一次消費失敗但第二次消費成功的情況被砍掉。
五、消息積壓1、出現原因消息積壓出現的場景一般有兩種:
1)、消費方的服務挂掉,導緻一直無法消費消息;
2)、消費方的服務節點太少,導緻消費能力不足,從而出現積壓,這種情況極可能就是生産方的流量過大導緻。
2、解決方案1)、既然消費能力不足,那就擴展更多消費節點,提升消費能力;
2)、建立專門的隊列消費服務,将消息批量取出并持久化,之後再慢慢消費。
(1)就是最直接的方式,也是消息積壓最常用的解決方案,但有些企業考慮到服務器成本壓力,會選擇第(2)種方案進行迂回,先通過一個獨立服務把要消費的消息存起來,比如存到數據庫,之後再慢慢處理這些消息即可。
六、使用心得這裡單獨講一下本人在工作中使用RabbitMQ的一些心得,希望能有所幫助。
1)、消息丢失、消息重複、消息積壓三個問題中,實際上主要解決的還是消息丢失,因為大部分公司遇不到消息積壓的場景,而稍微有水準的公司核心業務都會解決幂等問題,所以幾乎不存在消息重複的可能;
2)、消息丢失的最常見企業級方案之一就是定時任務補償,因為不論是SOA還是微服務的架構,必然會有分布式任務調度的存在,自然也就成為MQ最直接的補償方式,如果MQ一定要實現100%投遞,這種是最普遍的方案。但我實際上不推薦中小企業使用該方案,因為憑空增加維護成本,而且沒有一定規模的項目完全沒必要,大家都小看了RabbitMQ本身的性能,比如我們公司,支撐一個三甲醫院,也就是三台8核16G服務器的集群,上線至今3年毫無壓力;
3)、不要迷信網上和培訓機構講解的生産者消息确認機制,也就是前面兩個概念中講到的ConfirmCallback和ReturnCallback,這種機制十分降低MQ性能,我們團隊曾遇到過一次流量高峰期帶來的MQ傳輸及消費性能大幅降低的情況,後來發現是消息确認機制導緻,關閉後立馬恢複正常,從此以後都不再使用這種機制,MQ運行十分順暢。同時我們會建立後台管理實現人工補償,通過識别業務狀态判斷消費方是否處理了業務邏輯,畢竟這種情況都是少數,性能和運維成本,在這一塊我們選擇了性能;
4)、我工作這些年使用RabbitMQ沒見過自動簽收方式,一定是開啟手動簽收;
5)、手動簽收方式你在網上看到的教程幾乎都是處理完業務邏輯之後再手動簽收,但實際上這種用法是不科學的,在分布式的架構中,MQ用來解耦和轉發是非常常見的,如果是支付業務,往往在回調通知中通過MQ轉發到其他服務,其他服務如果業務處理不成功,那麼手動簽收也不執行,這個消息又會入隊發給其他消費者,這樣就可能在流量洪峰階段因為偶然的業務處理失敗造成堵塞,甚至标題所講的三種問題同時出現,這樣就會得不償失。
不科學的用法:在處理完業務邏輯後再手動簽收,否則不簽收,就好比客人進店了你得買東西,否則不讓走。
科學的用法:不論業務邏輯是否處理成功,最終都要将消息手動簽收,MQ的使命不是保證客人進店了必須消費,不消費就不讓走,而是客人能進來就行,哪怕是随便看看也算任務完成。
可能有人會問你這樣不是和自動簽收沒區别嗎,NO,你要知道如果自動簽收,出現消息丢失你連記錄日志的可能都沒有。
另外,為什麼一定要這麼做,因為MQ是中間件,本身就是輔助工具,就是一個滴滴司機,保證給你送到順便說個再見就行,沒必要還下車給你搬東西。
如果強加給MQ過多壓力,隻會造成本身業務的畸形。我們使用MQ的目的就是解耦和轉發,不再做多餘的事情,保證MQ本身是流暢的、職責單一的即可。
七、總結本篇主要講了RabbitMQ的三種常見問題及解決方案,同時分享了一些作者本人工作中使用的心得,我想網上是很難找到的,如果哪一天用到了,不妨再打開看看,也許能避免一些生産環境可能出現的問題。
我總結下來就是三點:
1)、消息100%投遞會增加運維成本,中小企業視情況使用,非必要不使用;
2)、消息确認機制影響性能,非必要不使用;
3)、消費者先保證消息能簽收,業務處理失敗可以人工補償。
工作中怕的永遠不是一個技術不會使用,而是遇到問題不知道有什麼解決思路。
END
私信作者回複「資源」有更多驚喜内容哦!
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!