RabbitMQ 相关概念
RabbitMQ 整體上是一個(gè)生產(chǎn)者與消費(fèi)者模型,主要負(fù)責(zé)接收、存儲(chǔ)和轉(zhuǎn)發(fā)消息。可以把消息傳遞的過(guò)程想象成:當(dāng)你講一個(gè)包裹送到郵局,郵局會(huì)暫存并最終將郵件通過(guò)郵遞員送到收件人的手上,RabbitMQ 就好比由郵局、郵箱和郵遞員組成的一個(gè)系統(tǒng)。從計(jì)算機(jī)術(shù)語(yǔ)層面來(lái)說(shuō),RabbitMQ 模型更像是一種交換機(jī)模型。
?
RabbitMQ 的整體模型架構(gòu)如下圖:
?
生產(chǎn)者和消費(fèi)者
Producer:生產(chǎn)者,就是投遞消息的一方。
生產(chǎn)者創(chuàng)建消息,然后發(fā)不到 RabbitMQ 中。消息一般可以包含 2 個(gè)部分:消息體和標(biāo)簽(Label)。消息體也可以稱之為 payload,在實(shí)際應(yīng)用中,消息體一般是一個(gè)帶有業(yè)務(wù)邏輯結(jié)構(gòu)的數(shù)據(jù),比如一個(gè) JSON 字符串。當(dāng)然可以進(jìn)一步對(duì)這個(gè)消息體進(jìn)行序列化操作。消息的標(biāo)簽用來(lái)表述這條消息,比如一個(gè)交換器的名稱和一個(gè)路由鍵。生產(chǎn)者把消息交由 RabbitMQ,RabbitMQ 之后會(huì)根據(jù)標(biāo)簽把消息發(fā)送給感興趣的消費(fèi)者(Consumer)。
?
Consumer:消費(fèi)者,就是接收消息的一方。
消費(fèi)者連接到 RabbitMQ 服務(wù)器,并訂閱到隊(duì)列上。當(dāng)消費(fèi)者消費(fèi)一條消息時(shí),只是消費(fèi)消息的消息體(payload)。在消息路由的過(guò)程中,消息的標(biāo)簽會(huì)丟棄,存入到隊(duì)列中的消息只有消息體,消費(fèi)者也會(huì)消費(fèi)到消息體,也就不知道消息的生產(chǎn)者是誰(shuí),當(dāng)然消費(fèi)者也不需要知道。
?
Broker:消息中間件的服務(wù)節(jié)點(diǎn)。
對(duì)于 RabbitMQ 來(lái)說(shuō),一個(gè) RabbitMQ Broker 可以簡(jiǎn)單地看作一個(gè) RabbitMQ 服務(wù)節(jié)點(diǎn),或者 RabbitMQ 服務(wù)實(shí)例。大多數(shù)情況下也可以將一個(gè) RabbitMQ Broker 看作一臺(tái) RabbitMQ 服務(wù)器。
?
下圖展示了生產(chǎn)者將消息存入 RabbitMQ Broker,以及消費(fèi)者從 Broker 中消費(fèi)數(shù)據(jù)的整個(gè)流程。
首先生產(chǎn)者將業(yè)務(wù)方數(shù)據(jù)進(jìn)行可能的包裝,之后封裝成消息,發(fā)送(AMQP 協(xié)議里這個(gè)動(dòng)作對(duì)應(yīng)的命令為 Basic.Publish)到 Broker 中。消費(fèi)者訂閱并接收消息(AMQP 協(xié)議里這個(gè)動(dòng)作對(duì)應(yīng)的命令為 Basic.Consume 或者 Basic.Get),經(jīng)過(guò)可能的解包處理得到原始的數(shù)據(jù),之后再進(jìn)行業(yè)務(wù)處理邏輯。這個(gè)業(yè)務(wù)處理邏輯并不一定需要和接收消息的邏輯使用同一個(gè)線程。消費(fèi)者線程可以使用一個(gè)線程去接收消息,存入到內(nèi)存中,比如使用 Java 中的 BlockingQueue。業(yè)務(wù)處理邏輯使用另一個(gè)線程從內(nèi)存中讀取數(shù)據(jù),這樣可以將應(yīng)用進(jìn)一步解耦,提高整個(gè)應(yīng)用的處理效率。
?
?
隊(duì)列
Queue:隊(duì)列,是 RabbitMQ 的內(nèi)部對(duì)象,用于存儲(chǔ)消息。
RabbitMQ 中消息只能存儲(chǔ)在隊(duì)列中,這一點(diǎn)和 Kafka 這種消息隊(duì)列中間件相反。Kafka 將消息存儲(chǔ)在 topic(主題)這個(gè)邏輯層面,而相對(duì)應(yīng)的隊(duì)列處理邏輯只是 topic 實(shí)際存儲(chǔ)文件中的位移標(biāo)識(shí)。RabbitMQ 的生產(chǎn)者生產(chǎn)消息并最終投遞到隊(duì)列中,消費(fèi)者可以從隊(duì)列中獲取消息并消費(fèi)。
多個(gè)消費(fèi)者可以訂閱同一個(gè)隊(duì)列,這時(shí)隊(duì)列中的消息會(huì)被平均分?jǐn)?#xff08;Round-Robin,即輪詢)給多個(gè)消費(fèi)者進(jìn)行處理,而不是每個(gè)消費(fèi)者都接收到所有的消息并處理。
RabbitMQ 不支持隊(duì)列層面的廣播消費(fèi),如果需要廣播消費(fèi),需要在其上進(jìn)行二次開發(fā),處理邏輯會(huì)變得異常復(fù)雜,同時(shí)也不建議這么做。
?
?
交換器、路由鍵、綁定
Exchange:交換器。在圖 2-4 中我們暫時(shí)可以理解成生產(chǎn)者將消息投遞到隊(duì)列中,實(shí)際上這個(gè)在 RabbitMQ 中不會(huì)發(fā)生。真實(shí)情況是,生產(chǎn)者將消息發(fā)送到 Exchange(交換器,通常也可以用大寫的 "X" 來(lái)表示),由交換器將消息路由到一個(gè)或者多個(gè)隊(duì)列中。如果路由不到,或許會(huì)返回給生產(chǎn)者,或許直接丟棄。這里可以將 RabbitMQ 中的交換器看作一個(gè)簡(jiǎn)單的實(shí)體。
RabbitMQ 中的交換器有四種類型,不同的類型有著不同的路由策略。
?
RoutingKey:路由鍵。生產(chǎn)者將消息發(fā)給交換器的時(shí)候,一般會(huì)指定一個(gè) RoutingKey,用來(lái)指定這個(gè)消息的路由規(guī)則,而這個(gè) RoutingKey 需要與交換器類型和 綁定鍵(BindingKey)聯(lián)合起來(lái)才能最終生效。
在交換器類型和綁定鍵(BindingKey)固定的情況下,生產(chǎn)者可以在發(fā)送消息給交換器時(shí),通過(guò)指定 RoutingKey 來(lái)決定消息流向哪里。
Binding:綁定。RabbitMQ 中通過(guò)綁定將 交換器與隊(duì)列關(guān)聯(lián)起來(lái),在綁定的時(shí)候一般會(huì)指定一個(gè)綁定鍵(BindingKey),這樣 RabbitMQ 就指定如何正確地將消息路由到隊(duì)列了。
生產(chǎn)者將消息發(fā)送給交換器,需要一個(gè) RoutingKey,當(dāng) BindingKey 和 RoutingKey 相匹配時(shí),消息會(huì)被路由到對(duì)應(yīng)的隊(duì)列中。在綁定多個(gè)隊(duì)列到同一個(gè)交換器的時(shí)候,這些綁定允許使用相同的 BindingKey。BindingKey 并不少在所有的情況下都生效,它依賴于交換器類型,比如 fanout 類型的交換器就會(huì)無(wú)視 BindingKey,而是將消息路由到所有綁定到該交換器的隊(duì)列中。
?
沿用本文開頭的比喻,交換器相當(dāng)于投遞包裹的郵箱,RoutingKey 相當(dāng)于寫在包裹上的地址,BindingKey 相當(dāng)于包裹的目的地,當(dāng)填寫在包裹上的地址和實(shí)際想要投遞的地址相匹配時(shí),那么這個(gè)包裹就會(huì)被正確投遞到目的地,最后這個(gè)目的地的 "主人" -- 隊(duì)列可以保留這個(gè)包裹。如果填寫的地址出錯(cuò),郵遞員不能正確投遞到目的地,包裹可能會(huì)回退給寄件人,也有可能被丟棄。
在某些情形下,RoutingKey 與 BindingKey 可以看作同一個(gè)東西。
?
channel.exchangeDeclare(EXCHANGE_NAME, "direct", true, false, null); channel.queueDeclare(QUEUE_NAME, true, false, false, null); channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY); String message = "Hello World!"; channel.basicPublic(EXCHANGE_NAME, ROUTING_KEY,MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());以上代碼聲明了一個(gè) direct 類型的交換器,然后將交換器和隊(duì)列綁定起來(lái)。注意這里使用的字樣是 "ROUTING_KEY",在本該使用 BindingKey 的 channel.queueBind 方法中卻和 channel.basicPublish 方法同樣使用了 RoutingKey,這樣做的潛臺(tái)詞是:這里的 RoutingKey 和 BindingKey 是同一個(gè)東西。在 direct 交換器類型下,RoutingKey 和 BindingKey 需要完全匹配才能使用,所以上面的代碼中采用了這種寫法會(huì)顯得方便許多。
但是在 topic 交換器類型下,RoutingKey 和 BindingKey 之間需要做模糊匹配,兩者并不是相同的。
BingingKey 其實(shí)也屬于路由鍵的一種,官方解釋為:the routing key to use for the binding。可以翻譯為:在綁定的時(shí)候使用的路由鍵。大多數(shù)時(shí)候,包括官方文檔和 RabbitMQ Java API 中都把 BindingKey 和 RoutingKey 看作 RoutingKey,為了避免混淆,可以這么理解:
在綁定的時(shí)候,其中需要的路由鍵是 BindingKey。涉及的客戶端方法如: channel.exchangeBind、channel.queueBind,對(duì)應(yīng) AMQP 命令為 Exchange.Bind、Queue.Bind。
發(fā)送消息的時(shí)候,其中需要的路由鍵是 RoutingKey。涉及的客戶端方法如 channel.basicPublish,對(duì)應(yīng)的 AMQP 命令為 Basic.Publish。
?
由于某些歷史的原因,包括現(xiàn)存能搜集到的資料顯示:大多數(shù)情況下習(xí)慣性地將 BindingKey 寫成 RoutingKey,尤其是在使用 direct 類型的交換器的時(shí)候。
?
交換器類型
RabbitMQ 常用的交換器類型有 fanout、direct、topic、headers 這四種。AMQP 協(xié)議里還提到另外兩種類型:System 和自定義。
?
fanout
它會(huì)把所有發(fā)送到該交換器的消息路由到所有與該交換器綁定的隊(duì)列中。
?
direct
direct 類型的交換器路由規(guī)則也很簡(jiǎn)單,它會(huì)把消息路由到那些 BindingKey 和 RoutingKey 完全匹配的隊(duì)列中。
以下圖為例,交換器的類型為 direct,如果我們發(fā)送一條消息,并在發(fā)送消息的時(shí)候設(shè)置路由鍵為 "warning",消息會(huì)路由到 Queue1 和 Queue2,對(duì)應(yīng)的示例代碼如下:
channel.basicPublish(EXCHANGE_NAME, "warning",MessageProperties.PERSISTENT_TEXT_PLAIN,message.getbytes());?
如果 在發(fā)送消息的時(shí)候設(shè)置路由鍵為 "info" 或者 "debug",消息只會(huì)路由到 Queue2。如果以其他的路由鍵發(fā)送消息,則消息不會(huì)路由到這兩個(gè)隊(duì)列中。
?
topic
前面講到 direct 類型的交換器路由規(guī)則是完全匹配 BindingKey 和 RoutingKey,但是這種嚴(yán)格的匹配方式在很多情況下不能滿足實(shí)際業(yè)務(wù)的需求。topic 類型的交換器在匹配規(guī)則上進(jìn)行了擴(kuò)展,它與 direct 類型的交換器相似,也是將消息路由到 BindingKey 和 RoutingKey 相匹配的隊(duì)列中,但這里的匹配規(guī)則有些不同,它約定:
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è)單詞, "#" 用于匹配多個(gè)單詞(可以是零個(gè))
以圖 2-8 中的配置為例:
路由鍵為 "com.rabbitmq.client" 的消息會(huì)同時(shí)路由到 Queue1 和 Queue2;
路由鍵為 "com.hidden.client" 的消息只會(huì)路由到 Queue2;
路由鍵為 "com.hidden.demo" 的消息只會(huì)路由到 Queue2;
路由鍵為 "java.util.concurrent" 的消息將會(huì)被丟棄或者返回給生產(chǎn)者(需要設(shè)置 mandatory 參數(shù)),因?yàn)樗鼪](méi)有匹配任何路由鍵。
?
headers
headers 類型的交換器不依賴于路由鍵的匹配規(guī)則來(lái)路由消息,而是根據(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ì)看到它的存在。
?
轉(zhuǎn)載于:https://www.cnblogs.com/eleven24/p/10327288.html
總結(jié)
以上是生活随笔為你收集整理的RabbitMQ 相关概念的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 鲍姆-韦尔奇算法 数学推导
- 下一篇: 工地上的“春晚”