Java高頻面試題---RabbitMQ

respectxx發表於2024-03-14

一、RabbitMQ的架構設計

Rabbit是一個開源的訊息中介軟體,用於在應用程式之間傳遞訊息。它實現了AMQP(高階訊息佇列協議)並支援其他訊息傳遞協議,如STOMP和MQTT。

整體架構如下:

Producer(生產者):生產者是訊息的傳送方,負責將訊息釋出到RabbitMQ的交換機(Exchange)。

VHost:是RabbitMQ中虛擬主機的概念,它類似於作業系統的名稱空間,用於將RabbitMQ的資源進行隔離和分組。每個VHost擁有自己的交換機、佇列、繫結和許可權設定,不同的VHost之間的資源互相獨立,互不干擾。VHost開源用於將不同的應用或服務進行隔離,以防止彼此之間的訊息衝突和資源競爭。

Exchange(交換機):交換機是訊息的接收和路由中心,它接受來自生產者的訊息,並將訊息路由到一個或多個與之繫結的佇列(Queue)中。

Queue(佇列):佇列是訊息的儲存和消費地,它儲存著未被消費的訊息,等待消費者(Consumer)從佇列獲取並處理訊息。

Binding(繫結):繫結是交換機和佇列之間的關聯關係,它定義了交換機將資訊路由到了哪些佇列中。

Consumer(消費者):消費者是訊息的接收方,負責從佇列獲取訊息,並進行處理和消費。

二、Rabbit是怎麼做訊息分發的?

rabbitMQ一共有6種工作模式(訊息分發方式)分別是簡單模式、工作佇列模式、釋出訂閱模式、

路由模式、主題模式以及RPC模式

簡單模式:最基本的工作模式,也是最簡單的訊息傳遞方式。在簡單模式中,一共生產者將訊息傳送到一個佇列中,一個消費者從佇列中獲取並處理訊息。這種模式適應於單個生產者和單個消費者的簡單場景,訊息處理也是同步的。

工作佇列模式:用於實現一個任務在多個消費者之間的併發處理。在工作佇列模式中,一個生產者將訊息傳送到一個佇列中,多個消費者從中獲取並處理訊息。每個訊息只能被一個消費者處理。這種模式適用於多個消費者併發處理資訊的情況,提高了系統的處理能力和吞吐量。

釋出/訂閱模式:用於實現一條訊息被多個消費者同時接收和處理。在釋出/訂閱模式中,一個生產者將訊息傳送到交換機(Exchange)中,交換機將訊息廣播到所有繫結的佇列,每個佇列對應一個消費者。這種模式適用於訊息需要被多個消費者同時接收和處理的場景,如日誌訂閱和事件通知等。

路由模式:用於實現根據訊息的路由鍵(Routing Key)將訊息路由到不同的佇列中。在路由模式中,一個生產者將訊息傳送到交換機中,並指定訊息的路由鍵,交換機根據路由鍵將訊息路由到與之匹配的佇列中。這種模式適用於根據不同的條件將訊息傳送到不同的佇列中,以實現訊息的篩選和分發。

主題模式:是一種更靈活的訊息路由模式,它使用萬用字元匹配路由鍵,將訊息路由到多個佇列中。在主題模式中,一個生產者將訊息傳送到交換機中,並指定主題(Topic)作為路由鍵,交換機根據萬用字元匹配將訊息路由到與之匹配的佇列中。這種模式適用於用於訊息的複雜路由需求,可以實現高度靈活的篩選和分發。

RPC模式:是一種用於實現分散式系統中遠端呼叫的工作模式。指的是透過rabbitMQ來實現一種RPC的能力。

三、RabbitMQ如何實現延遲資訊訊息?

有兩種方式:① 死信佇列 ② 延遲訊息外掛

死信佇列

當RabbitMQ中的一條正常的訊息,因為過了存活時間(TTL過期)、佇列長度超限、被消費者拒絕等原因無法被消費時,就會變成Dead Message,即死信。

當一個訊息變成死信後,它就能重新傳送到死信佇列中(其實是交換機-- Exchange)。

基於這樣的機制,就可以實現延遲訊息。首先給定一個訊息設定 TTL ,但是並不消費這個訊息,等他過期,過期之後就會進入到死信佇列,然後我們再監聽死信佇列的訊息消費就行了。

而且,RabbitMQ中的TTL是可以設定任意時長的,這相比於RocketMQ只支援一些固定的時長更加靈活一些。

存在的問題:可能會造成對頭阻塞,因為佇列是先進先出的,而且每次只會判斷隊頭的訊息是否過期,那麼,如果對頭的資訊時間很長,一直都不過期,那麼就會阻塞整個佇列,這時候即使安排在它後面的訊息過期了,那麼也會被一直阻塞。

基於RabbitMQ的死信佇列,可以實現延遲訊息,非常靈活的實現定時關單,並且藉助RabbitMQ的叢集擴充套件性,可以實現高可用,以及處理大併發量。缺點就是可能會儲存訊息阻塞的問題,還有就是方案比較複雜,不僅要依賴於RabbitMQ,而且還需要宣告很多佇列,增加系統的複雜度。

RabbitMQ外掛

基於RabbitMQ的話,不用死信佇列也可以實現延遲訊息,那就是基於 rabbitmq_delayed_massage_exchange外掛,這種方案能夠解決透過死信佇列實現延遲訊息出現的訊息阻塞問題。該外掛是RabbitMQ 3.6.12 開始支援的,對版本有要求。安裝之後就可以建立delated-message型別的交換機了。

區別:基於死信佇列的方式,是訊息會先投遞到一個正常的佇列中,在 TTL 過期後進入死信佇列。但是基於外掛的方式,訊息並不會立即進去佇列,而且先把它儲存在一個基於Erlang開發的Mnesia資料庫中,然後透過一個定時器取查詢需要被投遞的訊息,再把它們投遞到x-delayed-message交換機中。

缺點(限制):因為將訊息儲存於Mnesia表中,並且在當前節點上具有單個磁碟副本,會存在丟失的可能。

四、什麼是RabbitMQ的死信佇列?

RabbitMQ的死信佇列(Dead Letter Queue ,簡稱QLD)是一種用於訊息處理失敗或無法路由的訊息的機制。它允許將無法被正常消費的消費重新路由到一個佇列,以便稍後進一步的處理、分析和排查問題。

當訊息佇列裡面的訊息出現以下幾種情況時,就可能被稱為“死信”:

訊息處理失敗:當消費者由於程式碼錯誤、訊息格式不正確、業務規則衝突等原因無法成功處理一條訊息時,這條訊息可以被標記為死信。

訊息過期:在RabbitMQ中,訊息可以設定過期時間。如果訊息在規定時間內沒有被消費,它可以被認為是死信並被髮送到死信佇列。

訊息被拒絕:當消費者明確拒絕一條訊息時,它可以被標記為死信併傳送到死信佇列。拒絕訊息的原因可能是訊息無法處理,獲取消費者認為訊息不符合處理條件。

訊息無法路由:當訊息不能被路由到任何佇列時,例如,沒有匹配的繫結關係或路由鍵時,訊息可以被髮送到死信佇列。

當訊息變成 “死信”之後,如何配置了死信佇列,它將會被髮送到死信交換機,死信交換機將死信投遞到一個佇列上,這個佇列就是死信佇列。但是如果沒有配置死信佇列,那麼這個訊息將被丟棄。

RabbitMQ的死信佇列其實有很多作用,比如實現延遲訊息,進而實現訂單的到期關閉,超時關單的業務邏輯。

五、RabbitMQ是如何保證高可用的?

RabbitMQ可以透過多種方式來實現高可用性,以確保在硬體故障或其他不可預測的情況下,訊息佇列系統仍然能夠正常執行。RabbitMQ有三種模式:單機模式、普通叢集模式、映象叢集模式

其中單機模式一般用於demo搭建,不適合在生產環境中使用。剩下的叢集模式和映象模式都可以實現不同程度的高可用。

普通叢集模式

普通叢集模式,就是將RabbitMQ例項部署到多臺伺服器上,多個例項之間協同工作,共享佇列和交換機的後設資料,並透過內部通訊協議來協調訊息的傳遞和管理。

在這種模式下,我們建立的Queue,它的後設資料(配置資訊)會在叢集中的所有例項間進行同步,但是佇列中的訊息只會存在一個RabbitMQ例項上,而不會同步到其他佇列。

當我們消費訊息的時候,如果消費者連線到了為儲存訊息的例項,那麼那個例項會透過後設資料定位到訊息所在的例項,拉取資料過來傳送給消費者進行消費。

訊息的傳送也是一樣的,當傳送者連線到了一個不儲存訊息的例項時,也會被轉發到儲存資訊的例項上進行寫操作。

這種叢集模式下,每一個例項中的後設資料是一樣的,大家都是完整的資料。但是佇列中的訊息資料,在不同的例項上儲存的是不一樣的。這樣透過增加例項的方式就可以提升整個叢集的訊息儲存量,以及效能。

這種方式在高可用上有一段幫助,不至於一個節點掛了就全掛了。但是也還有缺點,至少這個是例項上的資料是沒法被讀寫了。

映象模式

顧名思義,就是每一臺RabbitMQ都像一個映象一樣,儲存的內容都是一樣的。這種模式下,Queue的後設資料和訊息資料不再是單獨儲存在某個例項上,而是叢集中所有例項上都會儲存一份。

這樣每次在訊息寫入的時候,就需要在叢集中的所有例項上都同步一份,這樣即使有一臺例項發生故障,剩餘的例項也可以正常提供完整的資料和服務。

這種模式下,就保障了RabbitMQ的高可用。

六、RabbitMQ如何實現消費端限流?

什麼是消費端限流,這是一種保護消費者的手段,假如說,現在是業務高峰期,訊息由大量堆積,導致MQ消費者需要不斷地進行訊息消費,很容易被打掛,甚至重啟之後還是被大量訊息湧入,繼續被打掛。

為了解決這個問題,RabbitMQ提供了basicQos地方式來實現消費端限流。我們可以在消費者端指定最大地未確認訊息數,當達到這個限制時,Rabbit將不再推送新的訊息給消費者,直到有一些訊息得到確認。

要想實現這個功能,首先把自動提交關閉。

接著進行限流配置:

如以上配置,可以實現消費者在處理完一條訊息後,才會獲取下一條訊息。

然後再在消費者處理完一條訊息後,手動傳送確認訊息給到RabbitMQ,這樣就可以拉取下一條訊息了:

七、RabbitMQ如何防止重複消費?

RabbitMQ的訊息是有確認機制的,正常情況下,消費者在訊息消費成功後,會傳送一個確認訊息,訊息佇列接收到之後,就會將該訊息從訊息佇列中刪除,下次也就不會再投遞了。

但是如果存在網路延遲的問題,導致確認訊息沒有傳送到訊息佇列,導致訊息重投了,是有可能,所以,在使用MQ時,消費者端自己也需要做好冪等控制來防止訊息被重複消費。

一般來說,處理這種冪等問題就是“一鎖、二判、三更新”

一鎖:第一步,先加鎖,可以加分散式鎖,悲觀鎖都可以。但是一定要是個互斥鎖!

二判:第二步,進行冪等性判斷。可以基於狀態機、流水錶、唯一性索引等進行重複操作的判斷。

三更新:第三步,進行資料的更新,將資料進行持久化。

也就是說我們在傳送訊息是需要生成一個唯一的標識並且把它放到訊息體中,根據這個標識就可以判斷兩次訊息是不是同一條。這樣我們在消費者端,接收到訊息以後,只需要解析出訊息體中的這個唯一標識,就可以判斷是否消費成功過了。

八、如何保障訊息一定能傳送到RabbitMQ?

作為一個訊息傳送方,如何保證給RabbitMQ傳送的訊息一定能傳送成功,如何確保它一定能收到這個訊息呢?

RabbitMQ的訊息最終是儲存在Queue上的,而在Queue之前還要經過Exchange,那麼這個過程中就有兩個地方可能導致訊息丟失。第一個是Producer到Exchange的過程,第二個就是Exchange到Queue的過程。

為了解決這個問題,有兩種方案,一種是透過 confirm 機制,另外就是事務機制(不推薦)。

上面兩個可能丟失的過程,都可以利用confirm機制,註冊回撥來監聽是否成功。

Publisher Confirm是一種機制,用於確保訊息已經被Exchange成功接收和處理。一旦訊息成功到達Exchange並被處理,RabbitMQ會向訊息生產者傳送確認訊號(ACK)。如果由於某種原因(例如,Exchange不存在或路由鍵不匹配)訊息無法被處理,RabbitMQ會向訊息生產者傳送否認訊號(NACK)。

Publisher Returns機制與Publisher Confirm類似,但用於處理訊息無法路由到任何佇列時的情況。當RabbitMQ在無法路由訊息時將訊息返回給消費者,但是如果能正確路由,則不會返回訊息。

透過以上方式,註冊了兩個回撥監聽,用於在訊息傳送到Exchange或者Queue失敗時進行異常處理。通常可以在失敗時進行報警或者重試來保障一定能傳送成功。

九、RabbitMQ如何保證訊息不丟失?

透過confirm 機制確保了RabbitMQ的生產者能夠把訊息投遞給RabbitMQ的Exchange和Queue,那麼,Queue又是如何保證訊息能夠不丟失呢?

RabbitMQ在接收到訊息後,預設並不會立即進行持久化,而是先把訊息暫存在記憶體中,這時候如果MQ掛了,那麼訊息就會丟失。所以要透過持久化機制來保證訊息可以被持久化下來。

佇列和交換機的持久化

在宣告佇列時,可以透過設定 durable 引數為ture來建立一個持久化佇列。持久化佇列會在RabbitMQ伺服器重啟後保留,確保佇列的後設資料不會丟失。

在宣告交換機時,也可以透過 durable 引數為ture來建立一個持久化交換機。持久化交換機會在RabbitMQ伺服器重啟後保留,以確保交換機的後設資料不會丟失。

繫結關係通常與佇列和交換機關聯。當建立繫結關係時,還是可以設定 durable 引數為ture,以建立一個持久化繫結。持久化繫結關係會在伺服器重啟後保留,以確保繫結關係不會丟失。

持久化訊息

生產者傳送的訊息可以透過設定訊息的deliveryMode 為 2來建立持久化訊息。持久化訊息傳送到持久化佇列後,將在伺服器重啟後保留,以確保訊息不會丟失。

透過設定 deliveryMode 類來實現訊息的持久化。但是將訊息設定為持久化會增加磁碟I/O開銷。

消費者確認機制

有了持久化機制後,那麼怎麼保證訊息在持久化下來之後一定能被消費者消費呢?這裡涉及到訊息的消費者確認機制。

在RabbitMQ中,消費者處理訊息成功後可以向MQ傳送ack回執,MQ收到回執後才會刪除該訊息,這樣才能確保訊息不丟失。如果消費者在處理訊息中出現了異常,那麼就會返回nack回執,MQ收到這個回執之後就會重新投遞一次訊息,如果消費者一直都沒有返回ACK / NACK的話,那麼它會再嘗試重新投遞。

無法做到100%不丟。

雖然透過傳送者進行非同步回撥,MQ進行持久化,消費者確認機制,但是也無法保證訊息100%不丟,因為MQ的持久化過程是非同步的。即使開了持久化,也有可能再記憶體暫存成功後,非同步持久化之間當機了,那麼這個訊息就會丟失。

十、介紹下RabbitMQ的事務機制

RabbitMQ的事務機制,允許生產者將一組操作打包成一個原子事務單元,要麼全部執行成功,要麼全部失敗。事務提供了一種確保資訊完整性的方法,但需要謹慎使用,因為它們對效能有一定的影響。

RabbitMQ是基於AMQP協議實現的,RabbitMQ中,事務是透過在通道(Channel)上啟用的,與事務機制有關的方法有三個:

① txSelect() :將當前channel設定成transaction模式。

② txCommit() :提交事務。

③ txRollback() :回滾事務。

我們先透過txSelect方法開啟事務,然後就可以釋出訊息給MQ,如果txCommit提交成功了,則訊息一定到達了MQ,如果在txCommit執行之前MQ例項異常崩潰或者丟擲異常,那我們就可以捕獲這個異常然後執行txRollback進行回滾事務。

所以,透過事務機制,也能確保訊息一定可以傳送到MQ。

相關文章