RabbitMQ消息投遞、可靠性傳輸、重復消費、消息的順序性等問題


1、什么是RabbitMQ?為什么要使用RabbitMQ?


         RabbitMQ是一款開源的、Erlang語言編寫的、基於AMQP協議的消息中間件。
         解耦:實現消費者和生產者之間的解耦
         異步:將消息寫入消息隊列,非必要的業務邏輯以異步的方式運行,加快響應速度
         削峰:將高並發時的同步訪問變為串行訪問達到一定量的限流,利於數據庫的操作

2、RabbitMQ的使用場景?


         1、服務間異步通信
         2、順序消費
         3、定時任務
         4、請求削峰

3、RabbitMQ的優缺點?


         優點:服務間高度解耦、異步通信性能高、流量削峰填谷。

         缺點:系統可用性降低:比如在系統中引入MQ,那么萬一MQ掛了怎么辦呢?一般而言,引入的外部依賴越多,系統越脆弱,每一個依賴出問題都會導致整個系統的崩潰;

         系統復雜度提高:要考慮MQ的各種情況,比如:消息的重復消費、消息丟失、保證消費順序等等;

         一致性問題:假設A系統依賴BCD,A已經給用戶返回操作成功,這時候操作BC都成功了,操作D卻失敗了,會導致數據不一致。


4、消息基於什么傳輸?


         由於TCP連接的創建和銷毀開銷很大,且並發數受系統資源限制,會造成系統瓶頸。RabbitMQ使用信道的方式傳輸數據。信道是建立在真實TCP連接內的虛擬連接,且每條TCP連接上的信道數量沒有限制。

5、消息如何分發?


         若該隊列至少有一個消費者訂閱,消息將以循環(round-robin)的方式發送給消費者。每條消息只會分發給一個訂閱的消費者(前提是消費者能夠正常處理消息並進行確認)。

6、消息怎么路由?


         從概念上來說,消息路由必須有三部分:交換器、路由、綁定。生產者把消息發布到交換器上;綁定決定了消息如何從路由器路由到特定的隊列;消息最終到達隊列,並被消費者接收。
  1. 消息發布到交換器時,消息將擁有一個路由鍵(routing key),在消息創建時設定。
  2. 通過隊列路由鍵,可以把隊列綁定到交換器上。
  3. 消息到達交換器后,RabbitMQ會將消息的路由鍵與隊列的路由鍵進行匹配(針對不同的交換器有不同的路由規則)。如果能夠匹配到隊列,則消息會投遞到相應隊列中;如果不能匹配到任何隊列,消息將進入 “黑洞”。

         常用的交換器主要分為一下三種:

  • direct:如果路由鍵完全匹配,消息就被投遞到相應的隊列
  • fanout:如果交換器收到消息,將會廣播到所有綁定的隊列上
  • topic:可以使來自不同源頭的消息能夠到達同一個隊列。 使用topic交換器時,可以使用通配符,比如:“*” 匹配特定位置的任意文本, “.” 把路由鍵分為了幾部分,“#” 匹配所有規則等。特別注意:發往topic交換器的消息不能隨意的設置選擇鍵(routing_key),必須是由"."隔開的一系列的標識符組成。

7、RabbitMQ概念里的channel、exchange和queue是邏輯概念還是對應者進程實體?分別起什么作用?


  • queue: 具有自己的 erlang 進程;
  • exchange: 內部實現為保存 binding 關系的查找表;
  • channel:實際進行路由工作的實體,即負責按照 routing_key 將 message 投遞給 queue 。

             由 AMQP 協議描述可知,channel 是真實 TCP 連接之上的虛擬連接,所有 AMQP 命令都是通過channel 發送的,且每一個channel有唯一的ID。一個channel只能被單獨一個操作系統線程使用,故投遞到特定 channel 上的 message是有順序的。但一個操作系統線程上允許使用多個 channel 。


8、RabbitMQ中的broker是指什么?cluster是指什么?vhost是什么,有什么作用?


  • broker 是指一個或多個 erlang node 的邏輯分組,且 node 上運行着 RabbitMQ 應用程序。
  • cluster 是在 broker 的基礎之上,增加了 node 之間共享元數據的約束。
  • vhost 可以理解為虛擬 broker ,即 mini-RabbitMQ server。其內部均含有獨立的 queue、exchange 和 binding 等。
  • vhost最重要的是,其擁有獨立的權限系統,可以做到 vhost 范圍的用戶控制。當然,從RabbitMQ 的全局角度,vhost可以作為不同權限隔離的手段(一個典型的例子就是不同的應用可以跑在不同的 vhost 中)。


9、什么是元數據?元數據分為哪些類型?包括哪些內容?與 cluster 相關的元數據有哪些?元數據是如何保存的?元數據在 cluster 中是如何分布的?


  • 在非cluster模式下,元數據主要分為 Queue 元數據(queue 名字和屬性等)、Exchange 元數據(exchange 名字、類型和屬性等)、Binding 元數據(存放路由關系的查找表)、Vhost 元數據(vhost范圍內針對前三者的名字空間約束和安全屬性設置)。
  • 在 cluster 模式下,還包括 cluster 中 node 位置信息和 node 關系信息。元數據按照 erlang node 的類型確定是僅保存於 RAM 中,還是同時保存在 RAM 和 disk 上。元數據在 cluster 中是全 node 分布的。


10、在單node系統和多node構成的cluster 系統中聲明queue、exchange,以及進行binding會有什么不同?


  • 當你在單 node 上聲明 queue 時,只要該 node 上相關元數據進行了變更,你就會得到 Queue.Declare-ok 回應;

  • 而在 cluster 上聲明 queue ,則要求 cluster 上的全部 node 都要進行元數據成功更新,才會得到 Queue.Declare-ok 回應。另外,若 node 類型為 RAM node 則變更的數據僅保存在內存中,若類型為 disk node 則還要變更保存在磁盤上的數據。

  • 死信隊列&死信交換器 : DLX 全稱(Dead-Letter-Exchange),稱之為死信交換器,當消息變成一個死信之后,如果這個消息所在的隊列存在x-dead-letter-exchange參數,那么它會被發送到x-dead-letter-exchange對應值的交換器上,這個交換器就稱之為死信交換器,與這個死信交換器綁定的隊列就是死信隊列。


11、如何保證消息正確地發送至RabbitMQ? 如何保證消息接收方消費了消息?


       RabbitMQ使用發送方確認模式,確保消息正確地發送到RabbitMQ。

       發送方確認模式: 將信道設置成confirm模式(發送方確認模式),則所有在信道上發布的消息都會被指派一個唯一的ID。一旦消息被投遞到目的隊列后,或者消息被寫入磁盤后(可持久化的消息),信道會發送一個確認給生產者(包含消息唯一ID)。如果RabbitMQ發生內部錯誤從而導致消息丟失,會發送一條nack(not acknowledged,未確認)消息。發送方確認模式是異步的,生產者應用程序在等待確認的同時,可以繼續發送消息。當確認消息到達生產者應用程序,生產者應用程序的回調方法就會被觸發來處理確認消息。

       接收方消息確認機制: 消費者接收每一條消息后都必須進行確認(消息接收和消息確認是兩個不同操作)。只有消費者確認了消息,RabbitMQ才能安全地把消息從隊列中刪除。這里並沒有用到超時機制,RabbitMQ僅通過Consumer的連接中斷來確認是否需要重新發送消息。也就是說,只要連接不中斷,RabbitMQ給了Consumer足夠長的時間來處理消息。

下面羅列幾種特殊情況:

  • 如果消費者接收到消息,在確認之前斷開了連接或取消訂閱,RabbitMQ會認為消息沒有被分發,然后重新分發給下一個訂閱的消費者。(可能存在消息重復消費的隱患,需要根據bizId去重)
  • 如果消費者接收到消息卻沒有確認消息,連接也未斷開,則RabbitMQ認為該消費者繁忙,將不會給該消費者分發更多的消息。

12、如何解決消息被重復投遞或重復消費?


       先說為什么會重復消費:正常情況下,消費者在消費消息的時候,消費完畢后,會發送一個確認消息給消息隊列,消息隊列就知道該消息被消費了,就會將該消息從消息隊列中刪除;但是因為網絡傳輸等等故障,確認信息沒有傳送到消息隊列,導致消息隊列不知道自己已經消費過該消息了,再次將消息分發給其他的消費者。

       解決思路是:保證消息的唯一性,就算是多次傳輸,不要讓消息的多次消費帶來影響;保證消息等冪性;

  • 在消息生產時,MQ內部針對每條生產者發送的消息生成一個inner-msg-id,作為去重和冪等的依據(消息投遞失敗並重傳),避免重復的消息進入隊列;
  • 在消息消費時,要求消息體中必須要有一個bizId(對於同一業務全局唯一,如支付ID、訂單ID、帖子ID等)作為去重和冪等的依據,避免同一條消息被重復消費。

       這個問題針對業務場景來答分以下幾點:

  1. 如果消息是做數據庫的insert操作,給這個消息做一個唯一主鍵,那么就算出現重復消費的情況,就會導致主鍵沖突,避免數據庫出現臟數據。

  2. 如果消息是做redis的set的操作,不用解決,因為無論set幾次結果都是一樣的,set操作本來就算冪等操作。

  3. 如果以上兩種情況還不行,可以准備一個第三方介質,來做消費記錄。以redis為例,給消息分配一個全局id,只要消費過該消息,將<id,message>以K-V形式寫入redis。那消費者開始消費前,先去redis中查詢有沒消費記錄即可。

13、如何保證消息的順序性?


       我們遇到的大多數場景都不需要消息的有序的,如果對於消息順序敏感,那么我們這里給出的方法是消息體通過hash分派到隊列里,每個隊列對應一個消費者,多分拆隊列。
       拆分多個 queue,每個 queue 一個 consumer,就是多一些 queue 而已,確實是麻煩點;或者就一個 queue 但是對應一個 consumer,然后這個 consumer 內部用內存隊列做排隊,然后分發給底層不同的 worker 來處理。一句話,主動去分配隊列,單個消費者。

參考:
https://www.jianshu.com/p/b100126a2bf3

https://www.cnblogs.com/huigelaile/p/10928984.html


14、如何保證消息的可靠傳輸,避免丟失的問題?


       消息不可靠的情況可能是消息丟失,劫持等原因;丟失又分為:生產者丟失消息、消息列表丟失消息、消費者丟失消息。
  • 生產者丟失消息:從生產者弄丟數據這個角度來看,RabbitMQ提供transaction和confirm模式來確保生產者不丟消息:

    transaction機制就是說:發送消息前,開啟事務(channel.txSelect()),然后發送消息,如果發送過程中出現什么異常,事務就會回滾(channel.txRollback()),如果發送成功則提交事務(channel.txCommit())。然而這種方式有個缺點:吞吐量下降;

    confirm模式用的居多:一旦channel進入confirm模式,所有在該信道上發布的消息都將會被指派一個唯一的ID(從1開始),一旦消息被投遞到所有匹配的隊列之后,rabbitMQ就會發送一個ACK給生產者(包含消息的唯一ID),這就使得生產者知道消息已經正確到達目的隊列了;如果rabbitMQ沒能處理該消息,則會發送一個Nack消息給你,你可以進行重試操作。

  • 消息隊列丟數據:消息持久化:

    處理消息隊列丟數據的情況,一般是開啟持久化磁盤的配置。 這個持久化配置可以和confirm機制配合使用,你可以在消息持久化磁盤后,再給生產者發送一個Ack信號。這樣,如果消息持久化磁盤之前,rabbitMQ陣亡了,那么生產者收不到Ack信號,生產者會自動重發。

       那么如何持久化呢?需要下面兩步

       1、將queue的持久化標識durable設置為true,則代表是一個持久的隊列;

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

       這樣設置以后,即使rabbitMQ掛了,重啟后也能恢復數據。在消息還沒有持久化到硬盤時,可能服務已經死掉,這種情況可以通過引入mirrored-queue即鏡像隊列,但也不能保證消息百分百不丟失(整個集群都掛掉)

  • 消費者丟失消息:消費者丟數據一般是因為采用了自動確認消息模式,改為手動確認消息即可。

    消費者在收到消息之后,處理消息之前,會自動回復RabbitMQ已收到消息;如果這時處理消息失敗,就會丟失該消息;

    解決方案:處理消息成功后,手動回復確認消息

       自動確認模式:消費者掛掉,待ack的消息回歸到隊列中。消費者拋出異常,消息會不斷的被重發,直到處理成功。不會丟失消息,即便服務掛掉,沒有處理完成的消息會重回隊列,但是異常會讓消息不斷重試。

       手動確認模式:如果消費者來不及處理就死掉時,沒有響應ack時會重復發送一條信息給其他消費者;如果監聽程序處理異常了,且未對異常進行捕獲,會一直重復接收消息,然后一直拋異常;如果對異常進行了捕獲,但是沒有在finally里ack,也會一直重復發送消息(重試機制)。

       不確認模式:acknowledge="none" 不使用確認機制,只要消息發送完成會立即在隊列移除,無論客戶端異常還是斷開,只要發送完就移除,不會重發。


15、死信隊列和延遲隊列的使用?


死信消息:

  1. 消息被拒絕(Basic.Reject或Basic.Nack)並且設置 requeue 參數的值為 false
  2. 消息過期了
  3. 隊列達到最大的長度

過期消息:

       在rabbitmq 中存在2種方可設置消息的過期時間,第一種通過對隊列進行設置,這種設置后,該隊列中所有的消息都存在相同的過期時間,第二種通過對消息本身進行設置,那么每條消息的過期時間都不一樣。如果同時使用這2種方法,那么以過期時間小的那個數值為准。當消息達到過期時間還沒有被消費,那么那個消息就成為了一個死信消息。

       隊列設置:在隊列申明的時候使用 x-message-ttl 參數,單位為 毫秒

       單個消息設置:是設置消息屬性的 expiration 參數的值,單位為 毫秒

延時隊列:

       在rabbitmq中不存在延時隊列,但是我們可以通過設置消息的過期時間和死信隊列來模擬出延時隊列。消費者監聽死信交換器綁定的隊列,而不要監聽消息發送的隊列。

       創建延遲隊列:

       1、延時隊列可以由過期消息+死信隊列來實現

       2、過期消息通過隊列中設置 x-message-ttl 參數實現

       3、死信隊列通過在隊列申明時,給隊列設置 x-dead-letter-exchange 參數,然后另外申明一個隊列綁定x-dead-letter-exchange對應的交換器

16、RabbitMQ的集群


       鏡像集群模式,創建的queue,無論元數據還是queue里的消息都會存在於多個實例上,然后每次寫消息到queue的時候,都會自動把消息到多個實例的queue里進行消息同步。 集群模式增加了系統的可用性但同時也增加了性能開銷,消息同步所有機器,導致網絡帶寬壓力和消耗很重。

參考:

https://blog.csdn.net/qq_42629110/article/details/84965084

https://www.cnblogs.com/woadmin/p/10537174.html

https://blog.csdn.net/jerryDzan/article/details/89183625


免責聲明!

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



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