RabbitMq常見面試題

使用RabbitMQ有什麼好處?

1.解耦,系統A在代碼中直接調用系統B和系統C的代碼,如果將來D系統接入,系統A還需要修改代碼,過於麻煩!

2.異步,將消息寫入消息隊列,非必要的業務邏輯以異步的方式運行,加快響應速度

3.削峯,併發量大的時候,所有的請求直接懟到數據庫,造成數據庫連接異常

rabbitmq組件斷連重連機制

方案一:
Rabbitmq在啓動時,爲rabbitmq設置一個status,在第一次建立連接的時候將其變爲true,rabbitmq client在初始化時啓動一個定時器,每隔一段時間開啓一個線程,查詢當前status的狀態,如果status變爲false,重新建立連接(包括connection、channel的連接)。
方案二:
Implement shutdown listener,如果rabbitmq斷線,在shutdown方法執行相應的重連方法。

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

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

1.比如,你拿到這個消息做數據庫的insert操作。那就容易了,給這個消息做一個唯一主鍵,那麼就算出現重複消費的情況,就會導致主鍵衝突,避免數據庫出現髒數據。

2.再比如,你拿到這個消息做redis的set的操作,那就容易了,不用解決,因爲你無論set幾次結果都是一樣的,set操作本來就算冪等操作。

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

如何保證消息的順序性?

針對這個問題,通過某種算法,將需要消息排完序放到同一個消息隊列中。然後只用一個消費者去消費該隊列。同一個queue裏的消息一定是順序消息的。我的觀點是保證入隊有序就行,出隊以後的順序交給消費者自己去保證,沒有固定套路。

如何確保消息接收方消費了消息?

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

下面羅列幾種特殊情況:

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

如何確保消息正確地發送至RabbitMQ?

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

消息基於什麼傳輸?

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

vhost 是什麼?起什麼作用?

vhost 可以理解爲虛擬 broker ,即 mini-RabbitMQ  server。其內部均含有獨立的 queue、exchange 和 binding 等,但最最重要的是,其擁有獨立的權限系統,一個broker裏可以有多個vhost,用作不同用戶的權限分離。

RabbitMQ 中的 broker 是指什麼?cluster 又是指什麼?

broker 是指一個或多個 erlang node 的邏輯分組,且 node 上運行着 RabbitMQ 應用程序。cluster 是在 broker 的基礎之上,增加了 node 之間共享元數據的約束。

消息怎麼路由?

從概念上來說,消息路由必須有三部分:交換器、路由、綁定。生產者把消息發佈到交換器上;綁定決定了消息如何從路由器路由到特定的隊列;消息最終到達隊列,並被消費者接收。

消息發佈到交換器時,消息將擁有一個路由鍵(routing key),在消息創建時設定。
通過隊列路由鍵,可以把隊列綁定到交換器上。
消息到達交換器後,RabbitMQ會將消息的路由鍵與隊列的路由鍵進行匹配(針對不同的交換器有不同的路由規則)。如果能夠匹配到隊列,則消息會投遞到相應隊列中;如果不能匹配到任何隊列,消息將進入 “黑洞”。
常用的交換器主要分爲一下5種:

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

RabbitMQ 消費端限流

1. 爲什麼要對消費端限流

假設一個場景,首先,我們 Rabbitmq 服務器積壓了有上萬條未處理的消息,我們隨便打開一個消費者客戶端,會出現這樣情況: 巨量的消息瞬間全部推送過來,但是我們單個客戶端無法同時處理這麼多數據!

當數據量特別大的時候,我們對生產端限流肯定是不科學的,因爲有時候併發量就是特別大,有時候併發量又特別少,我們無法約束生產端,這是用戶的行爲。所以我們應該對消費端限流,用於保持消費端的穩定,當消息數量激增的時候很有可能造成資源耗盡,以及影響服務的性能,導致系統的卡頓甚至直接崩潰。

2.限流的 api 講解

RabbitMQ 提供了一種 qos (服務質量保證)功能,即在非自動確認消息的前提下如果一定數目的消息(通過基於 consume 或者 channel 設置 Qos 的值)未被確認前,不進行消費新的消息。

void basicQos(int prefetchSize, int prefetchCount, boolean global) throws IOException;
  • prefetchSize:0,單條消息大小限制,0代表不限制

  • prefetchCount:一次性消費的消息數量。會告訴 RabbitMQ 不要同時給一個消費者推送多於 N 個消息,即一旦有 N 個消息還沒有 ack,則該 consumer 將 block 掉,直到有消息 ack。

  • global:true、false 是否將上面設置應用於 channel,簡單點說,就是上面限制是 channel 級別的還是 consumer 級別。當我們設置爲 false 的時候生效,設置爲 true 的時候沒有了限流功能,因爲 channel 級別尚未實現。

  • 注意:prefetchSize 和 global 這兩項,rabbitmq 沒有實現,暫且不研究。特別注意一點,prefetchCount 在 no_ask=false 的情況下才生效,即在自動應答的情況下這兩個值是不生效的。

3.如何對消費端進行限流

  • 首先第一步,我們既然要使用消費端限流,我們需要關閉自動 ack,將 autoAck 設置爲 falsechannel.basicConsume(queueName, false, consumer);

  • 第二步我們來設置具體的限流大小以及數量。channel.basicQos(0, 15, false);

  • 第三步在消費者的 handleDelivery 消費方法中手動 ack,並且設置批量處理 ack 迴應爲 truechannel.basicAck(envelope.getDeliveryTag(), true);

  • 當我們將void basicQos(int prefetchSize, int prefetchCount, boolean global)中的 global 設置爲 true的時候我們發現並沒有了限流的作用。

rabbitmq死信隊列

producer將消息投遞到broker或者直接到queue裏了,consumer從queue取出消息進行消費,但某些時候由於特定的原因導致queue中的某些消息無法被消費,這樣的消息如果沒有後續的處理,就變成了死信,有死信,自然就有了死信隊列;

對rabbitmq來說,產生死信的來源大致有如下幾種:

  • 消息被拒絕(basic.reject或basic.nack)並且requeue=false.
  • 消息TTL過期
  • 隊列達到最大長度(隊列滿了,無法再添加數據到mq中)死信的處理方式

死信的產生既然不可避免,那麼就需要從實際的業務角度和場景出發,對這些死信進行後續的處理,常見的處理方式大致有下面幾種,

死信處理過程

  • DLX也是一個正常的Exchange,和一般的Exchange沒有區別,它能在任何的隊列上被指定,實際上就是設置某個隊列的屬性。
  • 當這個隊列中有死信時,RabbitMQ就會自動的將這個消息重新發布到設置的Exchange上去,進而被路由到另一個隊列。
  • 可以監聽這個隊列中的消息做相應的處理。

死信隊列設置

首先需要設置死信隊列的exchange和queue,然後進行綁定:

Exchange: dlx.exchange
Queue: dlx.queue
RoutingKey: #
#表示只要有消息到達了Exchange,那麼都會路由到這個queue上
  • 然後需要有一個監聽,去監聽這個隊列進行處理
  • 然後我們進行正常聲明交換機、隊列、綁定,只不過我們需要在隊列加上一個參數即可:arguments.put(" x-dead-letter-exchange","dlx.exchange");,這樣消息在過期、requeue、 隊列在達到最大長度時,消息就可以直接路由到死信隊列!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章