1. 程式人生 > 實用技巧 >RabbitMQ知識點整理2-相關概念介紹

RabbitMQ知識點整理2-相關概念介紹

RabbitMQ 整體上是一個生產者與消費者模型,主要負責接收、儲存和轉發訊息。可以把訊息傳遞的過程想象成:當你將一個包裹送到郵局,郵局會暫存並最終將郵件通過郵遞員送到收件人的手上, RabbitMQ 就好比由郵局、郵箱和郵遞員組成的一個系統。從計算機術語層面來說,RabbitMQ 模型更像是一種交換機模型。

RabbitMQ 的整體模型架構如圖2-1 所示。

1.生產者和消費者

1.1.Producer: 生產者,就是投遞訊息的一方。

生產者建立訊息,然後釋出到RabbitMQ 中。訊息一般可以包含2 個部分:訊息體和標籤Label 。訊息體也可以稱之為payload ,在實際應用中,訊息體一般是一個帶有業務邏輯結構的資料,比如一個JSON 字串。當然可以進一步對這個訊息體進行序列化操作。訊息的標籤用來表述這條訊息, 比如一個交換器的名稱和一個路由鍵。生產者把訊息交由RabbitMQ ,
RabbitMQ 之後會根據標籤把訊息傳送給感興趣的消費者Consumer。


1.2.Consumer: 消費者, 就是接收訊息的一方。

消費者連線到RabbitMQ 伺服器,並訂閱到佇列上。當消費者消費一條訊息時, 只是消費訊息的訊息體payload 。在訊息路由的過程中, 訊息的標籤會丟棄, 存入到佇列中的訊息只有訊息體,消費者也只會消費到訊息體, 也就不知道訊息的生產者是誰,當然消費者也不需要知道。

1.3.Broker: 訊息中介軟體的服務節點。

對於RabbitMQ 來說, 一個RabbitMQ Broker 可以簡單地看作一個RabbitMQ 服務節點,或者RabbitMQ 服務例項。大多數情況下也可以將一個RabbitMQ Broker 看作一臺RabbitMQ伺服器。

圖2-2 展示了生產者將訊息存入RabbitMQ Broker ,以及消費者從Broker 中消費資料的整個流程。

首先生產者將業務方資料進行可能的包裝, 之後封裝成訊息, 傳送(AMQP 協議裡這個動作對應的命令為Basic.Publish)到Broker 中。消費者訂閱並接收訊息(AMQP 協議裡這個動作對應的命令為Basic.Consume 或者Basic.Get) ,經過可能的解包處理得到原始的資料,之後再進行業務處理邏輯。這個業務處理邏輯並不一定需要和接收訊息的邏輯使用同一個執行緒。消費者程序可以使用一個執行緒去接收訊息,存入到記憶體中,比如使用Java 中的BlockingQueue 。業務處理邏輯使用另一個執行緒從記憶體中讀取資料,這樣可以將應用進一步解稿,提高整個應用的處理效率。

2.佇列

Queue: 佇列,是RabbitMQ 的內部物件,用於儲存訊息。參考圖2-1, 佇列可以用圖2-3表示。

RabbitMQ 中訊息都只能儲存在佇列中,這一點和Kafka這種訊息中介軟體相反。Kafka將訊息儲存在topic(主題)這個邏輯層面,而相對應的佇列邏輯只是topic 實際儲存檔案中的位移標識。RabbitMQ 的生產者生產訊息並最終技遞到佇列中,消費者可以從佇列中獲取訊息並消費。

多個消費者可以訂閱同一個佇列,這時佇列中的訊息會被平均分攤(Round-Robin ,即輪詢)給多個消費者進行處理,而不是每個消費者都收到所有的訊息井處理,如圖2-4 所示。

RabbitMQ 不支援佇列層面的廣播消費,如果需要廣播消費,需要在其上進行二次開發,處理邏輯會變得異常複雜,同時也不建議這麼做。

3.交換器、路由鍵、繫結

Exchange: 交換器。在圖2-4 中我們暫時可以理解成生產者將訊息投遞到佇列中,實際上這個在RabbitMQ 中不會發生。真實情況是,生產者將訊息傳送到Exchange (交換器,通常也可以用大寫的"X" 來表示),由交換器將訊息路由到一個或者多個佇列中。如果路由不到,或許會返回給生產者,或許直接丟棄。這裡可以將RabbitMQ 中的交換器看作一個簡單的實體,更多的細節會在後面的章節中有所涉及。

交換器的具體示意圖如圖2-5 所示。

RabbitMQ 中的交換器有四種類型,不同的型別有著不同的路由策略,這將在後面的交換器型別(Exchange Types) 中介紹。

RoutingKey: 路由鍵。生產者將訊息發給交換器的時候, 一般會指定一個RoutingKey ,用來指定這個訊息的路由規則,而這個RoutingKey 需要與交換器型別和繫結鍵(BindingKey) 聯合使用才能最終生效。

在交換器型別和繫結鍵(BindingKey) 固定的情況下,生產者可以在傳送訊息給交換器時,通過指定RoutingKey 來決定訊息流向哪裡。

Binding: 繫結。RabbitMQ 中通過繫結將交換器與佇列關聯起來,在繫結的時候一般會指定一個繫結鍵( BindingKey ) ,這樣RabbitMQ 就知道如何正確地將訊息路由到隊列了,如圖2-6所示。

生產者將訊息傳送給交換器時, 需要一個RoutingKey , 當BindingKey 和RoutingKey 相匹配時, 訊息會被路由到對應的佇列中。在繫結多個佇列到同一個交換器的時候, 這些繫結允許使用相同的BindingKey, BindingKey 並不是在所有的情況下都生效,它依賴於交換器型別, 比如fanout 型別的交換器就會無視BindingKey , 而是將訊息路由到所有繫結到該交換器的佇列中。

沿用本章開頭的比喻, 交換器相當於投遞包裹的郵箱, RoutingKey 相當於填寫在包裹上的地址, BindingKey 相當於包裹的目的地, 當填寫在包裹上的地址和實際想要投遞的地址相匹配時, 那麼這個包裹就會被正確投遞到目的地, 最後這個目的地的"主人"一一佇列可以保留這個包裹。如果填寫的地址出錯,郵遞員不能正確投遞到目的地, 包裹可能會回退給寄件人,也有可能被丟棄。

有經驗的讀者可能會發現, 在某些情形下, RoutingKey 與BindingKey 可以看作同一個東西。程式碼中的體現可以檢視 -> RabbitMQ知識點整理1-生產和消費訊息

RabbitMQ知識點整理1-生產和消費訊息中生產者客戶端程式碼中, 聲明瞭一個direct型別的交換器(關於交換器在後面會講解), 然後將交換器和佇列繫結起來, 注意這裡使用的字樣是"ROUTING_KEY",在本該使用BindingKey 的channel.queueBind 方法中卻和channel.basicPublish 方法同樣使用了RoutingKey, 這樣做的潛臺詞是:這裡的RoutingKey 和BindingKey 是同一個東西。在direct 交換器型別下,RoutingKey 和BindingKey 需要完全匹配才能使用,所以程式碼中採用了此種寫法會顯得方便許多。

但是在topic 交換器型別下, RoutingKey 和BindingKey 之間需要做模糊匹配,兩者並不是相同的。

BindingKey 其實也屬於路由鍵中的一種,官方解釋為: the routing key to use for the binding。可以翻譯為:在繫結的時候使用的路由鍵。大多數時候,包括官方文件和RabbitMQ Java API中都把BindingKey 和RoutingKey 看作RoutingKey ,為了避免混淆,可以這麼理解:

  • 在使用繫結的時候,其中需要的路由鍵是BindingKey 。涉及的客戶端方法如:channel.exchangeBind 、channel.queueBind ,對應的AMQP 命令為Exchange.Bind 、Queue.Bind 。
  • 在傳送訊息的時候,其中需要的路由鍵是RoutingKey。涉及的客戶端方法如channel.basicPublish ,對應的AMQP 命令為Basic.Publish 。

4.交換器型別

RabbitMQ 常用的交換器型別有fanout 、direct、topic 、headers 這四種。AMQP 協議裡還提到另外兩種型別: System 和自定義,這裡不予描述。對於這四種類型下面一一闡述。

4.1.fanout

它會把所有傳送到該交換器的訊息路由到所有與該交換器繫結的佇列中。

4.2.direct

direct 型別的交換器路由規則也很簡單,它會把訊息路由到那些BindingKey 和RoutingKey完全匹配的佇列中。

以圖2-7 為例,交換器的型別為direct,如果我們傳送一條訊息,並在傳送訊息的時候設定路由鍵為" warning" ,則訊息會路由到Queuel 和Queue2 ,對應的示例程式碼如下:

channel.basicPublish(EXCHANGE_NAME, "warning", MessageProperties.PERSISTENT TEXT PLAIN, message.getBytes()) ;

如果在傳送訊息的時候設定路由鍵為" info" 或者"debug" ,訊息只會路由到Queue2 。如果以其他的路由鍵傳送訊息,則訊息不會路由到這兩個佇列中。

4.3.topic

前面講到direct 型別的交換器路由規則是完全匹配BindingKey 和RoutingKey ,但是這種嚴格的匹配方式在很多情況下不能滿足實際業務的需求。topic 型別的交換器在匹配規則上進行了擴充套件,它與direct 型別的交換器相似,也是將訊息路由到BindingKey 和RoutingKey 相匹配的佇列中,但這裡的匹配規則有些不同,它約定:

  • RoutingKey為一個點號"."分隔的字串(被點號"."分隔的每一段獨立的字串稱為一個單詞), 如"com.rabbitmq.client"、"java.util.concurrent"、"com.hidden.client"
  • BindingKey和RoutingKey一樣也是點號"."分隔的字串
  • BindingKey中可以存在兩種特殊字串"*"和"#", 用於模糊匹配, 其中"*"用於匹配一個單詞, "#"用於匹配多規格單詞(可以是零個)

以圖2-8 中的配置為例:

  • 路由鍵為" com.rabbitmq.client" 的訊息會同時路由到Queue1 和Queue2;
  • 路由鍵為" com.hidden.client" 的訊息只會路由到Queue2 中;
  • 路由鍵為" com.hidden.demo" 的訊息只會路由到Queue2 中:
  • 路由鍵為"java.rabbitmq.demo" 的訊息只會路由到Queue1 中:
  • 路由鍵為" java.util.concurrent" 的訊息將會被丟棄或者返回給生產者(需要設定mandatory 引數) ,因為它沒有匹配任何路由鍵。

4.4.headers

headers 型別的交換器不依賴於路由鍵的匹配規則來路由訊息,而是根據傳送的訊息內容中的headers 屬性進行匹配。在繫結佇列和交換器時制定一組鍵值對, 當傳送訊息到交換器時,RabbitMQ 會獲取到該訊息的headers (也是一個鍵值對的形式) ,對比其中的鍵值對是否完全匹配佇列和交換器繫結時指定的鍵值對,如果完全匹配則訊息會路由到該佇列,否則不會路由到該佇列。headers 型別的交換器效能會很差,而且也不實用,基本上不會看到它的存在。