本文大量內(nèi)容來之網(wǎng)絡(luò),如有侵權(quán)聯(lián)系我
4. AMQP
4.1. 相關(guān)概念介紹
AMQP 一個提供統(tǒng)一消息服務(wù)的應(yīng)用層標準高級消息隊列協(xié)議,是應(yīng)用層協(xié)議的一個開放標準,為面向消息的中間件設(shè)計。
AMQP是一個二進制協(xié)議,擁有一些現(xiàn)代化特點:多信道、協(xié)商式,異步,安全,擴平臺,中立,高效。
RabbitMQ是AMQP協(xié)議的Erlang的實現(xiàn)。
概念 | 說明 |
---|---|
連接Connection | 一個網(wǎng)絡(luò)連接,比如TCP/IP套接字連接。 |
會話Session | 端點之間的命名對話。在一個會話上下文中,保證“恰好傳遞一次”。 |
信道Channel | 多路復(fù)用連接中的一條獨立的雙向數(shù)據(jù)流通道。為會話提供物理傳輸介質(zhì)。 |
客戶端Client | AMQP連接或者會話的發(fā)起者。AMQP是非對稱的,客戶端生產(chǎn)和消費消息,服務(wù)器存儲和路由這些消息。 |
服務(wù)節(jié)點Broker | 消息中間件的服務(wù)節(jié)點;一般情況下可以將一個RabbitMQ Broker看作一臺RabbitMQ 服務(wù)器。 |
端點 | AMQP對話的任意一方。一個AMQP連接包括兩個端點(一個是客戶端,一個是服務(wù)器)。 |
消費者Consumer | 一個從消息隊列里請求消息的客戶端程序。 |
生產(chǎn)者Producer | 一個向交換機發(fā)布消息的客戶端應(yīng)用程序。 |
1.4. RabbitMQ
RabbitMQ是由erlang語言開發(fā),基于AMQP(Advanced Message Queue 高級消息隊列協(xié)議)協(xié)議實現(xiàn)的消息隊列,它是一種應(yīng)用程序之間的通信方法,消息隊列在分布式系統(tǒng)開發(fā)中應(yīng)用非常廣泛。
RabbitMQ官方地址:http://www.rabbitmq.com/
RabbitMQ提供了6種模式:簡單模式,work模式,Publish/Subscribe發(fā)布與訂閱模式,Routing路由模式,Topics主題模式,RPC遠程調(diào)用模式(遠程調(diào)用,不太算MQ;暫不作介紹);
官網(wǎng)對應(yīng)模式介紹:https://www.rabbitmq.com/getstarted.html
3.1.2. 添加依賴
往heima-rabbitmq的pom.xml文件中添加如下依賴:
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.6.0</version>
</dependency>
在基礎(chǔ)文章中我會先用代碼展示所有常用api,在整個文集的最后會附加我github封裝,學(xué)習(xí)都是一步步來的,先看看基礎(chǔ)api
1、簡單模式
在上圖的模型中,有以下概念:
- P:生產(chǎn)者,也就是要發(fā)送消息的程序
- C:消費者:消息的接受者,會一直等待消息到來。
- queue:消息隊列,圖中紅色部分。類似一個郵箱,可以緩存消息;生產(chǎn)者向其中投遞消息,消費者從其中取出消息。
生產(chǎn)者代碼Producer
package com.itheima.rabbitmq.simple;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Producer {
static final String QUEUE_NAME = "simple_queue";
public static void main(String[] args) throws Exception {
//創(chuàng)建連接工廠
ConnectionFactory connectionFactory = new ConnectionFactory();
//主機地址;默認為 localhost
connectionFactory.setHost("localhost");
//連接端口;默認為 5672
connectionFactory.setPort(5672);
//虛擬主機名稱;默認為 /
connectionFactory.setVirtualHost("/cast");
//連接用戶名;默認為guest
connectionFactory.setUsername("guest");
//連接密碼;默認為guest
connectionFactory.setPassword("guest");
//創(chuàng)建連接
Connection connection = connectionFactory.newConnection();
// 創(chuàng)建頻道
Channel channel = connection.createChannel();
// 聲明(創(chuàng)建)隊列
/**
* 參數(shù)1:隊列名稱
* 參數(shù)2:是否定義持久化隊列
* 參數(shù)3:是否獨占本次連接
* 參數(shù)4:是否在不使用的時候自動刪除隊列
* 參數(shù)5:隊列其它參數(shù)
*/
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
// 要發(fā)送的信息
String message = "你好, 小美妞!";
/**
* 參數(shù)1:交換機名稱,如果沒有指定則使用默認Default Exchage
* 參數(shù)2:路由key,簡單模式可以傳遞隊列名稱
* 參數(shù)3:消息其它屬性
* 參數(shù)4:消息內(nèi)容
*/
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println("已發(fā)送消息:" + message);
// 關(guān)閉資源
channel.close();
connection.close();
}
}
在生產(chǎn)者代碼運行起來后開在控制臺查看的
編寫消費者代碼Consumer
package com.itheima.rabbitmq.simple;
import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtil.getConnection();
// 創(chuàng)建頻道
Channel channel = connection.createChannel();
// 聲明(創(chuàng)建)隊列
/**
* 參數(shù)1:隊列名稱
* 參數(shù)2:是否定義持久化隊列
* 參數(shù)3:是否獨占本次連接
* 參數(shù)4:是否在不使用的時候自動刪除隊列
* 參數(shù)5:隊列其它參數(shù)
*/
channel.queueDeclare(Producer.QUEUE_NAME, true, false, false, null);
//創(chuàng)建消費者;并設(shè)置消息處理
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
/**
* consumerTag 消息者標簽,在channel.basicConsume時候可以指定
* envelope 消息包的內(nèi)容,可從中獲取消息id,消息routingkey,交換機,消息和重傳標志(收到消息失敗后是否需要重新發(fā)送)
* properties 屬性信息
* body 消息
*/
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key為:" + envelope.getRoutingKey());
//交換機
System.out.println("交換機為:" + envelope.getExchange());
//消息id
System.out.println("消息id為:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("接收到的消息為:" + new String(body, "utf-8"));
}
};
//監(jiān)聽消息
/**
* 參數(shù)1:隊列名稱
* 參數(shù)2:是否自動確認,設(shè)置為true為表示消息接收到自動向mq回復(fù)接收到了,mq接收到回復(fù)會刪除消息,設(shè)置為false則需要手動確認
* 參數(shù)3:消息接收到后回調(diào)
*/
channel.basicConsume(Producer.QUEUE_NAME, true, consumer);
//不關(guān)閉資源,應(yīng)該一直監(jiān)聽消息
//channel.close();
//connection.close();
}
}
4.2. RabbitMQ運轉(zhuǎn)流程
在上述代碼中:
- 生產(chǎn)者發(fā)送消息
- 生產(chǎn)者創(chuàng)建連接(Connection),開啟一個信道(Channel),連接到RabbitMQ Broker;
- 聲明隊列并設(shè)置屬性;如是否排它,是否持久化,是否自動刪除;
- 將路由鍵(空字符串)與隊列綁定起來;
- 發(fā)送消息至RabbitMQ Broker;
- 關(guān)閉信道;
- 關(guān)閉連接;
- 消費者接收消息
- 消費者創(chuàng)建連接(Connection),開啟一個信道(Channel),連接到RabbitMQ Broker
- 向Broker 請求消費相應(yīng)隊列中的消息,設(shè)置相應(yīng)的回調(diào)函數(shù);
- 等待Broker回應(yīng)閉關(guān)投遞響應(yīng)隊列中的消息,消費者接收消息;
- 確認(ack,自動確認)接收到的消息;
- RabbitMQ從隊列中刪除相應(yīng)已經(jīng)被確認的消息;
- 關(guān)閉信道;
-
關(guān)閉連接;
image.png
4.3. 生產(chǎn)者流轉(zhuǎn)過程說明
- 客戶端與代理服務(wù)器Broker建立連接。會調(diào)用newConnection() 方法,這個方法會進一步封裝Protocol Header 0-9-1 的報文頭發(fā)送給Broker ,以此通知Broker 本次交互采用的是AMQPO-9-1 協(xié)議,緊接著Broker 返回Connection.Start 來建立連接,在連接的過程中涉及Connection.Start/.Start-OK 、Connection.Tune/.Tune-Ok ,Connection.Open/ .Open-Ok 這6 個命令的交互。
- 客戶端調(diào)用connection.createChannel方法。此方法開啟信道,其包裝的channel.open命令發(fā)送給Broker,等待channel.basicPublish方法,對應(yīng)的AMQP命令為Basic.Publish,這個命令包含了content Header 和content Body()。content Header 包含了消息體的屬性,例如:投遞模式,優(yōu)先級等,content Body 包含了消息體本身。
-
客戶端發(fā)送完消息需要關(guān)閉資源時,涉及到Channel.Close和Channl.Close-Ok 與Connetion.Close和Connection.Close-Ok的命令交互。
image.png
4.4. 消費者流轉(zhuǎn)過程說明
- 消費者客戶端與代理服務(wù)器Broker建立連接。會調(diào)用newConnection() 方法,這個方法會進一步封裝Protocol Header 0-9-1 的報文頭發(fā)送給Broker ,以此通知Broker 本次交互采用的是AMQPO-9-1 協(xié)議,緊接著Broker 返回Connection.Start 來建立連接,在連接的過程中涉及Connection.Start/.Start-OK 、Connection.Tune/.Tune-Ok ,Connection.Open/ .Open-Ok 這6 個命令的交互。
- 消費者客戶端調(diào)用connection.createChannel方法。和生產(chǎn)者客戶端一樣,協(xié)議涉及Channel . Open/Open-Ok命令。
- 在真正消費之前,消費者客戶端需要向Broker 發(fā)送Basic.Consume 命令(即調(diào)用channel.basicConsume 方法〉將Channel 置為接收模式,之后Broker 回執(zhí)Basic . Consume - Ok 以告訴消費者客戶端準備好消費消息。
- Broker 向消費者客戶端推送(Push) 消息,即Basic.Deliver 命令,這個命令和Basic.Publish 命令一樣會攜帶Content Header 和Content Body。
- 消費者接收到消息并正確消費之后,向Broker 發(fā)送確認,即Basic.Ack 命令。
- 客戶端發(fā)送完消息需要關(guān)閉資源時,涉及到Channel.Close和Channl.Close-Ok 與Connetion.Close和Connection.Close-Ok的命令交互。
[圖片上傳失敗...(image-cbb769-1612271105479)]
Work queues工作隊列模式
Work Queues
與入門程序的簡單模式
相比,多了一個或一些消費端,多個消費端共同消費同一個隊列中的消息。
應(yīng)用場景:對于 任務(wù)過重或任務(wù)較多情況使用工作隊列可以提高任務(wù)處理的速度。
Work Queues
與入門程序的簡單模式
的代碼是幾乎一樣的;可以完全復(fù)制,并復(fù)制多一個消費者進行多個消費者同時消費消息的測試。
package com.itheima.rabbitmq.work;
import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Producer {
static final String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws Exception {
//創(chuàng)建連接
Connection connection = ConnectionUtil.getConnection();
// 創(chuàng)建頻道
Channel channel = connection.createChannel();
// 聲明(創(chuàng)建)隊列
/**
* 參數(shù)1:隊列名稱
* 參數(shù)2:是否定義持久化隊列
* 參數(shù)3:是否獨占本次連接
* 參數(shù)4:是否在不使用的時候自動刪除隊列
* 參數(shù)5:隊列其它參數(shù)
*/
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
for (int i = 1; i <= 30; i++) {
// 發(fā)送信息
String message = "你好,小美妞 work模式--" + I;
/**
* 參數(shù)1:交換機名稱,如果沒有指定則使用默認Default Exchage
* 參數(shù)2:路由key,簡單模式可以傳遞隊列名稱
* 參數(shù)3:消息其它屬性
* 參數(shù)4:消息內(nèi)容
*/
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println("已發(fā)送消息:" + message);
}
// 關(guān)閉資源
channel.close();
connection.close();
}
}
package com.itheima.rabbitmq.work;
import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer1 {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtil.getConnection();
// 創(chuàng)建頻道
Channel channel = connection.createChannel();
// 聲明(創(chuàng)建)隊列
/**
* 參數(shù)1:隊列名稱
* 參數(shù)2:是否定義持久化隊列
* 參數(shù)3:是否獨占本次連接
* 參數(shù)4:是否在不使用的時候自動刪除隊列
* 參數(shù)5:隊列其它參數(shù)
*/
channel.queueDeclare(Producer.QUEUE_NAME, true, false, false, null);
//一次只能接收并處理一個消息
channel.basicQos(1);
//創(chuàng)建消費者;并設(shè)置消息處理
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
/**
* consumerTag 消息者標簽,在channel.basicConsume時候可以指定
* envelope 消息包的內(nèi)容,可從中獲取消息id,消息routingkey,交換機,消息和重傳標志(收到消息失敗后是否需要重新發(fā)送)
* properties 屬性信息
* body 消息
*/
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
//路由key
System.out.println("路由key為:" + envelope.getRoutingKey());
//交換機
System.out.println("交換機為:" + envelope.getExchange());
//消息id
System.out.println("消息id為:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消費者1-接收到的消息為:" + new String(body, "utf-8"));
Thread.sleep(1000);
//確認消息
channel.basicAck(envelope.getDeliveryTag(), false);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//監(jiān)聽消息
/**
* 參數(shù)1:隊列名稱
* 參數(shù)2:是否自動確認,設(shè)置為true為表示消息接收到自動向mq回復(fù)接收到了,mq接收到回復(fù)會刪除消息,設(shè)置為false則需要手動確認
* 參數(shù)3:消息接收到后回調(diào)
*/
channel.basicConsume(Producer.QUEUE_NAME, false, consumer);
}
}
package com.itheima.rabbitmq.work;
import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer2 {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtil.getConnection();
// 創(chuàng)建頻道
Channel channel = connection.createChannel();
// 聲明(創(chuàng)建)隊列
/**
* 參數(shù)1:隊列名稱
* 參數(shù)2:是否定義持久化隊列
* 參數(shù)3:是否獨占本次連接
* 參數(shù)4:是否在不使用的時候自動刪除隊列
* 參數(shù)5:隊列其它參數(shù)
*/
channel.queueDeclare(Producer.QUEUE_NAME, true, false, false, null);
//一次只能接收并處理一個消息
channel.basicQos(1);
//創(chuàng)建消費者;并設(shè)置消息處理
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
/**
* consumerTag 消息者標簽,在channel.basicConsume時候可以指定
* envelope 消息包的內(nèi)容,可從中獲取消息id,消息routingkey,交換機,消息和重傳標志(收到消息失敗后是否需要重新發(fā)送)
* properties 屬性信息
* body 消息
*/
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
//路由key
System.out.println("路由key為:" + envelope.getRoutingKey());
//交換機
System.out.println("交換機為:" + envelope.getExchange());
//消息id
System.out.println("消息id為:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消費者2-接收到的消息為:" + new String(body, "utf-8"));
Thread.sleep(1000);
//確認消息
channel.basicAck(envelope.getDeliveryTag(), false);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//監(jiān)聽消息
/**
* 參數(shù)1:隊列名稱
* 參數(shù)2:是否自動確認,設(shè)置為true為表示消息接收到自動向mq回復(fù)接收到了,mq接收到回復(fù)會刪除消息,設(shè)置為false則需要手動確認
* 參數(shù)3:消息接收到后回調(diào)
*/
channel.basicConsume(Producer.QUEUE_NAME, false, consumer);
}
}
小結(jié)
在一個隊列中如果有多個消費者,那么消費者之間對于同一個消息的關(guān)系是競爭的關(guān)系。
訂閱模式類型
前面2個案例中,只有3個角色:
- P:生產(chǎn)者,也就是要發(fā)送消息的程序
- C:消費者:消息的接受者,會一直等待消息到來。
- queue:消息隊列,圖中紅色部分
而在訂閱模型中,多了一個exchange角色,而且過程略有變化:
- P:生產(chǎn)者,也就是要發(fā)送消息的程序,但是不再發(fā)送到隊列中,而是發(fā)給X(交換機)
- C:消費者,消息的接受者,會一直等待消息到來。
- Queue:消息隊列,接收消息、緩存消息。
- Exchange:交換機,圖中的X。一方面,接收生產(chǎn)者發(fā)送的消息。另一方面,知道如何處理消息,例如遞交給某個特別隊列、遞交給所有隊列、或是將消息丟棄。到底如何操作,取決于Exchange的類型。Exchange有常見以下3種類型:
- Fanout:廣播,將消息交給所有綁定到交換機的隊列
- Direct:定向,把消息交給符合指定routing key 的隊列
- Topic:通配符,把消息交給符合routing pattern(路由模式) 的隊列
Exchange(交換機)只負責轉(zhuǎn)發(fā)消息,不具備存儲消息的能力,因此如果沒有任何隊列與Exchange綁定,或者沒有符合路由規(guī)則的隊列,那么消息會丟失!
producer
package com.itheima.rabbitmq.ps;
import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
/**
* 發(fā)布與訂閱使用的交換機類型為:fanout
*/
public class Producer {
//交換機名稱
static final String FANOUT_EXCHAGE = "fanout_exchange";
//隊列名稱
static final String FANOUT_QUEUE_1 = "fanout_queue_1";
//隊列名稱
static final String FANOUT_QUEUE_2 = "fanout_queue_2";
public static void main(String[] args) throws Exception {
//創(chuàng)建連接
Connection connection = ConnectionUtil.getConnection();
// 創(chuàng)建頻道
Channel channel = connection.createChannel();
/**
* 聲明交換機
* 參數(shù)1:交換機名稱
* 參數(shù)2:交換機類型,fanout、topic、direct、headers
*/
channel.exchangeDeclare(FANOUT_EXCHAGE, BuiltinExchangeType.FANOUT);
// 聲明(創(chuàng)建)隊列
/**
* 參數(shù)1:隊列名稱
* 參數(shù)2:是否定義持久化隊列
* 參數(shù)3:是否獨占本次連接
* 參數(shù)4:是否在不使用的時候自動刪除隊列
* 參數(shù)5:隊列其它參數(shù)
*/
channel.queueDeclare(FANOUT_QUEUE_1, true, false, false, null);
channel.queueDeclare(FANOUT_QUEUE_2, true, false, false, null);
//隊列綁定交換機
channel.queueBind(FANOUT_QUEUE_1, FANOUT_EXCHAGE, "");
channel.queueBind(FANOUT_QUEUE_2, FANOUT_EXCHAGE, "");
for (int i = 1; i <= 10; i++) {
// 發(fā)送信息
String message = "你好;小兔子!發(fā)布訂閱模式--" + I;
/**
* 參數(shù)1:交換機名稱,如果沒有指定則使用默認Default Exchage
* 參數(shù)2:路由key,簡單模式可以傳遞隊列名稱
* 參數(shù)3:消息其它屬性
* 參數(shù)4:消息內(nèi)容
*/
channel.basicPublish(FANOUT_EXCHAGE, "", null, message.getBytes());
System.out.println("已發(fā)送消息:" + message);
}
// 關(guān)閉資源
channel.close();
connection.close();
}
}
consumer1
package com.itheima.rabbitmq.ps;
import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer1 {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtil.getConnection();
// 創(chuàng)建頻道
Channel channel = connection.createChannel();
//聲明交換機
channel.exchangeDeclare(Producer.FANOUT_EXCHAGE, BuiltinExchangeType.FANOUT);
// 聲明(創(chuàng)建)隊列
/**
* 參數(shù)1:隊列名稱
* 參數(shù)2:是否定義持久化隊列
* 參數(shù)3:是否獨占本次連接
* 參數(shù)4:是否在不使用的時候自動刪除隊列
* 參數(shù)5:隊列其它參數(shù)
*/
channel.queueDeclare(Producer.FANOUT_QUEUE_1, true, false, false, null);
//隊列綁定交換機
channel.queueBind(Producer.FANOUT_QUEUE_1, Producer.FANOUT_EXCHAGE, "");
//創(chuàng)建消費者;并設(shè)置消息處理
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
/**
* consumerTag 消息者標簽,在channel.basicConsume時候可以指定
* envelope 消息包的內(nèi)容,可從中獲取消息id,消息routingkey,交換機,消息和重傳標志(收到消息失敗后是否需要重新發(fā)送)
* properties 屬性信息
* body 消息
*/
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key為:" + envelope.getRoutingKey());
//交換機
System.out.println("交換機為:" + envelope.getExchange());
//消息id
System.out.println("消息id為:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消費者1-接收到的消息為:" + new String(body, "utf-8"));
}
};
//監(jiān)聽消息
/**
* 參數(shù)1:隊列名稱
* 參數(shù)2:是否自動確認,設(shè)置為true為表示消息接收到自動向mq回復(fù)接收到了,mq接收到回復(fù)會刪除消息,設(shè)置為false則需要手動確認
* 參數(shù)3:消息接收到后回調(diào)
*/
channel.basicConsume(Producer.FANOUT_QUEUE_1, true, consumer);
}
}
comsumer2
package com.itheima.rabbitmq.ps;
import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer2 {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtil.getConnection();
// 創(chuàng)建頻道
Channel channel = connection.createChannel();
//聲明交換機
channel.exchangeDeclare(Producer.FANOUT_EXCHAGE, BuiltinExchangeType.FANOUT);
// 聲明(創(chuàng)建)隊列
/**
* 參數(shù)1:隊列名稱
* 參數(shù)2:是否定義持久化隊列
* 參數(shù)3:是否獨占本次連接
* 參數(shù)4:是否在不使用的時候自動刪除隊列
* 參數(shù)5:隊列其它參數(shù)
*/
channel.queueDeclare(Producer.FANOUT_QUEUE_2, true, false, false, null);
//隊列綁定交換機
channel.queueBind(Producer.FANOUT_QUEUE_2, Producer.FANOUT_EXCHAGE, "");
//創(chuàng)建消費者;并設(shè)置消息處理
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
/**
* consumerTag 消息者標簽,在channel.basicConsume時候可以指定
* envelope 消息包的內(nèi)容,可從中獲取消息id,消息routingkey,交換機,消息和重傳標志(收到消息失敗后是否需要重新發(fā)送)
* properties 屬性信息
* body 消息
*/
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key為:" + envelope.getRoutingKey());
//交換機
System.out.println("交換機為:" + envelope.getExchange());
//消息id
System.out.println("消息id為:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消費者2-接收到的消息為:" + new String(body, "utf-8"));
}
};
//監(jiān)聽消息
/**
* 參數(shù)1:隊列名稱
* 參數(shù)2:是否自動確認,設(shè)置為true為表示消息接收到自動向mq回復(fù)接收到了,mq接收到回復(fù)會刪除消息,設(shè)置為false則需要手動確認
* 參數(shù)3:消息接收到后回調(diào)
*/
channel.basicConsume(Producer.FANOUT_QUEUE_2, true, consumer);
}
}
啟動所有消費者,然后使用生產(chǎn)者發(fā)送消息;在每個消費者對應(yīng)的控制臺可以查看到生產(chǎn)者發(fā)送的所有消息;到達廣播的效果。
4.3.4. 小結(jié)
交換機需要與隊列進行綁定,綁定之后;一個消息可以被多個消費者都收到。
發(fā)布訂閱模式與工作隊列模式的區(qū)別
1、工作隊列模式不用定義交換機,而發(fā)布/訂閱模式需要定義交換機。
2、發(fā)布/訂閱模式的生產(chǎn)方是面向交換機發(fā)送消息,工作隊列模式的生產(chǎn)方是面向隊列發(fā)送消息(底層使用默認交換機)。
3、發(fā)布/訂閱模式需要設(shè)置隊列和交換機的綁定,工作隊列模式不需要設(shè)置,實際上工作隊列模式會將隊列綁 定到默認的交換機 。
4.4. Routing路由模式
4.4.1. 模式說明
路由模式特點:
隊列與交換機的綁定,不能是任意綁定了,而是要指定一個
RoutingKey
(路由key)消息的發(fā)送方在 向 Exchange發(fā)送消息時,也必須指定消息的
RoutingKey
。Exchange不再把消息交給每一個綁定的隊列,而是根據(jù)消息的
Routing Key
進行判斷,只有隊列的Routingkey
與消息的Routing key
完全一致,才會接收到消息
P:生產(chǎn)者,向Exchange發(fā)送消息,發(fā)送消息時,會指定一個routing key。
X:Exchange(交換機),接收生產(chǎn)者的消息,然后把消息遞交給 與routing key完全匹配的隊列
C1:消費者,其所在隊列指定了需要routing key 為 error 的消息
C2:消費者,其所在隊列指定了需要routing key 為 info、error、warning 的消息
代碼
在編碼上與 Publish/Subscribe發(fā)布與訂閱模式
的區(qū)別是交換機的類型為:Direct,還有隊列綁定交換機的時候需要指定routing key。
package com.itheima.rabbitmq.routing;
import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
/**
* 路由模式的交換機類型為:direct
*/
public class Producer {
//交換機名稱
static final String DIRECT_EXCHAGE = "direct_exchange";
//隊列名稱
static final String DIRECT_QUEUE_INSERT = "direct_queue_insert";
//隊列名稱
static final String DIRECT_QUEUE_UPDATE = "direct_queue_update";
public static void main(String[] args) throws Exception {
//創(chuàng)建連接
Connection connection = ConnectionUtil.getConnection();
// 創(chuàng)建頻道
Channel channel = connection.createChannel();
/**
* 聲明交換機
* 參數(shù)1:交換機名稱
* 參數(shù)2:交換機類型,fanout、topic、direct、headers
*/
channel.exchangeDeclare(DIRECT_EXCHAGE, BuiltinExchangeType.DIRECT);
// 聲明(創(chuàng)建)隊列
/**
* 參數(shù)1:隊列名稱
* 參數(shù)2:是否定義持久化隊列
* 參數(shù)3:是否獨占本次連接
* 參數(shù)4:是否在不使用的時候自動刪除隊列
* 參數(shù)5:隊列其它參數(shù)
*/
channel.queueDeclare(DIRECT_QUEUE_INSERT, true, false, false, null);
channel.queueDeclare(DIRECT_QUEUE_UPDATE, true, false, false, null);
//隊列綁定交換機
channel.queueBind(DIRECT_QUEUE_INSERT, DIRECT_EXCHAGE, "insert");
channel.queueBind(DIRECT_QUEUE_UPDATE, DIRECT_EXCHAGE, "update");
// 發(fā)送信息
String message = "新增了商品。路由模式;routing key 為 insert " ;
/**
* 參數(shù)1:交換機名稱,如果沒有指定則使用默認Default Exchage
* 參數(shù)2:路由key,簡單模式可以傳遞隊列名稱
* 參數(shù)3:消息其它屬性
* 參數(shù)4:消息內(nèi)容
*/
channel.basicPublish(DIRECT_EXCHAGE, "insert", null, message.getBytes());
System.out.println("已發(fā)送消息:" + message);
// 發(fā)送信息
message = "修改了商品。路由模式;routing key 為 update" ;
/**
* 參數(shù)1:交換機名稱,如果沒有指定則使用默認Default Exchage
* 參數(shù)2:路由key,簡單模式可以傳遞隊列名稱
* 參數(shù)3:消息其它屬性
* 參數(shù)4:消息內(nèi)容
*/
channel.basicPublish(DIRECT_EXCHAGE, "update", null, message.getBytes());
System.out.println("已發(fā)送消息:" + message);
// 關(guān)閉資源
channel.close();
connection.close();
}
}
package com.itheima.rabbitmq.routing;
import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer1 {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtil.getConnection();
// 創(chuàng)建頻道
Channel channel = connection.createChannel();
//聲明交換機
channel.exchangeDeclare(Producer.DIRECT_EXCHAGE, BuiltinExchangeType.DIRECT);
// 聲明(創(chuàng)建)隊列
/**
* 參數(shù)1:隊列名稱
* 參數(shù)2:是否定義持久化隊列
* 參數(shù)3:是否獨占本次連接
* 參數(shù)4:是否在不使用的時候自動刪除隊列
* 參數(shù)5:隊列其它參數(shù)
*/
channel.queueDeclare(Producer.DIRECT_QUEUE_INSERT, true, false, false, null);
//隊列綁定交換機
channel.queueBind(Producer.DIRECT_QUEUE_INSERT, Producer.DIRECT_EXCHAGE, "insert");
//創(chuàng)建消費者;并設(shè)置消息處理
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
/**
* consumerTag 消息者標簽,在channel.basicConsume時候可以指定
* envelope 消息包的內(nèi)容,可從中獲取消息id,消息routingkey,交換機,消息和重傳標志(收到消息失敗后是否需要重新發(fā)送)
* properties 屬性信息
* body 消息
*/
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key為:" + envelope.getRoutingKey());
//交換機
System.out.println("交換機為:" + envelope.getExchange());
//消息id
System.out.println("消息id為:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消費者1-接收到的消息為:" + new String(body, "utf-8"));
}
};
//監(jiān)聽消息
/**
* 參數(shù)1:隊列名稱
* 參數(shù)2:是否自動確認,設(shè)置為true為表示消息接收到自動向mq回復(fù)接收到了,mq接收到回復(fù)會刪除消息,設(shè)置為false則需要手動確認
* 參數(shù)3:消息接收到后回調(diào)
*/
channel.basicConsume(Producer.DIRECT_QUEUE_INSERT, true, consumer);
}
}
package com.itheima.rabbitmq.routing;
import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer2 {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtil.getConnection();
// 創(chuàng)建頻道
Channel channel = connection.createChannel();
//聲明交換機
channel.exchangeDeclare(Producer.DIRECT_EXCHAGE, BuiltinExchangeType.DIRECT);
// 聲明(創(chuàng)建)隊列
/**
* 參數(shù)1:隊列名稱
* 參數(shù)2:是否定義持久化隊列
* 參數(shù)3:是否獨占本次連接
* 參數(shù)4:是否在不使用的時候自動刪除隊列
* 參數(shù)5:隊列其它參數(shù)
*/
channel.queueDeclare(Producer.DIRECT_QUEUE_UPDATE, true, false, false, null);
//隊列綁定交換機
channel.queueBind(Producer.DIRECT_QUEUE_UPDATE, Producer.DIRECT_EXCHAGE, "update");
//創(chuàng)建消費者;并設(shè)置消息處理
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
/**
* consumerTag 消息者標簽,在channel.basicConsume時候可以指定
* envelope 消息包的內(nèi)容,可從中獲取消息id,消息routingkey,交換機,消息和重傳標志(收到消息失敗后是否需要重新發(fā)送)
* properties 屬性信息
* body 消息
*/
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key為:" + envelope.getRoutingKey());
//交換機
System.out.println("交換機為:" + envelope.getExchange());
//消息id
System.out.println("消息id為:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消費者2-接收到的消息為:" + new String(body, "utf-8"));
}
};
//監(jiān)聽消息
/**
* 參數(shù)1:隊列名稱
* 參數(shù)2:是否自動確認,設(shè)置為true為表示消息接收到自動向mq回復(fù)接收到了,mq接收到回復(fù)會刪除消息,設(shè)置為false則需要手動確認
* 參數(shù)3:消息接收到后回調(diào)
*/
channel.basicConsume(Producer.DIRECT_QUEUE_UPDATE, true, consumer);
}
}
測試
啟動所有消費者,然后使用生產(chǎn)者發(fā)送消息;在消費者對應(yīng)的控制臺可以查看到生產(chǎn)者發(fā)送對應(yīng)routing key對應(yīng)隊列的消息;到達按照需要接收的效果。
在執(zhí)行完測試代碼后,其實到RabbitMQ的管理后臺找到Exchanges
選項卡,點擊 direct_exchange
的交換機,可以查看到如下的綁定:
小結(jié)
Routing模式要求隊列在綁定交換機時要指定routing key,消息會轉(zhuǎn)發(fā)到符合routing key的隊列。
Topics通配符模式
模式說明
Topic
類型與Direct
相比,都是可以根據(jù)RoutingKey
把消息路由到不同的隊列。只不過Topic
類型Exchange
可以讓隊列在綁定Routing key
的時候使用通配符!
Routingkey
一般都是有一個或多個單詞組成,多個單詞之間以”.”分割,例如: item.insert
通配符規(guī)則:
#
:匹配一個或多個詞
*
:匹配不多不少恰好1個詞
舉例:
item.#
:能夠匹配item.insert.abc
或者 item.insert
item.*
:只能匹配item.insert
圖解:
- 紅色Queue:綁定的是
usa.#
,因此凡是以usa.
開頭的routing key
都會被匹配到 - 黃色Queue:綁定的是
#.news
,因此凡是以.news
結(jié)尾的routing key
都會被匹配
代碼
1)生產(chǎn)者
使用topic類型的Exchange,發(fā)送消息的routing key有3種: item.insert
、item.update
、item.delete
:
package com.itheima.rabbitmq.topic;
import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
/**
* 通配符Topic的交換機類型為:topic
*/
public class Producer {
//交換機名稱
static final String TOPIC_EXCHAGE = "topic_exchange";
//隊列名稱
static final String TOPIC_QUEUE_1 = "topic_queue_1";
//隊列名稱
static final String TOPIC_QUEUE_2 = "topic_queue_2";
public static void main(String[] args) throws Exception {
//創(chuàng)建連接
Connection connection = ConnectionUtil.getConnection();
// 創(chuàng)建頻道
Channel channel = connection.createChannel();
/**
* 聲明交換機
* 參數(shù)1:交換機名稱
* 參數(shù)2:交換機類型,fanout、topic、topic、headers
*/
channel.exchangeDeclare(TOPIC_EXCHAGE, BuiltinExchangeType.TOPIC);
// 發(fā)送信息
String message = "新增了商品。Topic模式;routing key 為 item.insert " ;
channel.basicPublish(TOPIC_EXCHAGE, "item.insert", null, message.getBytes());
System.out.println("已發(fā)送消息:" + message);
// 發(fā)送信息
message = "修改了商品。Topic模式;routing key 為 item.update" ;
channel.basicPublish(TOPIC_EXCHAGE, "item.update", null, message.getBytes());
System.out.println("已發(fā)送消息:" + message);
// 發(fā)送信息
message = "刪除了商品。Topic模式;routing key 為 item.delete" ;
channel.basicPublish(TOPIC_EXCHAGE, "item.delete", null, message.getBytes());
System.out.println("已發(fā)送消息:" + message);
// 關(guān)閉資源
channel.close();
connection.close();
}
}
消費者1
接收兩種類型的消息:更新商品和刪除商品
package com.itheima.rabbitmq.topic;
import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer1 {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtil.getConnection();
// 創(chuàng)建頻道
Channel channel = connection.createChannel();
//聲明交換機
channel.exchangeDeclare(Producer.TOPIC_EXCHAGE, BuiltinExchangeType.TOPIC);
// 聲明(創(chuàng)建)隊列
/**
* 參數(shù)1:隊列名稱
* 參數(shù)2:是否定義持久化隊列
* 參數(shù)3:是否獨占本次連接
* 參數(shù)4:是否在不使用的時候自動刪除隊列
* 參數(shù)5:隊列其它參數(shù)
*/
channel.queueDeclare(Producer.TOPIC_QUEUE_1, true, false, false, null);
//隊列綁定交換機
channel.queueBind(Producer.TOPIC_QUEUE_1, Producer.TOPIC_EXCHAGE, "item.update");
channel.queueBind(Producer.TOPIC_QUEUE_1, Producer.TOPIC_EXCHAGE, "item.delete");
//創(chuàng)建消費者;并設(shè)置消息處理
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
/**
* consumerTag 消息者標簽,在channel.basicConsume時候可以指定
* envelope 消息包的內(nèi)容,可從中獲取消息id,消息routingkey,交換機,消息和重傳標志(收到消息失敗后是否需要重新發(fā)送)
* properties 屬性信息
* body 消息
*/
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key為:" + envelope.getRoutingKey());
//交換機
System.out.println("交換機為:" + envelope.getExchange());
//消息id
System.out.println("消息id為:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消費者1-接收到的消息為:" + new String(body, "utf-8"));
}
};
//監(jiān)聽消息
/**
* 參數(shù)1:隊列名稱
* 參數(shù)2:是否自動確認,設(shè)置為true為表示消息接收到自動向mq回復(fù)接收到了,mq接收到回復(fù)會刪除消息,設(shè)置為false則需要手動確認
* 參數(shù)3:消息接收到后回調(diào)
*/
channel.basicConsume(Producer.TOPIC_QUEUE_1, true, consumer);
}
}
消費者2
接收所有類型的消息:新增商品,更新商品和刪除商品。
package com.itheima.rabbitmq.topic;
import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer2 {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtil.getConnection();
// 創(chuàng)建頻道
Channel channel = connection.createChannel();
//聲明交換機
channel.exchangeDeclare(Producer.TOPIC_EXCHAGE, BuiltinExchangeType.TOPIC);
// 聲明(創(chuàng)建)隊列
/**
* 參數(shù)1:隊列名稱
* 參數(shù)2:是否定義持久化隊列
* 參數(shù)3:是否獨占本次連接
* 參數(shù)4:是否在不使用的時候自動刪除隊列
* 參數(shù)5:隊列其它參數(shù)
*/
channel.queueDeclare(Producer.TOPIC_QUEUE_2, true, false, false, null);
//隊列綁定交換機
channel.queueBind(Producer.TOPIC_QUEUE_2, Producer.TOPIC_EXCHAGE, "item.*");
//創(chuàng)建消費者;并設(shè)置消息處理
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
/**
* consumerTag 消息者標簽,在channel.basicConsume時候可以指定
* envelope 消息包的內(nèi)容,可從中獲取消息id,消息routingkey,交換機,消息和重傳標志(收到消息失敗后是否需要重新發(fā)送)
* properties 屬性信息
* body 消息
*/
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key為:" + envelope.getRoutingKey());
//交換機
System.out.println("交換機為:" + envelope.getExchange());
//消息id
System.out.println("消息id為:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消費者2-接收到的消息為:" + new String(body, "utf-8"));
}
};
//監(jiān)聽消息
/**
* 參數(shù)1:隊列名稱
* 參數(shù)2:是否自動確認,設(shè)置為true為表示消息接收到自動向mq回復(fù)接收到了,mq接收到回復(fù)會刪除消息,設(shè)置為false則需要手動確認
* 參數(shù)3:消息接收到后回調(diào)
*/
channel.basicConsume(Producer.TOPIC_QUEUE_2, true, consumer);
}
}
測試
啟動所有消費者,然后使用生產(chǎn)者發(fā)送消息;在消費者對應(yīng)的控制臺可以查看到生產(chǎn)者發(fā)送對應(yīng)routing key對應(yīng)隊列的消息;到達按照需要接收的效果;并且這些routing key可以使用通配符。
在執(zhí)行完測試代碼后,其實到RabbitMQ的管理后臺找到Exchanges
選項卡,點擊 topic_exchange
的交換機,可以查看到如下的綁定:
小結(jié)
Topic主題模式可以實現(xiàn) Publish/Subscribe發(fā)布與訂閱模式
和 Routing路由模式
的功能;只是Topic在配置routing key 的時候可以使用通配符,顯得更加靈活。
模式總結(jié)
RabbitMQ工作模式:
1、簡單模式 HelloWorld
一個生產(chǎn)者、一個消費者,不需要設(shè)置交換機(使用默認的交換機)
2、工作隊列模式 Work Queue
一個生產(chǎn)者、多個消費者(競爭關(guān)系),不需要設(shè)置交換機(使用默認的交換機)
3、發(fā)布訂閱模式 Publish/subscribe
需要設(shè)置類型為fanout的交換機,并且交換機和隊列進行綁定,當發(fā)送消息到交換機后,交換機會將消息發(fā)送到綁定的隊列
4、路由模式 Routing
需要設(shè)置類型為direct的交換機,交換機和隊列進行綁定,并且指定routing key,當發(fā)送消息到交換機后,交換機會根據(jù)routing key將消息發(fā)送到對應(yīng)的隊列
5、通配符模式 Topic
需要設(shè)置類型為topic的交換機,交換機和隊列進行綁定,并且指定通配符方式的routing key,當發(fā)送消息到交換機后,交換機會根據(jù)routing key將消息發(fā)送到對應(yīng)的隊列