一:摘要概覽
第一篇文章RabbitMQ(一) – 初識RabbitMQ中基於AMQP協議對RabbitMQ整體進行了簡介,旨在幫助閱讀本系列文章的朋友建立初步的概念。文中最後部分使用到的客戶端操作API並未深入的進行學習理解,本文將從RabbbitMQ應用服務部分即Broker所包含的交換器與隊列兩方面深入學習
二:Exchange
爲什麼要在生產者與隊列之間提出交換器概念?試想如果生產者與隊列直接耦合,當生產者客戶端需要將一條消息發送至多個隊列,那就需要多次操作。多次操作不僅僅意味着連接性能損耗,雖然Channel是輕量級,並且意味着客戶端複雜度上升。所以,提出交換器概念,生產者只需要提前綁定設置好其與隊列關係,發送消息時攜帶路由轉發規則,後續邏輯交給服務端處理即可
2.1 默認交換器
RabbitMQ官網第一個使用示例Hello World中編寫類似如下示例代碼。當時就在好奇這不就生產者與隊列直接交互耦合了麼?其實不然,查看basicPublish()源碼實現,你會發現參數含義依次爲交換器、routingKey、BasicProperties、messageBytes。當第一個參數爲空時消息將發送到默認交換器,即RabbitMQ服務端提前創建好的交換器,該交換器類型爲direct
@SneakyThrows
public static void main (String[] args) {
Channel channel = createChannel();
String queueName = "queueName" , message = "測試消息";
channel.basicPublish("",queueName,null,message.getBytes());
}
打開RabbitMQ管理界面,查看AMQP Default Exchange的Bindings描述有如下一句話。所以這就可以解決很多初學者的疑惑,爲什麼官網給的第一個例子會出現將生產者與隊列直接耦合的假象
// 默認交換器隱式地綁定到每個隊列,其路由鍵等於隊列名稱
// 無法顯式綁定到默認交換器或從默認交換器解綁定。它也不能被刪除。
The default exchange is implicitly bound to every queue,
with a routing key equal to the queue name.
It is not possible to explicitly bind to,
or unbind from the default exchange.
It also cannot be deleted.
2.2 交換器類型
不同類型的交換器針對不同場景應用而設計,如將消息路由至全部綁定隊列選用fanout、將消息路由至指定routingKey隊列選用direct、將消息路由至模糊匹配routingKey則選用topic。當然還有一種header類型交換器,因爲其性能與依賴消息頭header進行規則匹配的原因,所以基本不會使用
序號 | 類型 | 路由匹配規則 | 備註 |
---|---|---|---|
1 | fanout | 消息路由到所有綁定隊列 | 交換器隊列綁定無需binding,消息發送無需routingKey |
2 | direct | routingKey與binding比較 | 一致性校驗路由 |
3 | topic | routingKey與binding比較 | 模糊匹配,binding/routingKey單詞層次間用"." 標識,binding綁定關係支持"*" 與"#" 兩種模糊規則。如com.message.log.* |
序號 | 模糊規則 | 備註 |
---|---|---|
1 | * | 代表一層,如綁定關係binding爲 log.* 則與 routingKey 爲 log.error 或 log.info 的消息匹配 |
2 | # | 代表0或多層,如綁定關係binding爲 log.# 則與 routingKey 爲 log 或 log.message.info 的消息匹配 |
2.3 交換器創建
參數 | 含義 |
---|---|
exchange | 交換器的名字 |
type | 交換器種類,BuiltinExchangeType枚舉類封裝了上面講的四類交換器類型 |
durable | 耐磨持久化,也就是當RabbitMQ服務應用重啓後該交換器是否還存在 |
autoDelete | 自動刪除,當所有與該交換器綁定的交換器或隊列不存在時即自動刪除 |
internal | true表示該交換器爲內置交換器,客戶端不能直接發送消息,需要交換器到交換器方式使用 |
arguments | key-value形式的一些交換器特性參數,如alternate-exchange。後續講解 |
交換器聲明的API具備衆多重載的形式,只要理解具體參數含義,選用合適的API進行使用即可 |
如下Demo示例創建持久化、自動刪除的交換器,打開控制檯可以看到D
、AD
標識則標識這兩個特性,如下圖所示
String exchangeName = "fanoutExchange";
// 持久化、自動刪除
boolean durable = true , autoDelete = false;
channel.exchangeDeclare(
exchangeName, BuiltinExchangeType.FANOUT,durable,autoDelete,null
);
2.4 刪除交換器
參數 | 含義 |
---|---|
exchange | 交換器名稱 |
ifUnused | true表示交換器未使用刪除,若正在使用則不刪除且拋出異常。false表示必須刪除,默認值false |
// 是否校驗交換器正在使用
boolean ifUnused = true;
channel.exchangeDelete(exchangeName,true);
2.5 補充API
方法 | 含義 |
---|---|
exchangeDeclareNoWait | 不需要返回值,等於異步執行交換器創建 。慎用!!!! |
exchangeDeleteNoWait | 與上述創建不等待相似 |
exchangeDeclarePassive | 檢測交換器存在,不存在拋出異常 |
三:Queue
3.1 隊列創建
參數 | 含義 |
---|---|
queue | 隊列的名字 |
durable | 耐磨持久化,也就是當RabbitMQ服務應用重啓後該隊列是否還存在 |
autoDelete | 自動刪除,當所有消費者斷開與隊列連接後自動刪除。前提是隊列創建後有消費者與其連接過 |
exclusive | 獨佔,true表示只能創建隊列的連接才能使用這個隊列。注意是連接,如同一連接創建的信道是可以使用該隊列的 |
arguments | key-value形式的一些隊列特性參數 |
隊列聲明的API具備衆多重載的形式,只要理解具體參數含義,選用合適的API進行使用即可 |
如下Demo創建一個獨佔、自動刪除的隊列。看到效果圖可能會奇怪爲什麼設置了持久化但是沒有D
持久化標誌,只有代表自動刪除的AD
、獨佔標識Excl
。自動刪除的隊列還要持久化幹嘛?
// 持久化、獨佔、自動刪除
boolean durable = true, exclusive = true, autoDelete = true;
channel.queueDeclare(queueName,durable,exclusive,autoDelete,null);
3.2 隊列刪除
參數 | 含義 |
---|---|
exchange | 交換器名稱 |
ifUnused | true表示隊列未使用刪除,正在使用則不刪除且拋出異常。false表示必須刪除,默認值false |
ifEmpty | true表示隊列爲空刪除,不爲空則不刪除且拋出異常。false表示必須刪除,默認值false |
// 校驗隊列是否正在使用
boolean ifUnused = true;
// 校驗隊列是否爲空
boolean ifEmpty = true;
channel.queueDelete(queueName,ifUnused,ifEmpty);
3.3 補充API
方法 | 含義 |
---|---|
queuePurge | 清空隊列但是不刪除隊列 |
queueDeclarePassive | 與交換器類似檢測隊列存在 |
queueDeclareNoWait | 與交換器類似不等待返回值創建 |
queueDeleteNoWait | 與交換器類似不等待返回值刪除 |
四:Binding
RabbitMQ服務應用通過前面兩大節基本就介紹完了,兩個重要組成就是交換器和隊列。兩者之間的綁定是通過Binding實現,同時需要注意的一點就是交換器與交換器之間也可以進行綁定。內置的交換器客戶端不能直接發送消息,就需要通過綁定交換器的方式使用
4.1 交換器綁定
需要明確的一點就是當創建兩個交換器綁定關係之後,當第二個參數的交換器接收消息之後會將消息發送給第一個參數的交換器
// 創建交換器
String directExchangeA = "directExchangeA",directExchangeB= "directExchangeB";
boolean durable = true,autoDelete = false;
channel.exchangeDeclare(
directExchangeA,BuiltinExchangeType.TOPIC,durable,autoDelete,null
);
channel.exchangeDeclare(
directExchangeB,BuiltinExchangeType.DIRECT,durable,autoDelete,null
);
// 交換器綁定
String exchangeBinding = "com.message.info";
channel.exchangeBind(directExchangeB,directExchangeA,exchangeBinding);
// 隊列聲明
String queueName1= "queue1" , queueName2 = "queue2";
boolean exclusive = false;
channel.queueDeclare(queueName1,durable,exclusive,autoDelete,null);
channel.queueDeclare(queueName2,durable,exclusive,autoDelete,null);
// B交換器綁定隊列
String queueBinding1 = "com.message.info" ,
queueBinding2 = "com.message.error";
channel.queueBind(queueName1,directExchangeB,queueBinding1);
channel.queueBind(queueName2,directExchangeB,queueBinding2);
// 發送消息
String messageBytes = "測試消息", routingKey = "com.message.info";
channel.basicPublish(directExchangeA,routingKey,null,messageBytes.getBytes());
通過控制檯查看最後結果,發送到交換器A的消息路由到了交換器B,並最終路由到隊列1。基本可以斷定其中交換器A – B,交換器B – 隊列1使用的routingKey就是最初消息發送的routingKey。可以修改相關binding驗證,具體驗證過程這裏不再給出
4.2 隊列綁定
隊列綁定在上面例子中其實已經有了對應的API操作,就是將隊列與交換器進行綁定。參數中需要定義綁定關係的Binding,當然fanout交換器綁定的時候不需要用到這個關係。借用上面例子截圖隊列綁定關係如下:
4.3 補充API
方法 | 含義 |
---|---|
exchangeUnbind | 解除交換器綁定 |
queueUnbind | 解除隊列綁定 |
queueBindNoWait | 無返回值綁定隊列 |
exchangeBindNoWait | 無返回值綁定交換器 |