柚子快報(bào)邀請(qǐng)碼778899分享:RabbitMQ 常見面試題
柚子快報(bào)邀請(qǐng)碼778899分享:RabbitMQ 常見面試題
目錄
1.前置知識(shí)1.1.什么是 MQ?它有什么作用?1.2.什么是消費(fèi)者生產(chǎn)者模型?1.3.AMQP 是什么?
2.RabbitMQ 入門2.1.什么是 RabbitMQ?有什么特點(diǎn)?2.2.RabbitMQ 的核心概念有哪些?2.2.1.生產(chǎn)者 (Producer)、消費(fèi)者 (Consumer) 和消息中間件的服務(wù)節(jié)點(diǎn) (Broker)2.2.3.隊(duì)列 (Queue)2.2.4.交換器 (Exchange)、路由鍵 (RoutingKey)、綁定 (Binding)
2.3.?交換機(jī) (Exchange) 有哪些類型?不同類型的交換機(jī)什么應(yīng)用場(chǎng)景?2.4.?RabbitMQ 的工作原理是什么?2.5.RabbitMQ 中的消息怎么傳輸?
3.RabbitMQ 進(jìn)階3.1.當(dāng)消息的生產(chǎn)者將消息發(fā)送出去之后,如何確定消息到底有沒有正確地到達(dá)服務(wù)器?3.2.請(qǐng)介紹一下 RabbitMQ 中的事務(wù)機(jī)制。3.3.使用 RabbitMQ 事務(wù)機(jī)制可能會(huì)引發(fā)什么問題?如何解決?3.4.RabbitMQ 中的發(fā)布確認(rèn)機(jī)制是指什么?3.5.?發(fā)布確認(rèn)機(jī)制有哪些實(shí)現(xiàn)方式?3.6.什么是死信交換機(jī)和死信隊(duì)列?死信隊(duì)列有哪些應(yīng)用場(chǎng)景?3.7.什么是延遲隊(duì)列?它有哪些應(yīng)用場(chǎng)景?3.8.如何設(shè)置死信隊(duì)列?3.9.什么是優(yōu)先級(jí)隊(duì)列?它有哪些應(yīng)用場(chǎng)景?3.10.如何設(shè)置優(yōu)先級(jí)隊(duì)列?3.11.RabbitMQ 中的冪等性是指什么?如何保證?3.12.RabbitMQ 中的消息持久化是指什么?3.13.如何解決消息隊(duì)列的延時(shí)以及過期失效問題?3.14.?如何保證 RabbitMQ 中消息的順序性和可靠性?3.15.RabbitMQ 有哪些工作模式?
4.RabbitMQ 高階4.1.RabbitMQ 中的集群是指什么?如何搭建?4.2.如何保證 RabbitMQ 的高可用性?4.3.什么是惰性隊(duì)列是?它有哪些應(yīng)用場(chǎng)景?4.4.RabbitMQ 中的鏡像隊(duì)列是指什么?有哪些應(yīng)用場(chǎng)景?
參考文章: RabbitMQ——入門介紹 《RabbitMQ 實(shí)戰(zhàn)指南》朱忠華著
1.前置知識(shí)
1.1.什么是 MQ?它有什么作用?
(1)MQ 指的是 Message Queue(消息隊(duì)列),其本質(zhì)上是個(gè)隊(duì)列(FIFO 先進(jìn)先出),它是一種在分布式系統(tǒng)中用于異步通信的技術(shù)。消息隊(duì)列可以讓不同的組件(例如應(yīng)用程序、進(jìn)程、服務(wù)等)通過發(fā)送和接收消息來進(jìn)行通信,這些消息被保存在隊(duì)列中,直到被接收方處理。
(2)MQ 的作用如下:
應(yīng)用解耦:通過使用消息隊(duì)列,不同的組件可以通過發(fā)送和接收消息來進(jìn)行通信,這使得它們之間的耦合度更低。這意味著當(dāng)一個(gè)組件發(fā)生變化時(shí),其他組件不需要進(jìn)行大規(guī)模的更改,從而使系統(tǒng)更加靈活。異步通信:使用消息隊(duì)列可以實(shí)現(xiàn)異步通信,即發(fā)送方不需要等待接收方處理完消息才能繼續(xù)執(zhí)行。這使得系統(tǒng)可以更高效地利用資源,處理大量請(qǐng)求??煽啃裕合㈥?duì)列通常提供多種機(jī)制來確保消息的可靠性,例如確認(rèn)機(jī)制、持久化、重試等。這些機(jī)制可以保證即使在出現(xiàn)故障或網(wǎng)絡(luò)異常的情況下,消息也不會(huì)丟失或重復(fù)發(fā)送。擴(kuò)展性:通過使用消息隊(duì)列,系統(tǒng)可以更容易地?cái)U(kuò)展,因?yàn)椴煌慕M件可以獨(dú)立地處理消息,而不需要進(jìn)行緊密的協(xié)調(diào)。這使得系統(tǒng)可以更容易地處理大量請(qǐng)求和并發(fā)訪問。順序保證:在大多數(shù)使用場(chǎng)景下,數(shù)據(jù)處理的順序很重要,大部分消息中間件支持一定程度上的順序性。緩解流量高峰:消息隊(duì)列可以緩解流量高峰的問題,因?yàn)榘l(fā)送方可以將請(qǐng)求發(fā)送到消息隊(duì)列中,而不是直接發(fā)送給接收方。這可以使接收方更容易處理請(qǐng)求,而不會(huì)因?yàn)橥蝗坏母叻鍖?dǎo)致系統(tǒng)崩潰。
1.2.什么是消費(fèi)者生產(chǎn)者模型?
(1)消費(fèi)者生產(chǎn)者模型 (Producer-Consumer Model) 是一種并發(fā)編程模型,通常用于解決生產(chǎn)者和消費(fèi)者之間的數(shù)據(jù)共享和同步問題。在該模型中,生產(chǎn)者負(fù)責(zé)生成數(shù)據(jù)并將其添加到共享數(shù)據(jù)結(jié)構(gòu)中,而消費(fèi)者則負(fù)責(zé)從共享數(shù)據(jù)結(jié)構(gòu)中獲取數(shù)據(jù)并進(jìn)行處理。
(2)具體來說,生產(chǎn)者將數(shù)據(jù)放入共享緩沖區(qū)中,消費(fèi)者從共享緩沖區(qū)中取出數(shù)據(jù)進(jìn)行處理。如果共享緩沖區(qū)已滿,則生產(chǎn)者需要等待,直到有空間可用。同樣,如果共享緩沖區(qū)為空,則消費(fèi)者需要等待,直到有數(shù)據(jù)可用。這個(gè)模型通過同步機(jī)制來實(shí)現(xiàn)生產(chǎn)者和消費(fèi)者之間的協(xié)作,以確保數(shù)據(jù)的正確性和一致性。
(3)生產(chǎn)者消費(fèi)者模型常用于多線程編程、進(jìn)程間通信、消息隊(duì)列等場(chǎng)景中。例如,在消息隊(duì)列中,生產(chǎn)者將消息發(fā)送到隊(duì)列中,而消費(fèi)者從隊(duì)列中獲取消息進(jìn)行處理。這種模型能夠有效地解耦生產(chǎn)者和消費(fèi)者,提高系統(tǒng)的可擴(kuò)展性和可靠性。
1.3.AMQP 是什么?
(1)RabbitMQ 是 AMQP 協(xié)議的 Erlang 的實(shí)現(xiàn)(RabbitMQ 也支持 STOMP2、 MQTT3 等協(xié)議)。AMQP 的模型架構(gòu)和 RabbitMQ 的模型架構(gòu)是一樣的。RabbitMQ 中的交換器、交換器類型、隊(duì)列、綁定、路由鍵等都是遵循的 AMQP 協(xié)議中相應(yīng)的概念。
(2)AMQP 協(xié)議本身包括三層:
Module Layer:位于協(xié)議最高層,主要定義了一些供客戶端調(diào)用的命令,客戶端可以利用這些命令實(shí)現(xiàn)自己的業(yè)務(wù)邏輯。例如,客戶端可以使用 Queue.Declare 命令聲明一個(gè)隊(duì)列或者使用 Basic.Consume 訂閱消費(fèi)一個(gè)隊(duì)列中的消息。Session Layer:位于中間層,主要負(fù)責(zé)將客戶端的命令發(fā)送給服務(wù)器,再將服務(wù)端的應(yīng)答返回給客戶端,主要為客戶端與服務(wù)器之間的通信提供可靠性同步機(jī)制和錯(cuò)誤處理。Transport Layer:位于最底層,主要傳輸二進(jìn)制數(shù)據(jù)流,提供幀的處理、信道復(fù)用、錯(cuò)誤檢測(cè)和數(shù)據(jù)表示等。
2.RabbitMQ 入門
2.1.什么是 RabbitMQ?有什么特點(diǎn)?
(1)RabbitMQ 是一個(gè)開源的消息中間件,它實(shí)現(xiàn)了高度靈活的消息隊(duì)列模式。它是使用 Erlang 語言開發(fā)的,基于AMQP(Advanced Message Queuing Protocol,高級(jí)消息隊(duì)列協(xié)議),并且提供了許多客戶端庫,包括Java、Python、Ruby等,使得開發(fā)者可以方便地與其進(jìn)行交互。
(2)RabbitMQ 的具體特點(diǎn)可以概括為以下幾點(diǎn):
可靠性:RabbitMQ 使用一些機(jī)制來保證可靠性,如持久化、傳輸確認(rèn)及發(fā)布確認(rèn)等。靈活的路由:在消息進(jìn)入隊(duì)列之前,通過交換器來路由消息。對(duì)于典型的路由功能,RabbitMQ 己經(jīng)提供了一些內(nèi)置的交換器來實(shí)現(xiàn)。針對(duì)更復(fù)雜的路由功能,可以將多個(gè)交換器綁定在一起, 可以通過插件機(jī)制來實(shí)現(xiàn)自己的交換器。擴(kuò)展性:多個(gè) RabbitMQ 節(jié)點(diǎn)可以組成一個(gè)集群,也可以根據(jù)實(shí)際業(yè)務(wù)情況動(dòng)態(tài)地?cái)U(kuò)展集群中節(jié)點(diǎn)。高可用性:隊(duì)列可以在集群中的機(jī)器上設(shè)置鏡像,使得在部分節(jié)點(diǎn)出現(xiàn)問題的情況下隊(duì)列仍然可用。多種協(xié)議 RabbitMQ 除了原生支持 AMQP 協(xié)議,還支持 STOMP、MQTT 等多種消息中間件協(xié)議。多語言客戶端:RabbitMQ 幾乎支持所有常用語言,比如 Java、Python、Ruby、PHP、C#、JavaScript 等。管理界面:RabbitMQ 提供了一個(gè)易用的用戶界面,使得用戶可以監(jiān)控和管理消息、集群中的節(jié)點(diǎn)等。插件機(jī)制: RabbitMQ 提供了許多插件 以實(shí)現(xiàn)從多方面進(jìn)行擴(kuò)展,當(dāng)然也可以編寫自己的插件。
2.2.RabbitMQ 的核心概念有哪些?
RabbitMQ 整體上是一個(gè)生產(chǎn)者與消費(fèi)者模型,主要負(fù)責(zé)接收、存儲(chǔ)和轉(zhuǎn)發(fā)消息??梢园严鬟f的過程想象成:當(dāng)你將一個(gè)包裹送到郵局,郵局會(huì)暫存并最終將郵件通過郵遞員送到收件人的手上, RabbitMQ 就好比由郵局、郵箱和郵遞員組成的一個(gè)系統(tǒng)。從計(jì)算機(jī)術(shù)語層面來說,RabbitMQ 模型更像是一種交換機(jī)模型。RabbitMQ 的整體模型架構(gòu)如下圖所示:
2.2.1.生產(chǎn)者 (Producer)、消費(fèi)者 (Consumer) 和消息中間件的服務(wù)節(jié)點(diǎn) (Broker)
(1)Producer:生產(chǎn)者,就是投遞消息的一方。生產(chǎn)者創(chuàng)建消息,然后發(fā)布到 RabbitMQ 中。消息一般可以包含 2 個(gè)部分:消息體和標(biāo)簽。消息體也可以稱之為 payload,在實(shí)際應(yīng)用中,消息體般是一個(gè)帶有業(yè)務(wù)邏輯結(jié)構(gòu)的數(shù)據(jù),比如一個(gè) JSON 字符串。當(dāng)然可以進(jìn)一步對(duì)這個(gè)消息體進(jìn)行序列化操作。消息的標(biāo)簽用來表述這條消息,比如一個(gè)交換器的名稱和一個(gè)路由鍵。生產(chǎn)者把消息交由 RabbitMQ,RabbitMQ 之后會(huì)根據(jù)標(biāo)簽把消息發(fā)送給感興趣的消費(fèi)者。
(2)Consumer:消費(fèi)者,就是接收消息的一方。消費(fèi)者連接到 RabbitMQ 服務(wù)器,并訂閱到隊(duì)列上,當(dāng)消費(fèi)者消費(fèi)一條消息時(shí),只是消費(fèi)消息的消息體 (payload) 在消息路由的過程中,消息的標(biāo)簽會(huì)丟棄,存入到隊(duì)列中的消息只有消息體,消費(fèi)者也只會(huì)消費(fèi)到消息體,也就不知道消息的生產(chǎn)者是誰,當(dāng)然消費(fèi)者也不需要知道。
(3)Broker:消息中間件的服務(wù)節(jié)點(diǎn)。對(duì) RabbitMQ 來說, 一個(gè) RabbitMQ Broker 簡(jiǎn)單地看作 RabbitMQ 服務(wù)節(jié)點(diǎn)或者 RabbitMQ 服務(wù)實(shí)例。大多數(shù)情況下也可以將一個(gè) RabbitMQ Broker 看作一臺(tái) RabbitMQ 服務(wù)器。
2.2.3.隊(duì)列 (Queue)
Queue:隊(duì)列,是 RabbitMQ 的內(nèi)部對(duì)象,用于存儲(chǔ)消息。RabbitMQ 的生產(chǎn)者生產(chǎn)消息井最終技遞到隊(duì)列中,消費(fèi)者可以從隊(duì)列中獲取消息并消費(fèi)。多個(gè)消費(fèi)者可以訂閱同一個(gè)隊(duì)列,這時(shí)隊(duì)列中的消息會(huì)被平均分?jǐn)偅≧ound-Robin ,即輪詢)給多個(gè)消費(fèi)者進(jìn)行處理,而不是每個(gè)消費(fèi)者都收到所有的消息井處理,如下圖所示:
2.2.4.交換器 (Exchange)、路由鍵 (RoutingKey)、綁定 (Binding)
(1)Exchange:交換機(jī),一方面它接收來自生產(chǎn)者的消息,另一方面它將消息推送到隊(duì)列中。交換機(jī)必須確切知道如何處理它接收到的消息,是將這些消息推送到特定隊(duì)列還是推送到多個(gè)隊(duì)列,亦或者是把消息丟棄,這個(gè)由交換機(jī)類型決定,如下圖所示:
(2)RoutingKey:路由鍵。生產(chǎn)者將消息發(fā)給交換器的時(shí)候, 一般會(huì)指定一個(gè) RoutingKey,用來指定這個(gè)消息的路由規(guī)則,而這個(gè) RoutingKey 需要與交換器類型和綁定鍵 (BindingKey) 合使用才能最終生效。在交換器類型和綁定鍵 (BindingKey) 固定的情況下,生產(chǎn)者可以在發(fā)送消息給交換器時(shí),通過指定 RoutingKey 來決定消息流向哪里。
(3)Binding:綁定。RabbitMQ 中通過綁定將交換器與隊(duì)列關(guān)聯(lián)起來,在綁定的時(shí)候一般會(huì)指定一 綁定鍵 BindingKey,這樣RabbitMQ 就知道如何正確地將消息路由到隊(duì)列了,如下圖所示:
生產(chǎn)者將消息發(fā)送給交換器時(shí), 需要一個(gè) RoutingKey,當(dāng) BindingKey 和 RoutingKey 相匹時(shí)(后面將兩者和合稱為路由鍵), 消息會(huì)被路由到對(duì)應(yīng)的隊(duì)列中。在綁定多個(gè)隊(duì)列到同一個(gè)交換器的時(shí)候,這些綁定允許使用相同的 BindingKey。BindingKey 并不是在所有的情況下都生效,它依賴于交換器類型。
通過下面的比喻來理解交換器、路由鍵、綁定這幾個(gè)概念:交換器相當(dāng)于投遞包裹的郵箱,RoutingKey 相當(dāng)于填寫在包裹上的地址, BindingKey 相當(dāng)于包裹目的地,當(dāng)填寫在包裹上的地址和實(shí)際想要投遞的地址相匹配時(shí),那么這個(gè)包裹就會(huì)被正確投遞到目的地,最后這個(gè)目的地的"主人"一一隊(duì)列可以保留這個(gè)裹。如果填寫目的地址出錯(cuò),郵遞員不能正確投遞到目的地,包裹可能會(huì)回退給寄件人,也有可能被丟棄。
2.3.?交換機(jī) (Exchange) 有哪些類型?不同類型的交換機(jī)什么應(yīng)用場(chǎng)景?
(1)交換機(jī) (Exchange) 是一種用于將消息路由到一個(gè)或多個(gè)隊(duì)列的組件。生產(chǎn)者發(fā)送消息到交換機(jī),交換機(jī)根據(jù)特定的路由規(guī)則將消息路由到一個(gè)或多個(gè)隊(duì)列,然后消費(fèi)者從隊(duì)列中獲取消息進(jìn)行處理。
(2)交換機(jī)必須確切知道如何處理收到的消息,是應(yīng)該把這些消息放到特定隊(duì)列,還是說把他們到許多隊(duì)列中,還是說應(yīng)該丟棄它們。這就的由交換機(jī)的類型來決定。RabbitMQ 支持以下四種類型的交換機(jī):
Fanout Exchange:把所有發(fā)送到該交換器的消息路由到所有與該交換器綁定的隊(duì)列中。Direct Exchange:把消息路由到那些 BindingKey 和 RoutingKey 完全匹配的隊(duì)列中。Topic Exchange:在匹配規(guī)則上進(jìn)行了擴(kuò)展,它與 direct 類型的交換器相似,也是將消息路由到 RoutingKey 和 BindingKey 相匹配的隊(duì)列中,但這里的匹配規(guī)則有些不同,它約定:根據(jù)消息的 Routing Key 和通配符將消息路由到一個(gè)或多個(gè)隊(duì)列。
RoutingKey 為一個(gè)點(diǎn)號(hào) "." 分隔的字符串(被點(diǎn)號(hào) "." 分隔開的每一段獨(dú)立的字符串稱為一個(gè)單詞),例如 “com.rabbitmq.client”、“java.util.concurrent”、“com.hidden.client”;BindingKey 和 RoutingKey 一樣也是點(diǎn)號(hào) "." 分隔的字符串;BindingKey 中可以存在兩種特殊字符串 “*” 和 “#”,用于做模糊匹配,其中 "*"用于匹配一個(gè)單詞,"#" 用于匹配多規(guī)格單詞(可以是零個(gè)); Headers Exchange:該類型的交換器不依賴于路由鍵的匹配規(guī)則來路由消息,而是根據(jù)發(fā)送的消息內(nèi)容中 headers 屬性進(jìn)行匹配。在綁定隊(duì)列和交換器時(shí)制定一組鍵值對(duì),當(dāng)發(fā)送消息到交換器時(shí),RabbitMQ 會(huì)獲取到該消息的 headers(也是一個(gè)鍵值對(duì)的形式),對(duì)比其中的鍵值對(duì)是否完全匹配隊(duì)列和交換器綁定時(shí)指定的鍵值對(duì),如果完全匹配則消息會(huì)路由到該隊(duì)列,否則不會(huì)路由到該隊(duì)列。headers 類型的交換器性能會(huì)很差,而且也不實(shí)用,基本上不會(huì)看到它的存在。
(3)不同類型的交換機(jī)適用于不同的應(yīng)用場(chǎng)景。例如:
Fanout Exchange 適用于將消息廣播給所有綁定到該交換機(jī)上的隊(duì)列;Direct Exchange 適用于將消息路由到特定的隊(duì)列;Topic Exchange 適用于模糊匹配 Routing Key;Headers Exchange 適用于根據(jù)消息的 Headers 屬性進(jìn)行路由,但實(shí)際上很少使用;
2.4.?RabbitMQ 的工作原理是什么?
(1)我們將 RabbitMQ 的工作原理分為生成者發(fā)送消息和消費(fèi)者接收消息這兩部分:
在最初狀態(tài)下,生產(chǎn)者發(fā)送消息的時(shí)候:
生產(chǎn)者連接到 RabbitMQ Broker,建立一個(gè)連接 (Connection),開啟一個(gè)信道 (Channel);生產(chǎn)者聲明一個(gè)交換器,并設(shè)置相關(guān)屬性,比如交換機(jī)類型、是否持久化等;生產(chǎn)者聲明一個(gè)隊(duì)列,并設(shè)置相關(guān)屬性,比如是否排他、是否持久化、是否自動(dòng)刪除等;生產(chǎn)者通過路由鍵將交換器和隊(duì)列綁定起來;生產(chǎn)者發(fā)送消息至 RabbitMQ Broker,其中包含路由鍵、交換器等信息。相應(yīng)的交換器根據(jù)接收到的路由鍵以及自身類型,來查找相匹配的隊(duì)列:
如果找到 ,則將從生產(chǎn)者發(fā)送過來的消息存入相應(yīng)的隊(duì)列中。如果沒有找到 ,則根據(jù)生產(chǎn)者配置的屬性選擇丟棄還是回退給生產(chǎn)者。 關(guān)閉信道,關(guān)閉連接。 消費(fèi)者接收消息的過程:
消費(fèi)者連接到 RabbitMQ Broker,建立一個(gè)連接 (Connection) ,開啟一個(gè)信道 (Channel);消費(fèi)者向 RabbitMQ Broker 請(qǐng)求消費(fèi)相應(yīng)隊(duì)列中的消息,可能會(huì)設(shè)置相應(yīng)的回調(diào)函數(shù),以及做一些準(zhǔn)備工作;等待 RabbitMQ Broker 回應(yīng)并投遞相應(yīng)隊(duì)列中的消息, 消費(fèi)者接收消息;消費(fèi)者確認(rèn) (ack) 接收到的消息;RabbitMQ 從隊(duì)列中刪除相應(yīng)己經(jīng)被確認(rèn)的消息;關(guān)閉信道,關(guān)閉連接。
(2)下面介紹兩個(gè)新的概念:Connection 和 Channel。我們知道無論是生者還是消費(fèi)者,都需要和 RabbitMQ Broker 建立連接,這個(gè)連接就是一條 TCP 連接,也就是 Connection。一旦 TCP 連接建立起來,客戶端緊接著可以創(chuàng)建一個(gè) AMQP 信道 (Channel) ,每個(gè)信道都會(huì)被指派一個(gè)唯一的 ID。信道是建立在 Connection 之上的虛擬連接, RabbitMQ 處理的每條 AMQP 指令都是通過信道完成的。
(3)我們完全可以直接使用 Connection 就能完成信道的工作,為什么還要引入信道呢?試想這樣一個(gè)場(chǎng)景,一個(gè)應(yīng)用程序中有很多個(gè)線程需要從 RabbitMQ 中消費(fèi)消息,或者生產(chǎn)消息,那么必然需要建立很多個(gè) Connection,也就是許多個(gè) TCP 連接。然而對(duì)于操作系統(tǒng)而言,建立和銷毀 TCP 連接是非常昂貴的開銷,如果遇到使用高峰,性能瓶頸也隨之顯現(xiàn)。 RabbitMQ 采用類似 NIO (Non-blocking I/O) 的做法,選擇 TCP 連接復(fù)用,不僅可以減少性能開銷,同時(shí)也便于管理。
2.5.RabbitMQ 中的消息怎么傳輸?
為了避免 TCP 連接的開銷和并發(fā)數(shù)受限的性能瓶頸,RabbitMQ 采用了信道 (Channel) 的方式來傳輸數(shù)據(jù)。信道是生產(chǎn)者、消費(fèi)者與 RabbitMQ 通信的通道,它是建立在 TCP 連接上的虛擬連接,而且每個(gè) TCP 連接可以擁有多個(gè)信道,沒有數(shù)量限制。因此,RabbitMQ 可以在一條 TCP 連接上建立成百上千個(gè)信道,以實(shí)現(xiàn)多線程處理。這樣,多個(gè)線程可以共享同一條 TCP 連接,而每個(gè)信道在 RabbitMQ 中都有唯一的 ID,從而確保信道的私有性。每個(gè)信道對(duì)應(yīng)一個(gè)線程的使用,這種方式使得 RabbitMQ 能夠高效地利用系統(tǒng)資源。
3.RabbitMQ 進(jìn)階
3.1.當(dāng)消息的生產(chǎn)者將消息發(fā)送出去之后,如何確定消息到底有沒有正確地到達(dá)服務(wù)器?
默認(rèn)情況下發(fā)送消息的操作是不會(huì)返回任何信息給生產(chǎn)者的,也就是默認(rèn)情況下生產(chǎn)者是不知道消息有沒有正確地到達(dá)服務(wù)器。如果在消息到達(dá)服務(wù)器之前已經(jīng)丟失,持久化操作也解決不了這個(gè)問題,因?yàn)橄⒏緵]有到達(dá) RabbitMQ 針對(duì)這個(gè)問題,提供了兩種解決方式:
通過事務(wù)機(jī)制實(shí)現(xiàn);通過發(fā)布確認(rèn)機(jī)制實(shí)現(xiàn)。
3.2.請(qǐng)介紹一下 RabbitMQ 中的事務(wù)機(jī)制。
(1)RabbitMQ 支持事務(wù)機(jī)制,通過事務(wù)機(jī)制可以保證消息的原子性、一致性和持久性。在 RabbitMQ 中,事務(wù)機(jī)制通過 AMQP (Advanced Message Queuing Protocol) 事務(wù)模型來實(shí)現(xiàn)。RabbitMQ 客戶端中與事務(wù)機(jī)制相關(guān)的方法有以下三個(gè):
channel.txSelect:用于將當(dāng)前的信道設(shè)置成事務(wù)模式;channel.txCommit:用于提交事務(wù);channel.txRollback:用于事務(wù)回滾。
(2)在通過 channel.txSelect 方法開啟事務(wù)之后,我們便可以發(fā)布消息給 RabbitMQ 了:
如果事務(wù)提交成功,則消息一定到達(dá)了 RabbitMQ 中;如果在事務(wù)提交執(zhí)行之前由于 RabbitMQ 異常崩潰或者其他原因拋出異常,這個(gè)時(shí)候我們便可以將其捕獲,進(jìn)而通過執(zhí)行channel.txRollback 方法來實(shí)現(xiàn)事務(wù)回滾。
(3)注意這里的 RabbitMQ 中的事務(wù)機(jī)制與大多數(shù)數(shù)據(jù)庫中的事務(wù)概念并不相同,需要注意區(qū)分。關(guān)鍵示例代碼如下所示:
channel.txSelect();
channel.basicPublish(EXCHANGE_NAME,ROUTING_KEY, MessageProperties.PERSISTENT_TEXT_PLAIN,
"transaction messages".getBytes());
channel.txCommit();
上面代碼對(duì)應(yīng)的 AMQP 協(xié)議流轉(zhuǎn)過程如下圖所示:
3.3.使用 RabbitMQ 事務(wù)機(jī)制可能會(huì)引發(fā)什么問題?如何解決?
(1)事務(wù)確實(shí)能夠解決消息發(fā)送方和 RabbitMQ 之間消息確認(rèn)的問題,只有消息成功被 RabbitMQ 接收,事務(wù)才能提交成功,否則便可在捕獲異常之后進(jìn)行事務(wù)回滾,與此同時(shí)可以進(jìn)行消息重發(fā)。
(2)但是使用事務(wù)機(jī)制會(huì)“吸干” RabbitMQ 的性能,因?yàn)槭聞?wù)機(jī)制在一條消息發(fā)送之后會(huì)使發(fā)送端阻塞,以等待 RabbitMQ 的回應(yīng),之后才能繼續(xù)發(fā)送下一條消息。那么有沒有更好的方法既能保證消息發(fā)送方確認(rèn)消息已經(jīng)正確送達(dá),又能基本上不帶來性能上的損失呢?從 AMQP 協(xié)議層面來看并沒有更好的辦法,但是 RabbitMQ 提供了一個(gè)改進(jìn)方案,即發(fā)送方確認(rèn)機(jī)制。
3.4.RabbitMQ 中的發(fā)布確認(rèn)機(jī)制是指什么?
(1)在 RabbitMQ 中,發(fā)布確認(rèn) (Publish/Confirm) 指的是生產(chǎn)者發(fā)送消息到 RabbitMQ 服務(wù)器后,等待服務(wù)器確認(rèn)已經(jīng)接收到消息的過程。具體來說,當(dāng)生產(chǎn)者通過 RabbitMQ 的 AMQP 協(xié)議發(fā)送消息到服務(wù)器時(shí),可以指定一個(gè)回調(diào)函數(shù),用于在服務(wù)器確認(rèn)接收到消息時(shí)進(jìn)行處理。如果服務(wù)器成功接收到消息,就會(huì)調(diào)用該回調(diào)函數(shù)進(jìn)行通知。如果服務(wù)器無法接收到消息,則會(huì)觸發(fā)異常處理。
(2)生產(chǎn)者將信道設(shè)置成 confirm(確認(rèn))模式,一旦信道進(jìn)入 confirm 模式,所有在該信道上面發(fā)布的消息都會(huì)被指派一個(gè)唯一的 ID(從 1 開始),一旦消息被投遞到所有匹配的隊(duì)列之后,RabbitMQ 就會(huì)發(fā)送一個(gè)確認(rèn) (Basic.Ack) 給生產(chǎn)者(包含消息的唯一 ID),這就使得生產(chǎn)者知曉消息已經(jīng)正確到達(dá)了目的地了。如果消息和隊(duì)列是可持久化的,那么確認(rèn)消息會(huì)在消息寫入磁盤之后發(fā)出。RabbitMQ 回傳給生產(chǎn)者的確認(rèn)消息中的 deliveryTag 包含了確認(rèn)消息的序號(hào),此外 RabbitMQ 也可以設(shè)置 channel.basicAck 方法中的 multiple 參數(shù),表示到這個(gè)序號(hào)之前的所有消息都已經(jīng)得到了處理,可以參考下圖。
如果 RabbitMQ 因?yàn)樽陨韮?nèi)部錯(cuò)誤導(dǎo)致消息丟失,就會(huì)發(fā)送一條 nack (Basic.Nack)命令,生產(chǎn)者應(yīng)用程序同樣可以在回調(diào)方法中處理該 nack 命令。生產(chǎn)者通過調(diào)用 channel.confirmSelect 方法(即 Confirm.Select 命令)將信道設(shè)置為 confirm 模式,之后 RabbitMQ 會(huì)返回 Confirm.Select-Ok 命令表示同意生產(chǎn)者將當(dāng)前信道設(shè)置為 confirm 模式。所有被發(fā)送的后續(xù)消息都被 ack 或者 nack 一次,不會(huì)出現(xiàn)一條消 息既被 ack 又被 nack 的情況,并且 RabbitMQ 也并沒有對(duì)消息被 confirm 的快慢做任何保證。
3.5.?發(fā)布確認(rèn)機(jī)制有哪些實(shí)現(xiàn)方式?
(1)在 RabbitMQ 中,單獨(dú)確認(rèn)、批量確認(rèn)和異步確認(rèn)模式是發(fā)布確認(rèn)機(jī)制的不同實(shí)現(xiàn)方式。
單獨(dú)確認(rèn)模式:指每次發(fā)送一條消息后,即等待 RabbitMQ 服務(wù)器返回該消息的確認(rèn)信息。單獨(dú)確認(rèn)模式適用于需要及時(shí)獲取每條消息的確認(rèn)結(jié)果并進(jìn)行相應(yīng)處理的場(chǎng)景。但由于每次發(fā)送消息都要等待確認(rèn),可能會(huì)對(duì)性能有所影響。具體步驟如下:
將 Channel 設(shè)置為確認(rèn)模式 (confirm mode),即通過調(diào)用 channel.confirmSelect() 方法啟用確認(rèn)模式。發(fā)布消息到 RabbitMQ 服務(wù)器。等待 RabbitMQ 服務(wù)器返回該消息的確認(rèn)信息,可以通過調(diào)用 channel.waitForConfirms() 方法來等待確認(rèn)。該方法會(huì)阻塞當(dāng)前線程,直到收到對(duì)應(yīng)消息的確認(rèn)信息或超時(shí)。 批量確認(rèn)模式:指在發(fā)送多個(gè)消息后,等待 RabbitMQ 服務(wù)器一次性返回多條確認(rèn)信息。批量確認(rèn)模式通過一次性返回多條確認(rèn)信息,提高了確認(rèn)的效率。適用于需要批量發(fā)送消息并等待確認(rèn)的場(chǎng)景。具體步驟如下:
將 Channel 設(shè)置為確認(rèn)模式。發(fā)布多條消息到 RabbitMQ 服務(wù)器。使用channel.waitForConfirms() 方法等待 RabbitMQ 服務(wù)器返回全部消息的確認(rèn)信息。 異步確認(rèn)模式:指通過添加一個(gè)確認(rèn)監(jiān)聽器 (confirm listener) 來異步處理確認(rèn)結(jié)果。使用異步確認(rèn)模式,可以在消息發(fā)送的過程中繼續(xù)進(jìn)行其他操作,不需要阻塞等待確認(rèn)的結(jié)果。一旦收到確認(rèn)信息,即可在監(jiān)聽器中進(jìn)行相應(yīng)的處理。具體步驟如下:
將 Channel 設(shè)置為確認(rèn)模式;發(fā)布消息到 RabbitMQ 服務(wù)器。注冊(cè)一個(gè)確認(rèn)監(jiān)聽器,在 channel.addConfirmListener() 方法中可以添加 ConfirmListener 這個(gè)回調(diào)接口,這個(gè) ConfirmListener 接口包含兩個(gè)方法:handleAck 和 handleNack,分別用來處理 RabbitMQ 回傳的 Basic.Ack 和 Basic.Nack。在這兩個(gè)方法中都包含有一個(gè)參數(shù)deliveryTag(在發(fā)布確認(rèn)模式下用來標(biāo)記消息的唯一有序序號(hào))。我們需要為每一個(gè)信道維護(hù)一個(gè) “unconfirm” 的消息序號(hào)集合,每發(fā)送一條消息,集合中的元素加 1。每當(dāng)調(diào)用 ConfirmListener 中的 handleAck 方法時(shí),“unconfirm”集合中刪掉相應(yīng)的一條(multiple 設(shè)置為 false)或者多條(multiple 設(shè)置為 true)記錄。從程序運(yùn)行效率上來看,這個(gè) “unconfirm” 集合最好采用有序集合 SortedSet 的存儲(chǔ)結(jié)構(gòu)。
(2)總結(jié):
單獨(dú)確認(rèn)模式適用于需要及時(shí)知道每條消息的確認(rèn)結(jié)果的場(chǎng)景;批量確認(rèn)模式適用于需要批量發(fā)送消息并等待確認(rèn)的場(chǎng)景;異步確認(rèn)模式適用于可以異步處理確認(rèn)結(jié)果的場(chǎng)景,不需要阻塞等待確認(rèn)。
注意:事務(wù)機(jī)制和發(fā)布確認(rèn)機(jī)制兩者是互斥的,不能共存!
3.6.什么是死信交換機(jī)和死信隊(duì)列?死信隊(duì)列有哪些應(yīng)用場(chǎng)景?
(1)當(dāng)消息在一個(gè)隊(duì)列中變成死信 (dead message) 之后,它能被重新被發(fā)送到另一個(gè)交換器中,這個(gè)交換器就是死信交換機(jī) (Dead-Letter-Exchange, DLX),綁定 DLX 的隊(duì)列就稱之為死信隊(duì)列 (Dead-Letter Queue, DLQ) 。死信隊(duì)列是指用于處理無法被消費(fèi)者正常處理的消息的特殊隊(duì)列。消息變成死信一般是由于以下幾種情況:
消息被拒絕 (Basic.Reject/Basic.Nack),井且設(shè)置 requeue 參數(shù)為 false;消息過期,即消息在隊(duì)列中的生存時(shí)間超過設(shè)置 TTL(Time-To-Live,存活時(shí)間)值;隊(duì)列達(dá)到最大長(zhǎng)度;
(2)DLX 是一個(gè)正常的交換器,和一般的交換器沒有區(qū)別,它能在任何的隊(duì)列上被指定,實(shí)際上就是設(shè)置某個(gè)隊(duì)列的屬性。當(dāng)這個(gè)隊(duì)列中存在死信時(shí),RabbitMQ 就會(huì)自動(dòng)地將這個(gè)消息新發(fā)布到設(shè)置的 DLX ,進(jìn)而被路由到另一個(gè)隊(duì)列,即死信隊(duì)列。通過在 channel.queueDeclare 方法中設(shè)置 x-dead-letter-exchange 參數(shù)來為這個(gè)隊(duì)列添加 DLX。
(3)通過使用死信隊(duì)列,可以使得無法被正常處理的消息得到有效的處理,避免了因?yàn)橄⒈弧斑z棄”而造成的資源浪費(fèi),具體應(yīng)用場(chǎng)景如下:
消息重試:如果一個(gè)消息在隊(duì)列中被消費(fèi)者拒絕或者超時(shí),可以將該消息發(fā)送到死信隊(duì)列中,以便重新處理該消息。通過設(shè)置消息的 TTL(Time-To-Live,存活時(shí)間)和重試次數(shù)等參數(shù),可以實(shí)現(xiàn)自動(dòng)重試機(jī)制。延遲消息:通過將消息發(fā)送到帶有 TTL 參數(shù)的隊(duì)列中,可以實(shí)現(xiàn)延遲消息的功能。當(dāng)消息的 TTL時(shí)間到達(dá)后,消息會(huì)被自動(dòng)發(fā)送到死信隊(duì)列中,從而實(shí)現(xiàn)延遲消息的處理。分流:通過將不同的消息路由到不同的隊(duì)列中,并且在隊(duì)列中設(shè)置不同的 TTL 時(shí)間,可以實(shí)現(xiàn)消息的分流功能。當(dāng)消息的 TTL 時(shí)間到達(dá)后,可以根據(jù)不同的死信路由鍵將消息發(fā)送到不同的死信隊(duì)列中,從而實(shí)現(xiàn)消息的分流處理。日志收集:通過將不同的日志級(jí)別路由到不同的隊(duì)列中,并且在隊(duì)列中設(shè)置不同的 TTL 時(shí)間,可以實(shí)現(xiàn)日志的收集功能。當(dāng)日志的 TTL 時(shí)間到達(dá)后,可以將日志發(fā)送到死信隊(duì)列中,并且通過指定的死信路由鍵將日志路由到指定的日志處理系統(tǒng)中。隊(duì)列監(jiān)控:通過設(shè)置死信交換機(jī)和死信隊(duì)列來監(jiān)控隊(duì)列中的異常消息,并且通過將異常消息發(fā)送到死信隊(duì)列中來進(jìn)行異常處理。通過監(jiān)控死信隊(duì)列中的消息,可以及時(shí)發(fā)現(xiàn)隊(duì)列中的問題,并且進(jìn)行處理。
3.7.什么是延遲隊(duì)列?它有哪些應(yīng)用場(chǎng)景?
(1)延遲隊(duì)列指用來存放需要在指定時(shí)間被處理的消息的隊(duì)列。在 AMQP 協(xié)議中,或者 RabbitMQ 本身沒有直接支持延遲隊(duì)列的功能,但是可以通過 DLX 和 TTL 模擬出延遲隊(duì)列的功能。具體來說,當(dāng)一個(gè)消息被發(fā)送到延遲隊(duì)列時(shí),可以設(shè)置消息的 TTL 參數(shù),指定該消息需要被延遲的時(shí)間。RabbitMQ 會(huì)在消息的 TTL 時(shí)間到達(dá)后,將該消息發(fā)送到一個(gè)特定的交換機(jī),然后根據(jù)指定的路由鍵將該消息路由到對(duì)應(yīng)的隊(duì)列中,從而實(shí)現(xiàn)消息的延遲投遞。
(2)延遲隊(duì)列在實(shí)際應(yīng)用中有很多的應(yīng)用場(chǎng)景,例如:
訂單超時(shí)處理:可以將訂單過期時(shí)間作為消息的 TTL 參數(shù),當(dāng)訂單過期時(shí),該訂單會(huì)被自動(dòng)發(fā)送到死信隊(duì)列中進(jìn)行處理。短信驗(yàn)證碼發(fā)送:可以將短信驗(yàn)證碼的過期時(shí)間作為消息的 TTL 參數(shù),當(dāng)驗(yàn)證碼過期時(shí),該消息會(huì)被自動(dòng)發(fā)送到死信隊(duì)列中進(jìn)行處理。消息重試:可以將消息發(fā)送到延遲隊(duì)列中,設(shè)置合適的 TTL 時(shí)間和重試次數(shù),實(shí)現(xiàn)消息重試的功能。購物車過期處理:可以將購物車的過期時(shí)間作為消息的 TTL 參數(shù),當(dāng)購物車過期時(shí),該消息會(huì)被自動(dòng)發(fā)送到死信隊(duì)列中進(jìn)行處理。定時(shí)任務(wù)調(diào)度:可以使用延遲隊(duì)列來實(shí)現(xiàn)定時(shí)任務(wù)調(diào)度功能,將任務(wù)的執(zhí)行時(shí)間作為消息的 TTL 參數(shù),當(dāng)任務(wù)到達(dá)執(zhí)行時(shí)間時(shí),該消息會(huì)被自動(dòng)發(fā)送到死信隊(duì)列中進(jìn)行處理。
3.8.如何設(shè)置死信隊(duì)列?
(1)在 RabbitMQ 中設(shè)置死信隊(duì)列需要以下幾個(gè)步驟:
創(chuàng)建死信隊(duì)列及其綁定:首先,需要?jiǎng)?chuàng)建一個(gè)用于存儲(chǔ)死信消息的隊(duì)列,并將其綁定到一個(gè)交換機(jī)上。可以使用 queue.declare 和 exchange.declare 方法進(jìn)行創(chuàng)建和聲明。設(shè)置原始隊(duì)列的參數(shù):接下來,需要設(shè)置原始隊(duì)列(即希望成為死信的隊(duì)列)的參數(shù),使其成為一個(gè)具有死信功能的隊(duì)列。可以通過調(diào)用 queue.declare 方法,并設(shè)置 x-dead-letter-exchange 和 x-dead-letter-routing-key 參數(shù)來指定死信隊(duì)列的交換機(jī)和路由鍵。
x-dead-letter-exchange:指定死信消息發(fā)送到的交換機(jī)。x-dead-letter-routing-key:指定死信消息的路由鍵。 將消息發(fā)送到原始隊(duì)列:現(xiàn)在,當(dāng)消息被發(fā)送到原始隊(duì)列時(shí),如果滿足一定的條件,例如消息過期、消息被拒絕等,該消息將被標(biāo)記為死信,并被發(fā)送到死信隊(duì)列。處理死信消息:最后,應(yīng)用程序可以從死信隊(duì)列中接收和處理死信消息。通過訂閱死信隊(duì)列,可以消費(fèi)這些死信消息,并根據(jù)實(shí)際需求做出相應(yīng)的處理,例如記錄日志、重新投遞等。
(2)需要注意的是,在設(shè)置死信隊(duì)列時(shí)還可以配置其他參數(shù),如消息過期時(shí)間、最大重試次數(shù)等,以滿足具體的業(yè)務(wù)需求。
3.9.什么是優(yōu)先級(jí)隊(duì)列?它有哪些應(yīng)用場(chǎng)景?
(1)優(yōu)先級(jí)隊(duì)列是指一種可以根據(jù)消息的優(yōu)先級(jí)進(jìn)行排序和處理的隊(duì)列,可以通過設(shè)置隊(duì)列的 x-max-priority 參數(shù)來實(shí)現(xiàn)。具體來說,當(dāng)一個(gè)消息被發(fā)送到優(yōu)先級(jí)隊(duì)列時(shí),可以為該消息設(shè)置一個(gè)優(yōu)先級(jí)參數(shù)。RabbitMQ 會(huì)根據(jù)消息的優(yōu)先級(jí)對(duì)消息進(jìn)行排序,并優(yōu)先處理優(yōu)先級(jí)高的消息。如果兩個(gè)消息的優(yōu)先級(jí)相同,則按照先進(jìn)先出的原則進(jìn)行處理。
(2)優(yōu)先級(jí)隊(duì)列在實(shí)際應(yīng)用中有很多的應(yīng)用場(chǎng)景,例如:
任務(wù)調(diào)度:可以使用優(yōu)先級(jí)隊(duì)列來實(shí)現(xiàn)任務(wù)調(diào)度功能,將不同優(yōu)先級(jí)的任務(wù)放入不同的隊(duì)列中,并按照優(yōu)先級(jí)順序處理任務(wù)。日志處理:可以使用優(yōu)先級(jí)隊(duì)列來處理日志信息,將重要的日志信息放入優(yōu)先級(jí)高的隊(duì)列中,以保證這些信息能夠及時(shí)被處理。數(shù)據(jù)庫更新:可以使用優(yōu)先級(jí)隊(duì)列來處理數(shù)據(jù)庫更新操作,將高優(yōu)先級(jí)的更新操作放在隊(duì)列的前面,以保證這些操作能夠優(yōu)先被處理。
(3)總之,優(yōu)先級(jí)隊(duì)列是 RabbitMQ 中非常有用的一種特性,可以幫助我們優(yōu)化消息的處理順序,提高消息的實(shí)時(shí)性和重要性,適用于很多不同的業(yè)務(wù)場(chǎng)景。
3.10.如何設(shè)置優(yōu)先級(jí)隊(duì)列?
(1)核心步驟如下:
創(chuàng)建隊(duì)列時(shí)設(shè)置優(yōu)先級(jí)參數(shù):在創(chuàng)建隊(duì)列時(shí),可以通過設(shè)置 x-max-priority 參數(shù)來指定隊(duì)列的最大優(yōu)先級(jí)。發(fā)布消息時(shí)設(shè)置優(yōu)先級(jí)屬性:在消息的屬性中,設(shè)置 priority 屬性來指定消息的優(yōu)先級(jí)。可以使用 basic.publish 方法,并將 priority 設(shè)置為所需的優(yōu)先級(jí)數(shù)值,范圍從 0(最低優(yōu)先級(jí))到隊(duì)列最大優(yōu)先級(jí)數(shù)值(最高優(yōu)先級(jí))。
(2)示例代碼如下:
Map
params.put("x-max-priority", 10);
channel.queueDeclare("hello", true, false, false, params);
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().priority(5).build();
注意:消費(fèi)者需要等待消息已經(jīng)發(fā)送到隊(duì)列中才去消費(fèi)(即不能消費(fèi)地太快,否則優(yōu)先級(jí)隊(duì)列來不及對(duì)消息進(jìn)行排序),這樣才有機(jī)會(huì)對(duì)消息進(jìn)行排序。
3.11.RabbitMQ 中的冪等性是指什么?如何保證?
(1)在消息隊(duì)列系統(tǒng)中,冪等性是指同一條消息被處理多次時(shí),處理的結(jié)果保持一致。
舉個(gè)最簡(jiǎn)單的例子,那就是支付,用戶購買商品后支付,支付扣款成功,但是返回結(jié)果的時(shí)候網(wǎng)絡(luò)異常,此時(shí)錢已經(jīng)扣了,用戶再次點(diǎn)擊按鈕,此時(shí)會(huì)進(jìn)行第二次扣款,返回結(jié)果成功,用戶查詢余額發(fā)現(xiàn)多扣錢了,流水記錄也變成了兩條。在以前的單應(yīng)用系統(tǒng)中,我們只需要把數(shù)據(jù)操作放入事務(wù)中即可,發(fā)生錯(cuò)誤立即回滾,但是再響應(yīng)客戶端的時(shí)候也有可能出現(xiàn)網(wǎng)絡(luò)中斷或者異常等等。
(2)在 RabbitMQ 中,消息的冪等性可以通過以下方式來保證:
消息 ID:可以在發(fā)送消息時(shí)為每條消息生成一個(gè)唯一的 ID,然后在消息被消費(fèi)時(shí),通過比較消息 ID 和已處理消息的 ID 來判斷消息是否已經(jīng)被處理過。業(yè)務(wù) ID:可以使用消息中的業(yè)務(wù) ID 來判斷消息是否已經(jīng)被處理過。在處理消息時(shí),可以將消息的業(yè)務(wù) ID 和處理結(jié)果存儲(chǔ)到數(shù)據(jù)庫或緩存中,如果下次收到相同的業(yè)務(wù) ID 的消息,則可以從數(shù)據(jù)庫或緩存中獲取之前的處理結(jié)果,避免重復(fù)處理。版本號(hào):可以在消息中加入版本號(hào),當(dāng)消息被消費(fèi)時(shí),可以比較消息的版本號(hào)和已處理消息的版本號(hào)來判斷消息是否已經(jīng)被處理過。如果版本號(hào)相同,則表示消息已經(jīng)被處理過,可以直接忽略。
(3)在 RabbitMQ 中保證消息的冪等性非常重要,因?yàn)樵谙㈥?duì)列中,同一條消息可能會(huì)被多次消費(fèi)。如果消息的處理結(jié)果不保持一致,可能會(huì)導(dǎo)致數(shù)據(jù)不一致或業(yè)務(wù)錯(cuò)誤。因此,需要在實(shí)現(xiàn)消費(fèi)者時(shí)考慮消息的冪等性,保證消息的處理結(jié)果是可預(yù)期的。
3.12.RabbitMQ 中的消息持久化是指什么?
(1)在 RabbitMQ 中,消息持久化是指將消息存儲(chǔ)到磁盤上,以確保即使在 RabbitMQ 服務(wù)器意外崩潰或重啟時(shí),也能保證消息的安全性和可靠性。與之相對(duì)的是非持久化的消息,這種消息只會(huì)被存儲(chǔ)在內(nèi)存中,如果 RabbitMQ 服務(wù)器意外崩潰或重啟,這些消息將會(huì)丟失。
(2)要將消息持久化,需要同時(shí)對(duì)隊(duì)列和消息進(jìn)行持久化設(shè)置。在創(chuàng)建隊(duì)列時(shí),可以設(shè)置 durable 參數(shù)為 true,表示該隊(duì)列是持久化的。例如,以下代碼創(chuàng)建了一個(gè)名為 test_queue 的持久化隊(duì)列:
channel.queueDeclare('test_queue', durable=True)
對(duì)于要發(fā)送的消息,也需要設(shè)置持久化屬性。例如,以下代碼創(chuàng)建了一個(gè)名為 message 的持久化消息:
channel.basicPublish("exchange", 'test_queue', "Hello World!", MessageProperties.PERSISTENT_TEXT_PLAIN)
需要注意的是,將消息設(shè)置為持久化會(huì)對(duì)性能產(chǎn)生一定的影響,因?yàn)樾枰獙⑾懭氪疟P。在生產(chǎn)環(huán)境中,需要權(quán)衡可靠性和性能,選擇合適的持久化策略。
3.13.如何解決消息隊(duì)列的延時(shí)以及過期失效問題?
要解決消息隊(duì)列的延時(shí)和過期失效問題,可以采取以下方法:
延時(shí)隊(duì)列 (Delayed Queue):一種常見的方法是使用延時(shí)隊(duì)列來處理延時(shí)消息。將要延時(shí)處理的消息發(fā)送到延時(shí)隊(duì)列中,并設(shè)置消息的延時(shí)時(shí)間。在延時(shí)隊(duì)列中,可以使用定時(shí)任務(wù)或者定時(shí)器來檢查是否有過期的消息需要被處理,一旦到達(dá)延時(shí)時(shí)間,將消息轉(zhuǎn)移至正常處理隊(duì)列。TTL (Time To Live) 設(shè)置:在發(fā)送消息時(shí),可以設(shè)置消息的過期時(shí)間。消息在隊(duì)列中等待時(shí),會(huì)根據(jù)設(shè)定的過期時(shí)間進(jìn)行判斷,一旦超過過期時(shí)間還未被消費(fèi)者處理,消息會(huì)被標(biāo)記為過期失效并被丟棄。RabbitMQ 支持對(duì)整個(gè)隊(duì)列設(shè)置 TTL,也可以對(duì)每條消息單獨(dú)設(shè)置 TTL。定時(shí)任務(wù):在消費(fèi)者端使用定時(shí)任務(wù)來處理延時(shí)和過期消息。消費(fèi)者可以定期檢查隊(duì)列中是否有到達(dá)處理時(shí)間的消息,一旦檢測(cè)到延時(shí)或過期消息,即進(jìn)行相應(yīng)的處理。死信隊(duì)列 (Dead Letter Queue):可以為每個(gè)隊(duì)列設(shè)置一個(gè)死信隊(duì)列,用于接收處理失敗或過期的消息。當(dāng)消息無法被正常消費(fèi)或過期失效時(shí),將消息發(fā)送到死信隊(duì)列中,進(jìn)行后續(xù)的處理和記錄。定期清理:定期清理隊(duì)列中的過期消息??梢允褂枚〞r(shí)任務(wù)或者定時(shí)腳本來清理過期的消息,避免堆積過多的無效消息。
3.14.?如何保證 RabbitMQ 中消息的順序性和可靠性?
(1)RabbitMQ 本身并不直接支持嚴(yán)格的消息順序性,因?yàn)樗且粋€(gè)并發(fā)處理消息的分布式系統(tǒng),多個(gè)消費(fèi)者可以同時(shí)處理不同的消息。但是可以通過以下方法幫助實(shí)現(xiàn)近似的消息順序性:
單一消費(fèi)者:將消息隊(duì)列配置為只有一個(gè)消費(fèi)者(單消費(fèi)者模式),這樣消息將按照發(fā)送的順序依次被消費(fèi)。但這個(gè)方法會(huì)降低系統(tǒng)的吞吐量和并發(fā)性能。多個(gè)隊(duì)列:可以將消息按照順序分發(fā)到不同的隊(duì)列中,每個(gè)隊(duì)列對(duì)應(yīng)一個(gè)獨(dú)立的消費(fèi)者。消費(fèi)者按照隊(duì)列的順序消費(fèi)消息,從而實(shí)現(xiàn)消息的近似順序性。消息分組:可以為消息設(shè)置一個(gè)可排序的標(biāo)識(shí)(例如消息 ID 或時(shí)間戳),然后在消費(fèi)者端根據(jù)消息的標(biāo)識(shí)將消息進(jìn)行排序和處理。這需要在應(yīng)用層面進(jìn)行一些額外的處理,以確保消息按照指定的順序進(jìn)行消費(fèi)。手動(dòng)確認(rèn):使用消息的手動(dòng)確認(rèn)模式 (manual acknowledge),消費(fèi)者在處理完一條消息后手動(dòng)發(fā)送確認(rèn)操作給 RabbitMQ。這樣可以確保消費(fèi)者順序地處理消息,并按照處理完成的順序進(jìn)行確認(rèn)。需要注意的是,這種方式需要消費(fèi)者處理完一條消息后再處理下一條消息,會(huì)降低處理速度。使用有序隊(duì)列插件:RabbitMQ 社區(qū)提供了一個(gè)有序隊(duì)列插件 (rabbitmq-queue-master),該插件可以確保分布式環(huán)境中的消息順序性。該插件會(huì)將隊(duì)列中的消息路由到單獨(dú)的節(jié)點(diǎn)進(jìn)行處理,保證了消息的順序性,但也會(huì)引入額外的復(fù)雜性和效率損耗。
(2)為了保證 RabbitMQ 中消息的可靠性,可以采取以下措施:
持久化:通過將消息和隊(duì)列都設(shè)置為持久化,可以確保在 RabbitMQ 重啟或崩潰后,消息仍然可靠地保存在磁盤上。在生產(chǎn)者發(fā)送消息時(shí),將消息的投遞模式設(shè)置為 persistent,同時(shí)在隊(duì)列聲明時(shí)將 durable 參數(shù)設(shè)置為 true。手動(dòng)確認(rèn):消費(fèi)者可以使用手動(dòng)確認(rèn)模式 (manual ack) 來確認(rèn)消息的處理狀態(tài)。當(dāng)消費(fèi)者成功處理一條消息后,在代碼中發(fā)送確認(rèn)消息給RabbitMQ,表示消息已經(jīng)被處理。只有在確認(rèn)之后,RabbitMQ 才會(huì)將消息從隊(duì)列中刪除。這樣可以保證消息被成功處理后才被刪除,避免消息丟失。限制消息重試次數(shù):在消費(fèi)者處理消息發(fā)生錯(cuò)誤或異常時(shí),可以設(shè)置最大重試次數(shù)。當(dāng)消息達(dá)到最大重試次數(shù)后,可以進(jìn)行特殊處理,如記錄日志、發(fā)送警報(bào)或?qū)⑾G棄等。備份與復(fù)制:RabbitMQ 支持鏡像隊(duì)列 (mirrored queues) 的功能,通過將隊(duì)列在多個(gè)節(jié)點(diǎn)之間進(jìn)行備份或復(fù)制,可以提高消息的可靠性和容錯(cuò)性。如果某個(gè)節(jié)點(diǎn)發(fā)生故障,備份節(jié)點(diǎn)可以接管消息的處理。事務(wù)機(jī)制:RabbitMQ 支持事務(wù),可以將一組操作包裝在事務(wù)中,要么全部成功執(zhí)行,要么全部回滾。但是,事務(wù)會(huì)帶來一定的開銷,影響性能,因此在需要保證遞交的可靠性時(shí)才使用事務(wù)。心跳機(jī)制:通過配置心跳間隔,生產(chǎn)者和消費(fèi)者可以定期向 RabbitMQ 服務(wù)器發(fā)送心跳,以確保連接的活性。如果長(zhǎng)時(shí)間沒有收到心跳,可能是網(wǎng)絡(luò)故障或服務(wù)器宕機(jī),此時(shí)可嘗試重新連接或進(jìn)行相應(yīng)的處理。
3.15.RabbitMQ 有哪些工作模式?
(1)RabbitMQ 支持多種工作模式,包括以下幾種常用的模式:
簡(jiǎn)單模式 (Simple Mode):也稱為點(diǎn)對(duì)點(diǎn)模式 (Point-to-Point),是最簡(jiǎn)單的模式。一個(gè)生產(chǎn)者發(fā)送消息到一個(gè)隊(duì)列,一個(gè)消費(fèi)者從隊(duì)列中接收并處理消息。消息只會(huì)被一個(gè)消費(fèi)者接收和處理,適用于單個(gè)消費(fèi)者場(chǎng)景。發(fā)布/訂閱模式 (Publish/Subscribe Mode):也稱為廣播模式 (Broadcast),一個(gè)生產(chǎn)者發(fā)送消息到一個(gè)交換機(jī),交換機(jī)將消息廣播給多個(gè)隊(duì)列。每個(gè)隊(duì)列有一個(gè)對(duì)應(yīng)的消費(fèi)者進(jìn)行消息的接收和處理。適用于多個(gè)消費(fèi)者同時(shí)接收同一份消息的場(chǎng)景。工作隊(duì)列模式 (Work Queue Mode):也稱為任務(wù)隊(duì)列模式 (Task Queue),一個(gè)生產(chǎn)者發(fā)送消息到一個(gè)隊(duì)列,多個(gè)消費(fèi)者并行地從隊(duì)列中接收和處理消息。消息會(huì)被競(jìng)爭(zhēng)性地分發(fā)給多個(gè)消費(fèi)者,每個(gè)消息只會(huì)被一個(gè)消費(fèi)者接收和處理。適用于任務(wù)分發(fā)和負(fù)載均衡的場(chǎng)景。主題模式 (Topic Mode):消息通過路由鍵 (Routing Key) 的模式匹配來進(jìn)行訂閱和路由。一個(gè)生產(chǎn)者發(fā)送消息到一個(gè)交換機(jī),交換機(jī)根據(jù)消息的路由鍵將消息路由到相關(guān)的隊(duì)列。消費(fèi)者可以使用通配符匹配路由鍵,選擇性地接收和處理消息。適用于靈活的消息訂閱和過濾的場(chǎng)景。頭部模式 (Header Mode):消息通過消息頭的匹配來進(jìn)行訂閱和路由。生產(chǎn)者發(fā)送帶有特定消息頭的消息到一個(gè)交換機(jī),交換機(jī)根據(jù)消息頭的匹配將消息路由到相關(guān)的隊(duì)列。適用于更復(fù)雜的消息匹配和路由場(chǎng)景。
(2)不同的工作模式適用于不同的業(yè)務(wù)場(chǎng)景和需求,可以根據(jù)實(shí)際情況選擇合適的工作模式來實(shí)現(xiàn)消息的傳輸和處理。
4.RabbitMQ 高階
4.1.RabbitMQ 中的集群是指什么?如何搭建?
(1)在 RabbitMQ 中,集群是指將多個(gè) RabbitMQ 服務(wù)器連接在一起,共同處理消息隊(duì)列。集群可以提高系統(tǒng)的可用性和可擴(kuò)展性,避免單點(diǎn)故障和瓶頸問題,支持水平擴(kuò)展。
(2)要搭建 RabbitMQ 集群,可以按照以下步驟:
在每個(gè)節(jié)點(diǎn)上安裝 RabbitMQ,假設(shè)有 3 三個(gè)節(jié)點(diǎn),即 3 臺(tái)機(jī)器,這里分別將它們命名為 node1、node2、node3,,其中 node1 為主節(jié)點(diǎn),并設(shè)它們的 IP 地址分別 192.168.88.16、192.168.88.17、192.168.88.18;在每個(gè)節(jié)點(diǎn)上配置 Erlang Cookie。Erlang Cookie 是一個(gè)用于節(jié)點(diǎn)間認(rèn)證的字符串,確保只有具有相同 Erlang Cookie 的節(jié)點(diǎn)才能連接在一起??梢酝ㄟ^修改 /var/lib/rabbitmq/.erlang.cookie 文件來設(shè)置 Erlang Cookie,確保各節(jié)點(diǎn)的 Erlang Cookie 相同。啟動(dòng) RabbitMQ 服務(wù),順帶啟動(dòng) Erlang 虛擬機(jī)和 RabbitMQ 應(yīng)用服務(wù)(在三臺(tái)節(jié)點(diǎn)上分別執(zhí)行以下命令)
rabbitmq-server -detached
在 node2 依次執(zhí)行以下命令:
rabbitmqctl stop_app
# rabbitmqctl stop 會(huì)將 Erlang 虛擬機(jī)關(guān)閉,rabbitmqctl stop_app 只關(guān)閉 RabbitMQ 服務(wù)
rabbitmqctl reset
rabbitmqctl join_cluster rabbit@node1
# 只啟動(dòng)應(yīng)用服務(wù)
rabbitmqctl start_app
在 node3 依次執(zhí)行以下命令:
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster rabbit@node2
rabbitmqctl start_app
查看集群狀態(tài)
# 在任意一個(gè)節(jié)點(diǎn)上都可查看
rabbitmqctl cluster_status
重新設(shè)置用戶
# 創(chuàng)建賬號(hào)
rabbitmqctl add_user admin 123
# 設(shè)置用戶角色
rabbitmqctl set_user_tags admin administrator
# 設(shè)置用戶權(quán)限
rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"
登錄 Web 管理頁面便可以查看該集群的相關(guān)信息
解除集群節(jié)點(diǎn)(在 node2 和 node3 上分別執(zhí)行)
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl start_app
rabbitmqctl cluster_status
# node1 上執(zhí)行
rabbitmqctl forget_cluster_node rabbit@node2
4.2.如何保證 RabbitMQ 的高可用性?
(1)要保證 RabbitMQ 的高可用性,可以采取以下措施:
集群配置:將多個(gè) RabbitMQ 節(jié)點(diǎn)組成一個(gè)集群。集群中的節(jié)點(diǎn)可以互相復(fù)制和同步消息,實(shí)現(xiàn)數(shù)據(jù)的冗余存儲(chǔ)和備份。當(dāng)一個(gè)節(jié)點(diǎn)發(fā)生故障時(shí),其他節(jié)點(diǎn)可以接管消息的處理。通過集群配置,可以提高系統(tǒng)的可用性和容錯(cuò)性。鏡像隊(duì)列:使用鏡像隊(duì)列 (Mirrored Queue) 將隊(duì)列數(shù)據(jù)復(fù)制到多個(gè)節(jié)點(diǎn)上,確保消息的可靠性和持久化。當(dāng)主節(jié)點(diǎn)發(fā)生故障時(shí),備份節(jié)點(diǎn)可以接管消息的處理,保證服務(wù)不中斷。鏡像隊(duì)列可用于實(shí)現(xiàn)主從架構(gòu)或者多主之間的數(shù)據(jù)冗余存儲(chǔ)。負(fù)載均衡:在高并發(fā)的情況下,將請(qǐng)求均勻地分發(fā)到不同的 RabbitMQ 節(jié)點(diǎn),避免某個(gè)節(jié)點(diǎn)負(fù)載過重??梢酝ㄟ^負(fù)載均衡器、反向代理或者DNS輪詢等方式實(shí)現(xiàn)負(fù)載均衡。心跳機(jī)制:配置心跳檢測(cè)機(jī)制,及時(shí)監(jiān)測(cè)節(jié)點(diǎn)和連接的活躍狀態(tài)。通過定期發(fā)送心跳信號(hào),能夠檢測(cè)節(jié)點(diǎn)是否正常運(yùn)行。當(dāng)節(jié)點(diǎn)或連接不可用時(shí),可以及時(shí)進(jìn)行故障處理和重連。故障轉(zhuǎn)移:當(dāng)某個(gè)節(jié)點(diǎn)或連接發(fā)生故障時(shí),需要進(jìn)行故障轉(zhuǎn)移處理,快速切換到備份節(jié)點(diǎn)或建立新的連接??梢酝ㄟ^使用自動(dòng)故障轉(zhuǎn)移工具(如 Pacemaker)或編寫自定義的監(jiān)控腳本來實(shí)現(xiàn)。持久化配置:將 RabbitMQ 的配置文件和數(shù)據(jù)進(jìn)行持久化存儲(chǔ),確保在節(jié)點(diǎn)重啟或崩潰后能夠恢復(fù)配置和數(shù)據(jù)??梢詫⑴渲梦募蛿?shù)據(jù)存儲(chǔ)在可靠的存儲(chǔ)介質(zhì)上,如磁盤或網(wǎng)絡(luò)文件系統(tǒng)。監(jiān)控和報(bào)警:設(shè)置監(jiān)控系統(tǒng)來監(jiān)測(cè) RabbitMQ 的各項(xiàng)指標(biāo)和運(yùn)行狀態(tài),如節(jié)點(diǎn)運(yùn)行情況、隊(duì)列長(zhǎng)度、消費(fèi)者數(shù)量等。及時(shí)發(fā)現(xiàn)異常情況并觸發(fā)報(bào)警,以便及時(shí)進(jìn)行故障處理和調(diào)優(yōu)。
(2)綜合應(yīng)用以上措施,能夠提高 RabbitMQ 的高可用性,保證系統(tǒng)在節(jié)點(diǎn)故障或網(wǎng)絡(luò)問題等情況下的穩(wěn)定運(yùn)行。需要根據(jù)實(shí)際情況和需求選擇合適的策略和配置方式。
4.3.什么是惰性隊(duì)列是?它有哪些應(yīng)用場(chǎng)景?
(1)RabbitMQ 中的惰性隊(duì)列 (Lazy Queue) 是指一種可以延遲加載消息的隊(duì)列。與普通的隊(duì)列不同,惰性隊(duì)列的消息在被消費(fèi)之前不會(huì)被完全加載到內(nèi)存中,而是在消費(fèi)者開始消費(fèi)時(shí)才會(huì)被加載。具體來說,當(dāng)一個(gè)消息被發(fā)送到惰性隊(duì)列時(shí),該消息會(huì)被存儲(chǔ)到磁盤中,而不是存儲(chǔ)在內(nèi)存中。當(dāng)消費(fèi)者開始消費(fèi)該隊(duì)列時(shí),RabbitMQ 會(huì)逐個(gè)從磁盤中讀取消息,并將消息加載到內(nèi)存中進(jìn)行處理。這種方式可以避免隊(duì)列過大導(dǎo)致內(nèi)存溢出的問題,同時(shí)也能夠提高隊(duì)列的性能和擴(kuò)展性。
(2)隊(duì)列具備兩種模式:default 和 lazy。默認(rèn)的為 default 模式,在 3.6.0 之前的版本無須做任何變更。lazy 模式即為惰性隊(duì)列的模式,可以通過調(diào)用 channel.queueDeclare 方法的時(shí)候在參數(shù)中設(shè)置,也可以通過 Policy 的方式設(shè)置,如果一個(gè)隊(duì)列同時(shí)使用這兩種方式設(shè)置,那么 Policy 的方式具備更高的優(yōu)先級(jí)。如果要通過聲明的方式改變已有隊(duì)列的模式,那么只能先刪除隊(duì)列,然后再重新聲明一個(gè)新的。在隊(duì)列聲明的時(shí)候可以通過 x-queue-mode 參數(shù)來設(shè)置隊(duì)列的模式,取值為 default 和 lazy。
(3)惰性隊(duì)列在實(shí)際應(yīng)用中有很多的應(yīng)用場(chǎng)景,例如:
高吞吐量:可以使用惰性隊(duì)列來處理高吞吐量的消息,因?yàn)槎栊躁?duì)列可以將消息存儲(chǔ)在磁盤中,不會(huì)對(duì)內(nèi)存造成過大的壓力。消息存儲(chǔ):可以使用惰性隊(duì)列來存儲(chǔ)大量的消息數(shù)據(jù),以避免內(nèi)存溢出的問題。數(shù)據(jù)分析:可以使用惰性隊(duì)列來存儲(chǔ)大量的數(shù)據(jù),例如用戶行為數(shù)據(jù)、網(wǎng)站訪問日志等,以便后續(xù)進(jìn)行數(shù)據(jù)分析和處理。
4.4.RabbitMQ 中的鏡像隊(duì)列是指什么?有哪些應(yīng)用場(chǎng)景?
(1)RabbitMQ 中的鏡像隊(duì)列 (Mirrored Queue) 是一種特殊的隊(duì)列配置,通過將隊(duì)列在多個(gè)節(jié)點(diǎn)之間進(jìn)行備份或復(fù)制,實(shí)現(xiàn)數(shù)據(jù)的冗余存儲(chǔ),提高消息的可靠性和容災(zāi)能力。在鏡像隊(duì)列中,主節(jié)點(diǎn) (Master) 上的消息會(huì)被同步地復(fù)制到一個(gè)或多個(gè)備份節(jié)點(diǎn) (Slave),從而保持備份隊(duì)列與主隊(duì)列的一致性。當(dāng)主節(jié)點(diǎn)發(fā)生故障或不可用時(shí),備份節(jié)點(diǎn)可以接管消息的處理,確保消息的持久化和可靠性。
(2)鏡像隊(duì)列可以用于以下應(yīng)用場(chǎng)景:
提高可用性:通過將隊(duì)列在多個(gè)節(jié)點(diǎn)之間進(jìn)行鏡像備份,當(dāng)某個(gè)節(jié)點(diǎn)發(fā)生故障時(shí),備份節(jié)點(diǎn)可以快速接管消息的處理,保證系統(tǒng)的高可用性。容災(zāi)備份:通過在不同的數(shù)據(jù)中心或服務(wù)器之間進(jìn)行消息的鏡像復(fù)制,保證消息的冗余存儲(chǔ),避免數(shù)據(jù)丟失。在主節(jié)點(diǎn)發(fā)生災(zāi)難性故障時(shí),可以迅速切換到備份節(jié)點(diǎn)上繼續(xù)消息處理。提高讀取吞吐量:通過將鏡像隊(duì)列部署在多個(gè)節(jié)點(diǎn)上,可以實(shí)現(xiàn)消費(fèi)者在多個(gè)節(jié)點(diǎn)上并行地讀取消息,提高整體的消費(fèi)吞吐量。
需要注意的是,使用鏡像隊(duì)列會(huì)增加系統(tǒng)的資源消耗和網(wǎng)絡(luò)傳輸開銷。因此,在配置鏡像隊(duì)列時(shí)需要根據(jù)實(shí)際情況考慮節(jié)點(diǎn)數(shù)量、網(wǎng)絡(luò)帶寬和延遲,以及對(duì)數(shù)據(jù)一致性和可用性的要求,綜合平衡資源和性能。此外,鏡像隊(duì)列需要使用 RabbitMQ 的集群功能進(jìn)行配置和管理。
(3)需要注意的是,鏡像隊(duì)列會(huì)增加系統(tǒng)的負(fù)載,因?yàn)樗鼤?huì)將消息復(fù)制到多個(gè)節(jié)點(diǎn)。因此,在配置鏡像隊(duì)列時(shí)需要考慮系統(tǒng)的性能和可擴(kuò)展性。另外,鏡像隊(duì)列還需要在集群中進(jìn)行配置,以確保消息在多個(gè)節(jié)點(diǎn)之間同步。
柚子快報(bào)邀請(qǐng)碼778899分享:RabbitMQ 常見面試題
文章鏈接
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。
轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。