rabbitMQ 基本概念
RabbitMQ 整體上是一個生產者與消費者模型,主要負責接收、存儲和轉發消息。可以把消
息傳遞的過程想象成:當你將一個包裹送到郵局,郵局會暫存並最終將郵件通過郵遞員送到收
件人的手上, RabbitMQ 就好比由郵局、郵箱和郵遞員組成的一個系統。從計算機術語層面來說,
RabbitMQ 模型更像是一種交換機模型。
生產者和消費者
Producer: 生產者,就是投遞消息的一方。
生產者創建消息,然後發布到RabbitMQ 中。消息一般可以包含2 個部分:消息體和標簽
(Label)。消息體也可以稱之為payload ,在實際應用中,消息體一般是一個帶有業務邏輯結構
的數據,比如一個JSON 字符串。當然可以進一步對這個消息體進行序列化操作。消息的標簽
用來表述這條消息, 比如一個交換器的名稱和一個路由鍵。生產者把消息交由RabbitMQ ,
RabbitMQ 之後會根據標簽把消息發送給感興趣的消費者C Consumer ) 。
Consumer: 消費者, 就是接收消息的一方。
消費者連接到RabbitMQ 服務器,並訂閱到隊列上。當消費者消費一條消息時, 只是消費
消息的消息體(payload ) 。在消息路由的過程中, 消息的標簽會丟棄, 存入到隊列中的消息只
有消息體,消費者也只會消費到消息體, 也就不知道消息的生產者是誰,當然消費者也不需要
知道。
Broker: 消息中間件的服務節點。
對於RabbitMQ 來說, 一個RabbitMQ Broker 可以簡單地看作一個RabbitMQ 服務節點,
或者RabbitMQ 服務實例。大多數情況下也可以將一個RabbitMQ Broker 看作一臺RabbitMQ
服務器。
首先生產者將業務方數據進行可能的包裝, 之後封裝成消息, 發送( AMQP 協議裏這個動
作對應的命令為Basic.Publish) 到Broker 中。消費者訂閱並接收消息(AMQP 協議裏這個
動作對應的命令為Basic.Consume 或者Basic. Get) ,經過可能的解包處理得到原始的數據,
之後再進行業務處理邏輯。這個業務處理邏輯並不一定需要和接收消息的邏輯使用同一個線程。
消費者進程可以使用一個線程去接收消息,存入到內存中,比如使用Java 中的BlockingQueue 。
業務處理邏輯使用另一個線程從內存中讀取數據,這樣可以將應用進一步解稿,提高整個應用
的處理效率。
隊列
Queue: 隊列,是RabbitMQ 的內部對象,用於存儲消息。參考圖2- 1.隊列可以用圖2-3
表示。
RabbitMQ 中消息都只能存儲在隊列中,這一點和Katka 這種消息中間件相反。Katka 將消
息存儲在topic (主題)這個邏輯層面,而相對應的隊列邏輯只是topic 實際存儲文件中的位移
標識。RabbitMQ 的生產者生產消息並最終投遞到隊列中,消費者可以從隊列中獲取消息並消費。
多個消費者可以訂閱同一個隊列,這時隊列中的消息會被平均分攤(Round-Robin ,即輪詢)
給多個消費者進行處理,而不是每個消費者都收到所有的消息井處理,如圖2-4 所示。
RabbitMQ 不支持隊列層面的廣播消費,如果需要廣播消費,需要在其上進行二次開發,處理邏輯會變得異常復雜,同時也不建議這麽做。
交換器、路由鍵、綁定
Exchange: 交換器。在圖2-4 中我們暫時可以理解成生產者將消息投遞到隊列中,實際上
這個在RabbitMQ 中不會發生。真實情況是,生產者將消息發送到Exchange (交換器,通常也
可以用大寫的"X" 來表示),由交換器將消息路由到一個或者多個隊列中。如果路由不到,或
許會返回給生產者,或許直接丟棄。這裏可以將RabbitMQ 中的交換器看作一個簡單的實體,
更多的細節會在後面的章節中有所涉及。
RabbitMQ 中的交換器有四種類型,不同的類型有著不同的路由策略,這將在下一節的交換
器類型(Exchange Types) 中介紹。
RoutingKey: 路由鍵。生產者將消息發給交換器的時候, 一般會指定一個RoutingKey ,用
來指定這個消息的路由規則,而這個RoutingKey 需要與交換器類型和綁定鍵(BindingKey) 聯
合使用才能最終生效。
在交換器類型和綁定鍵(BindingKey) 固定的情況下,生產者可以在發送消息給交換器時,
通過指定RoutingKey 來決定消息流向哪裏。
Binding: 綁定。RabbitMQ 中通過綁定將交換器與隊列關聯起來,在綁定的時候一般會指
定一個綁定鍵( BindingKey ) ,這樣RabbitMQ 就知道如何正確地將消息路由到隊列了,如圖2-6
所示。
生產者將消息發送給交換器時, 需要一個RoutingKey , 當BindingKey 和RoutingKey 相匹
配時, 消息會被路由到對應的隊列中。在綁定多個隊列到同一個交換器的時候, 這些綁定允許
使用相同的BindingKey。BindingKey 並不是在所有的情況下都生效,它依賴於交換器類型, 比
如fanout 類型的交換器就會無視BindingKey , 而是將消息路由到所有綁定到該交換器的隊列中。
對於初學者來說, 交換器、路由鍵、綁定這幾個概念理解起來會有點晦澀, 可以對照著代
碼清單1-1 來加深理解。
沿用本章開頭的比喻, 交換器相當於投遞包裹的郵箱, RoutingKey 相當於填寫在包裹上的
地址, BindingKey 相當於包裹的目的地, 當填寫在包裹上的地址和實際想要投遞的地址相匹配
時, 那麽這個包裹就會被正確投遞到目的地, 最後這個目的地的"主人"一一隊列可以保留這
個包裹。如果填寫的地址出錯,郵遞員不能正確投遞到目的地, 包裹可能會回退給寄件人,也
有可能被丟棄。
有經驗的讀者可能會發現, 在某些情形下, RoutingKey 與BindingKey 可以看作同一個東西。
代碼清單2-1 所展示的是代碼清單1-1 中的部分代碼:
以上代碼聲明了一個direct 類型的交換器(交換器的類型在下一節會詳細講述),然後將交
換器和隊列綁定起來。註意這裏使用的字樣是"ROUTING_阻y",在本該使用BindingKey 的
channel .qu eueBind 方法中卻和channe l.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 命令(詳情參見
2.2 節)為Exchange.Bind 、Queue.Bind 。
- 在發送消息的時候,其中需要的路由鍵是RoutingKey 。涉及的客戶端方法如
channel .basicPublish ,對應的AMQP 命令為Basic.Publish 。
由於某些歷史的原因,包括現存能搜集到的資料顯示:大多數情況下習慣性地將BindingKey
寫成RoutingKey ,尤其是在使用direct 類型的交換器的時候。本文後面的篇幅中也會將兩者合
稱為路由鍵,讀者需要註意區分其中的不同,可以根據上面的辨別方法進行有效的區分。
交換器類型
RabbitMQ 常用的交換器類型有fanout 、direct、topic 、headers 這四種。AMQP 協議裏還提
到另外兩種類型: System 和自定義,這裏不予描述。對於這四種類型下面一一闡述。
fanout
它會把所有發送到該交換器的消息路由到所有與該交換器綁定的隊列中。
direct
direct 類型的交換器路由規則也很簡單,它會把消息路由到那些BindingKey 和RoutingKey
完全匹配的隊列中。
以圖2-7 為例,交換器的類型為direct,如果我們發送一條消息,並在發送消息的時候設置
路由鍵為" warning" ,則消息會路由到Queuel 和Queue2 ,對應的示例代碼如下:
channel.basicPublish(EXCHANGE_NAME, "warning" ,
MessageProperties .PERSISTENT TEXT PLAIN,
message.getBytes()) ;
如果在發送消息的時候設置路由鍵為" info" 或者"debug" ,消息只會路由到Queue2 。如
果以其他的路由鍵發送消息,則消息不會路由到這兩個隊列中。
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" 的消息會同時路由到Queuel 和Queue2;
- 路由鍵為" com.hidden.client" 的消息只會路由到Queue2 中:
- 路由鍵為" com.hidden.demo" 的消息只會路由到Queue2 中:
- 路由鍵為"java.rabbitmq.demo" 的消息只會路由到Queuel 中:
-
路由鍵為" java.util..concurrent" 的消息將會被丟棄或者返回給生產者(需要設置
mandatory 參數) ,因為它沒有匹配任何路由鍵。
headers
headers 類型的交換器不依賴於路由鍵的匹配規則來路由消息,而是根據發送的消息內容中
的headers 屬性進行匹配。在綁定隊列和交換器時制定一組鍵值對, 當發送消息到交換器時,
RabbitMQ 會獲取到該消息的headers (也是一個鍵值對的形式) ,對比其中的鍵值對是否完全
匹配隊列和交換器綁定時指定的鍵值對,如果完全匹配則消息會路由到該隊列,否則不會路由
到該隊列。headers 類型的交換器性能會很差,而且也不實用,基本上不會看到它的存在。
rabbitMQ 基本概念