超詳細消息隊列MQ使用時常見問題以及解決辦法


超詳細消息隊列MQ使用時常見問題以及解決辦法

 

沒有什么問題是加一層解決不了的,如果有,那么久加兩層,今天就來講講加的這層MQ的缺點

接上一篇消息隊列的介紹以及各種消息隊列之間的對比的博文,上一篇文章介紹過MQ的有點了,這篇文章就直說缺點不說優點了

 

本文以RabbitMQ為例簡單說一嘴

任何技術都是雙刃劍,有利有弊。實際開發中需要考慮好利大於弊還是弊大於利,合理的使用各種技術方能搭建出完美的程序。這篇文章就來講一講消息隊列在使用中會出現的問題以及一些解決思路。

這篇文章就不扯一些花里花哨的話了,直接上干貨!!!!

會出現問題總結

  1. 消息隊列造成系統可用性降低
  2. 系統復雜性增加
  3. 消息丟失問題
  4. 消息重復消費
  5. 消費順序問題

千萬注意!使用消息隊列時一定要考慮這些問題,否則可能會造成不可估量的后果!!

 

問題解決
消息隊列造成系統可用性降低


這個很好理解,也相對很好解決。就是原本好好的項目,現在加了一層中間件,如果中間件掛掉了那么系統也會崩了。因此可以說消息隊列降低了系統的可用性。
為了提高系統的可用性,最好的辦法就是搭建集群。RabbitMQ的集群不同於RocketMQ,Kafka的分布式架構,使用的是主從架構。集群搭建的模式有兩種,分別是也有普通集群和鏡像集群模式。

集群不是這篇博客的重點,有想了解的小伙伴們可以單獨去搜MQ集群相關的博客就OK啦~

系統復雜性增加
這里應該很好理解,就是說原本沒用MQ的時候很多東西都不用考慮,用了MQ之后就需要考慮很多很多問題,消息丟失啊,重復消費啊,消息積壓呀等等等等。。。但是欲戴皇冠,必承其重的道理想必大家也都很清楚。

消息丟失問題
不用MQ還好,用了MQ如果不考慮消息丟失問題,可能會給公司帶來不可預估的財產損失,所以大家使用的時候也需要斟酌。在網絡環境中可能會遇到各種不可估計的錯誤導致數據丟失,接下來從三個角度來介紹防止消息丟失的一些解決辦法。

 

    生產者數據丟失


真實開發中會有兩種方式解決生產者數據丟失的情況。
第一種:使用了事務的機制,生產者發送消息前開啟事務channel.txSelect(),開啟事務后去發送消息,如果發送消息出現異常事務就會回滾,channel.txRollback(),如何不發生異常的話,事務就會提交成功發送消息channel.txCommit()。然而因為用到了事務機制,程序的效率肯定會受到影響,因此有了第二種解決辦法。
第二種:第二種解決生產者消息丟失的方法是confirm確認模式。這個模式的邏輯也很簡單,就是一旦channel進入了confirm模式,發布的消息都會被指派一個唯一的ID,從1開始。如果RabbitMQ收到了消息,則會返回一個消息唯一ID的ACK給生產者,如果沒有收到則會返回一個NACK給生產者,生產者通過這個辨別消息隊列是否收到了消息。

 

    消息隊列數據丟失

 


當消息成功抵達消息隊列后,消息的生產者的任務算是結束了。這時就該考慮如果消息隊列把消息弄丟了咋整。
前人栽樹,后人乘涼。這些問題很久前就已經有了很好的解決辦法。

這里解決消息隊列丟失消息的方法就是持久化機制搭配着confirm確認模式來一起使用,也就是當消息隊列接收到生產者消息,並且持久化成功后才會返回ACK消息給生產者,否則返回NACK消息,這樣一來,如果持久化失敗,生產者也會自動重發消息,如果持久化成功,就不用擔心消息隊列將消息丟失掉啦。就算MQ掛掉了,重啟一下也會將消息回復回來。

關於持久化消息的操作很簡單:
1、將queue的持久化開關打開,durable設置為true,代表是一個持久的隊列

2、發送消息的時候將deliveryMode=2

 

 

    消費者數據丟失


消費者消息丟失一般是因為使用的是自動確認消息模式,這種模式下消費者會自動確認收到消息,此時MQ會立即刪除消息。這種情況下如果消費者消費出現了異常,消費者就會丟失掉消息。

知道了原因,解決起來便很方便,即不采用自動確認消息模式,更換成手動確認消息的模式。

AcknowledgeMode.NONE:不確認
AcknowledgeMode.AUTO:自動確認
AcknowledgeMode.MANUAL:手動確認
在SpringBOOT中手動確認消息的配置如下

//  spring-boot中配置方法:
spring.rabbitmq.listener.simple.acknowledge-mode = manual

 

消息被重復消費


防止消息的重復消費換句話說就是保證消息的冪等性

這里簡單說一下冪等性的意思就是對相同資源的一次請求或者多次請求得到的結果是一致的

這個問題時消息隊列的基本問題,用來考察你的能力,也就是說可以結合實際場景來答,沒有固定的答案的。

不管哪種消息隊列都有可能出現重復消費的情況,但是大多處理的形式都差不多,就是消費者消費了之后會發一個消費成功的信號給MQ,然后MQ會去刪除消息,防止了重復消費。不同的MQ有不同的做法而已。
例如RabbitMQ是發一個ACK確認消息,RocketMQ是返回一個CONSUME_SUCCESS成功標志,kafka實際上有個offset的概念, 就是每一個消息都有一個offset,kafka消費過消息后,需要提交offset,讓消息隊列知道自己已經消費過了。

至於重復消費的原因也很多很多,比如網絡故障導致MQ無法收到消費成功的返回消息等等。

如何解決這個問題?這個可以針對不同的業務場景來回答。

如果使用這個消息去進行一個插入的INSERT操作
這樣的場景很好解決,加一個唯一的主鍵,再次消費的時候就會出現主鍵沖突防止了出現數據庫的臟數據。

如果使用這個消息去做一個Redis的Set操作
這就不用擔心了,本身Redis的set操作就是冪等的,set幾次的結果都是一樣。

如果還有其他需求的話?
可以做一個中間的記錄介質的存在,來記錄每一個消息的消費情況,就像使用redis的K-V結構將消息的消費情況存在redis中去。

消息積壓過多


一般這種情況發生在消費者服務掛掉了,導致沒辦法去消費,從而消息隊列里的數據大量積壓。如果不是熱門數據還好,就怕是熱門數據,一下積壓了很多很多的消息,很讓人頭疼。

解決辦法首當其沖的是先修復好consumer的問題然后再考慮怎么去處理積壓的消息,如果消費者仍然在掛掉的狀態,那么消息積壓只會越來越多。

解決好了消費服務器的問題之后就開始着手處理積壓的消息了

 

  1. 新建一個topic,partition是原來的10倍,臨時建立好原先10倍或者20倍的queue數量
  2. 然后寫一個臨時的分發數據的consumer程序,這個程序部署上去消費積壓的數據
  3. 消費之后不做耗時的處理,直接均勻輪詢寫入臨時建立好的10倍數量的queue
  4. 接着臨時征用10倍的機器來部署consumer,每一批consumer消費一個臨時queue的數據
  5. 這種做法相當於是臨時將queue資源和consumer資源擴大10倍,以正常的10倍速度來消費數據
  6. 等快速消費完積壓數據之后,得恢復原先部署架構,重新用原先的consumer機器來消費消息
  7. 這里還會有一些問題,比如消息積壓時間太長導致超時了,有些會被MQ自動清理掉,那么這些消息就徹底消失了。
  8. 這里可以寫一個臨時程序,將丟掉的那些消息再一點點查出來然后批量導入到消息隊列中去。

 

 

 

 

 


這次的問題就說到這里,希望能幫助上思進取的你~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM