一、RabbitMQ的模型架構
Producer:生產者,就是投遞消息的一方。
生產者創建消息,然後發佈到RabbitMQ中。消息一般可以包含2個部分:消息體和標籤(Label)。
Consumer:消費者,就是接收消息的一方。
消費者連接到RabbitMQ服務器,並訂閱到隊列上。當消費者消費一條消息時,只是消費消息的消息體(payload)。
Broker:消息中間件的服務節點。
一個RabbitMQ Broker可以簡單地看作一個RabbitMQ服務節點,或者RabbitMQ服務實例。
Queue:隊列,是RabbitMQ的內部對象,用於存儲消息。
RabbitMQ中消息都只能存儲在隊列中,這一點和Kafka這種消息中間件相反。Kafka將消息存儲在topic(主題)這個邏輯層面,而相對應的隊列邏輯只是topic實際存儲文件中的位移標識。RabbitMQ的生產者生產消息並最終投遞到隊列中,消費者可以從隊列中獲取消息並消費。
多個消費者可以訂閱同一個隊列,這時隊列中的消息會被平均分攤(Round-Robin,即輪詢)給多個消費者進行處理,而不是每個消費者都收到所有的消息並處理。
RabbitMQ不支持隊列層面的廣播消費
Exchange:交換器,生產者將消息發送到Exchange,由交換器將消息路由到一個或者多個隊列中。
RoutingKey:路由鍵。生產者將消息發給交換器的時候,一般會指定一個RoutingKey,用來指定這個消息的路由規則,而這個Routing Key需要與交換器類型和綁定鍵(BindingKey)聯合使用才能最終生效。
在交換器類型和綁定鍵(BindingKey)固定的情況下,生產者可以在發送消息給交換器時,通過指定RoutingKey來決定消息流向哪裏。
Binding:綁定。RabbitMQ中通過綁定將交換器與隊列關聯起來,在綁定的時候一般會指定一個綁定鍵(BindingKey),這樣RabbitMQ就知道如何正確地將消息路由到隊列了。
生產者將消息發送給交換器時,需要一個RoutingKey,當BindingKey和RoutingKey相匹配時,消息會被路由到對應的隊列中。
二、交換器類型
RabbitMQ常用的交換器類型有fanout、direct、topic、headers這四種。
fanout:它會把所有發送到該交換器的消息路由到所有與該交換器綁定的隊列中。很像子網廣播,每臺子網內的主機都獲得了一份複製的消息。
direct: 它會把消息路由到那些BindingKey和RoutingKey完全匹配的隊列中。
如果我們發送一條消息,並在發送消息的時候設置路由鍵爲“warning”,則消息會路由到Queue1和Queue2;
如果在發送消息的時候設置路由鍵爲“info”或者“debug”,消息只會路由到Queue2。
topic : 將消息路由到BindingKey和RoutingKey相匹配的隊列中,但這裏的匹配規則有些不同,它約定了一些規則:
- RoutingKey爲一個點號“.”分隔的字符串(被點號“.”分隔開的每一段獨立的字符串稱爲一個單詞),如“com.rabbitmq.client”、“java.util.concurrent”、“com.hidden.client”;
- BindingKey和RoutingKey一樣也是點號“.”分隔的字符串;
- BindingKey中可以存在兩種特殊字符串“*”和“#”,用於做模糊匹配,其中“*”用於匹配一個單詞,“#”用於匹配多規格單詞(可以是零個)。
-
headers : headers類型的交換器不依賴於路由鍵的匹配規則來路由消息,而是根據發送的消息內容中的headers屬性進行匹配。在綁定隊列和交換器時制定一組鍵值對,當發送消息到交換器時,RabbitMQ會獲取到該消息的headers(也是一個鍵值對的形式),對比其中的鍵值對是否完全匹配隊列和交換器綁定時指定的鍵值對,如果完全匹配則消息會路由到該隊列,否則不會路由到該隊列。
headers類型的交換器性能會很差,而且也不實用,基本上不會看到它的存在。
三、RabbitMQ運轉流程
生產者發送消息的過程:
- 生產者連接到RabbitMQ Broker,建立一個連接(Connection),開啓一個信道(Channel)
- 生產者聲明一個交換器,並設置相關屬性,比如交換機類型、是否持久化等
- 生產者聲明一個隊列並設置相關屬性,比如是否排他、是否持久化、是否自動刪除等
- 生產者通過路由鍵將交換器和隊列綁定起來
- 生產者發送消息至RabbitMQ Broker,其中包含路由鍵、交換器等信息
- 相應的交換器根據接收到的路由鍵查找相匹配的隊列。
- 如果找到,則將從生產者發送過來的消息存入相應的隊列中。
- 如果沒有找到,則根據生產者配置的屬性選擇丟棄還是回退給生產者
- 關閉信道。
- 關閉連接。
消費者接收消息的過程:
- 消費者連接到RabbitMQ Broker,建立一個連接(Connection),開啓一個信道(Channel)。
- 消費者向RabbitMQ Broker請求消費相應隊列中的消息,可能會設置相應的回調函數,以及做一些準備工作。
- 等待RabbitMQ Broker迴應並投遞相應隊列中的消息,消費者接收消息。
- 消費者確認(ack)接收到的消息。
- RabbitMQ從隊列中刪除相應已經被確認的消息。
- 關閉信道。
- 關閉連接。
Connection和Channel
生產者還是消費者,要和RabbitMQ Broker建立連接,這個連接就是一條TCP連接,也就是Connection。一旦TCP連接建立起來,客戶端緊接着可以創建一個AMQP信道(Channel),每個信道都會被指派一個唯一的ID。信道是建立在Connection之上的虛擬連接,RabbitMQ處理的每條AMQP指令都是通過信道完成的。
完全可以直接使用Connection就能完成信道的工作,爲什麼還要引入信道呢?
RabbitMQ採用類似NIO(Non-blocking I/O)的做法,選擇TCP連接複用,不僅可以減少性能開銷,同時也便於管理。
四、AMQP協議介紹
RabbitMQ就是AMQP協議的Erlang的實現(當然RabbitMQ還支持STOMP 、MQTT等協議)。
AMQP協議本身包括三層:
- Module Layer:位於協議最高層,主要定義了一些供客戶端調用的命令,客戶端可以利用這些命令實現自己的業務邏輯。
- Session Layer:位於中間層,主要負責將客戶端的命令發送給服務器,再將服務端的應答返回給客戶端,主要爲客戶端與服務器之間的通信提供可靠性同步機制和錯誤處理。
- Transport Layer:位於最底層,主要傳輸二進制數據流,提供幀的處理、信道複用、錯誤檢測和數據表示等。
AMQP說到底還是一個通信協議,通信協議都會涉及報文交互,從low-level層面舉例來說,AMQP本身是應用層的協議,其填充於TCP協議層的數據部分。而從high-level層面來說,AMQP是通過協議命令進行交互的。AMQP協議可以看作一系列結構化命令的集合,這裏的命令代表一種操作,類似於HTTP中的方法(GET、POST、PUT、DELETE等)。
AMQP生產者流轉過程
AMQPP 消費者流轉過程
NIO,也稱非阻塞I/O,包含三大核心部分:Channel(信道)、Buffer(緩衝區)和Selector(選擇器)。NIO基於Channel和Buffer進行操作,數據總是從信道讀取數據到緩衝區中,或者從緩衝區寫入到信道中。Selector用於監聽多個信道的事件(比如連接打開,數據到達等)。因此,單線程可以監聽多個數據的信道。NIO中有一個很有名的Reactor模式,有興趣的讀者可以深入研究。
STOMP,即Simple (or Streaming) Text Oriented Messaging Protocol,簡單(流)文本面向消息協議,它提供了一個可互操作的連接格式,運行STOMP客戶端與任意STOMP消息代理(Broker)進行交互。STOMP協議由於設計簡單,易於開發客戶端,因此在多種語言和平臺上得到廣泛的應用。
MQTT,即Message Queuing Telemetry Transport,消息隊列遙測傳輸,是IBM開發的一個即時通信協議,有可能成爲物聯網的重要組成部分。該協議支持所有平臺,幾乎可以把所有物聯網和外部連接起來,被用來當作傳感器和制動器的通信協議。