RabbitMQ的交換機exchange介紹

RabbitMQ的Exchange(交換器)分爲四類:
direct(默認)
headers
fanout
topic

其中headers交換器允許你匹配AMQP消息的header而非路由鍵,除此之外headers交換器和direct交換器完全一致,但性能卻很差,幾乎用不到

一、direct交換機
direct爲默認的交換器類型,也非常的簡單,如果路由鍵匹配的話,消息就投遞到相應的隊列,如圖:
在這裏插入圖片描述
使用代碼:channel.basicPublish("", QueueName, null, message)推送direct交換器消息到對於的隊列,空字符爲默認的direct交換器,用隊列名稱當做路由鍵。

代碼示例:
發送端

Connection conn = connectionFactoryUtil.GetRabbitConnection();
Channel channel = conn.createChannel();
// 聲明隊列【參數說明:參數一:隊列名稱,參數二:是否持久化;參數三:是否獨佔模式;參數四:消費者斷開連接時是否刪除隊列;參數五:消息其他參數】
channel.queueDeclare(config.QueueName, false, false, false, null);
String message = String.format("當前時間:%s", new Date().getTime());
// 推送內容【參數說明:參數一:交換機名稱;參數二:隊列名稱,參數三:消息的其他屬性-路由的headers信息;參數四:消息主體】
channel.basicPublish("", config.QueueName, null, message.getBytes("UTF-8"));

接收端,
持續接收消息:

Connection conn = connectionFactoryUtil.GetRabbitConnection();
Channel channel = conn.createChannel();
// 聲明隊列【參數說明:參數一:隊列名稱,參數二:是否持久化;參數三:是否獨佔模式;參數四:消費者斷開連接時是否刪除隊列;參數五:消息其他參數】
channel.queueDeclare(config.QueueName, false, false, false, null);
Consumer defaultConsumer = new DefaultConsumer(channel) {
	@Override
	public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
			byte[] body) throws IOException {
		String message = new String(body, "utf-8"); // 消息正文
		System.out.println("收到消息 => " + message);
		channel.basicAck(envelope.getDeliveryTag(), false); // 手動確認消息【參數說明:參數一:該消息的index;參數二:是否批量應答,true批量確認小於當前id的消息】
	}
};
channel.basicConsume(config.QueueName, false, "", defaultConsumer);

接收端,獲取單條消息

Connection conn = connectionFactoryUtil.GetRabbitConnection();
Channel channel = conn.createChannel();
channel.queueDeclare(config.QueueName, false, false, false, null);
GetResponse resp = channel.basicGet(config.QueueName, false);
String message = new String(resp.getBody(), "UTF-8");
channel.basicAck(resp.getEnvelope().getDeliveryTag(), false); // 消息確認

持續消息獲取使用:basic.consume;單個消息獲取使用:basic.get。

注意:不能使用for循環單個消息消費來替代持續消息消費,因爲這樣性能很低;

幾個特性:
1、消息的發後既忘特性
發後既忘模式是指接受者不知道消息的來源,如果想要指定消息的發送者,需要包含在發送內容裏面,這點就像我們在信件裏面註明自己的姓名一樣,只有這樣才能知道發送者是誰。

2、消息確認
消息接收到之後必須使用channel.basicAck()方法手動確認(非自動確認刪除模式下)

消息收到未確認會怎麼樣?

如果應用程序接收了消息,因爲bug忘記確認接收的話,消息在隊列的狀態會從“Ready”變爲“Unacked”,如圖:
在這裏插入圖片描述
如果消息收到卻未確認,Rabbit將不會再給這個應用程序發送更多的消息了,這是因爲Rabbit認爲你沒有準備好接收下一條消息。

此條消息會一直保持Unacked的狀態,直到你確認了消息,或者斷開與Rabbit的連接,Rabbit會自動把消息改完Ready狀態,分發給其他訂閱者。

3、消息確認demo

Connection conn = connectionFactoryUtil.GetRabbitConnection();
Channel channel = conn.createChannel();
channel.queueDeclare(config.QueueName, false, false, false, null);
GetResponse resp = channel.basicGet(config.QueueName, false);
String message = new String(resp.getBody(), "UTF-8");
channel.basicAck(resp.getEnvelope().getDeliveryTag(), false);

channel.basicAck(long deliveryTag, boolean multiple)爲消息確認,參數1:消息的id;參數2:是否批量應答,true批量確認消息id的消息。

4、消息拒絕
消息在確認之前,可以有兩個選擇:
選擇1:斷開與Rabbit的連接,這樣Rabbit會重新把消息分派給另一個消費者;
選擇2:拒絕Rabbit發送的消息使用channel.basicReject(long deliveryTag, boolean requeue),
參數1:消息的id;
參數2:處理消息的方式,如果是true,Rabbib會重新分配這個消息給其他訂閱者,如果設置成false的話,Rabbit會把消息發送到一個特殊的“死信”隊列,用來存放被拒絕而不重新放入隊列的消息。

消息拒絕Demo:

Connection conn = connectionFactoryUtil.GetRabbitConnection();
Channel channel = conn.createChannel();
channel.queueDeclare(config.QueueName, false, false, false, null);
GetResponse resp = channel.basicGet(config.QueueName, false);
String message = new String(resp.getBody(), "UTF-8");
channel.basicReject(resp.getEnvelope().getDeliveryTag(), true); //消息拒絕

二、fanout交換器——發佈/訂閱模式
fanout是一種發佈/訂閱模式的交換器,當你發送一條消息的時候,交換器會把消息廣播到所有附加到這個交換器的隊列上。
在這裏插入圖片描述
比如用戶上傳了自己的頭像,這個時候圖片需要清除緩存,同時用戶應該得到積分獎勵,你可以把這兩個隊列綁定到圖片上傳的交換器上,這樣當有第三個、第四個上傳完圖片需要處理的需求的時候,原來的代碼可以不變,只需要添加一個得到積分獎勵的訂閱消息即可,這樣發送方和消費者的代碼完全解耦,並可以輕而易舉的添加新功能了。

和direct交換器不同,我們在發送消息的時候新增channel.exchangeDeclare(ExchangeName, “fanout”),這行代碼聲明fanout交換器。

發送端:

final String ExchangeName = "fanoutec"; // 交換器名稱
Connection conn = connectionFactoryUtil.GetRabbitConnection();
Channel channel = conn.createChannel();
channel.exchangeDeclare(ExchangeName, "fanout"); // 聲明fanout交換器
String message = "時間:" + new Date().getTime();
channel.basicPublish(ExchangeName, "", null, message.getBytes("UTF-8"));

接受消息不同於direct,我們需要聲明fanout路由器,並使用默認的隊列綁定到fanout交換器上。
接收端

Connection conn = connectionFactoryUtil.GetRabbitConnection();
Channel channel = conn.createChannel();
channel.exchangeDeclare(ExchangeName, "fanout"); // 聲明fanout交換器
String queueName = channel.queueDeclare().getQueue(); // 聲明隊列
channel.queueBind(queueName, ExchangeName, "");
Consumer consumer = new DefaultConsumer(channel) {
	@Override
	public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
			byte[] body) throws IOException {
		String message = new String(body, "UTF-8");
	}
};
channel.basicConsume(queueName, true, consumer);

fanout和direct的區別最多的在接收端,fanout需要綁定隊列到對應的交換器用於訂閱消息。

其中channel.queueDeclare().getQueue()爲隨機隊列,Rabbit會隨機生成隊列名稱,一旦消費者斷開連接,該隊列會自動刪除。

注意:對於fanout交換器來說routingKey(路由鍵)是無效的,這個參數是被忽略的。

三、topic交換器——匹配訂閱模式
topic交換器運行和fanout類似,但是可以更靈活的匹配自己想要訂閱的信息,這個時候routingKey路由鍵就排上用場了,使用路由鍵進行消息(規則)匹配。
在這裏插入圖片描述
假設我們現在有一個日誌系統,會把所有日誌級別的日誌發送到交換器,warning、log、error、fatal,但我們只想處理error以上的日誌,要怎麼處理?這就需要使用topic路由器了。

topic路由器的關鍵在於定義路由鍵,定義routingKey名稱不能超過255字節,使用“.”作爲分隔符,例如:com.mq.rabbit.error。

消費消息的時候routingKey可以使用下面字符匹配消息:

"*"匹配一個分段(用“.”分割)的內容;
"#"匹配0和多個字符;
例如發佈了一個“com.mq.rabbit.error”的消息:
能匹配上的路由鍵:

cn.mq.rabbit.*
cn.mq.rabbit.#
#.error
cn.mq.#

不能匹配上的路由鍵:

cn.mq.*
*.error
*
所以如果想要訂閱所有消息,可以使用“#”匹配。

注意:fanout、topic交換器是沒有歷史數據的,也就是說對於中途創建的隊列,獲取不到之前的消息。

發佈端:

String routingKey = "com.mq.rabbit.error";
Connection conn = connectionFactoryUtil.GetRabbitConnection();
Channel channel = conn.createChannel();
channel.exchangeDeclare(ExchangeName, "topic"); // 聲明topic交換器
String message = "時間:" + new Date().getTime();
channel.basicPublish(ExchangeName, routingKey, null, message.getBytes("UTF-8"));

接收端:

Connection conn = connectionFactoryUtil.GetRabbitConnection();
Channel channel = conn.createChannel();
channel.exchangeDeclare(ExchangeName, "topic"); // 聲明topic交換器
String queueName = channel.queueDeclare().getQueue(); // 聲明隊列
String routingKey = "#.error";
channel.queueBind(queueName, ExchangeName, routingKey);
Consumer consumer = new DefaultConsumer(channel) {
	@Override
	public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
			byte[] body) throws IOException {
		String message = new String(body, "UTF-8");
		System.out.println(routingKey + "|接收消息 => " + message);
	}
};
channel.basicConsume(queueName, true, consumer);

四、headers類型的交換機
在這裏插入圖片描述
1、消息分發規則:headers類型的交換機分發消息不依賴routingKey,是使用發送消息時basicProperties對象中的headers來匹配的。headers是一個鍵值對類型,發送者發送消息時將這些鍵值對放到basicProperties對象中的headers字段中,隊列綁定交換機時綁定一些鍵值對,當兩者匹配時,隊列就可以收到消息。匹配模式有兩種,在隊列綁定到交換機時用x-match來指定,all代表定義的多個鍵值對都要滿足,而any則代碼只要滿足一個就可以了。fanout,direct,topic exchange的routingKey都需要要字符串形式的,而headers exchange則沒有這個要求,因爲鍵值對的值可以是任何類型。

2、圖示說明:
消息1附帶的鍵值對與Q9綁定鍵值對的color匹配、speed不匹配,但是Q9的x-match設置爲any,因此只要有一項匹配消息1就可以被分發到Q9。消息1與Q10完全匹配,消息2與Q10部分匹配,由於Q10的x-match設置爲all,所以只能接受到消息1。

3、代碼示例

生產者示例:

//聲明轉發器和類型headers  
        channel.exchangeDeclare(EXCHANGE_NAME, ExchangeTypes.HEADERS,false,true,null);  
        String message = "消息1";  
        Map<String,Object> headers =  new Hashtable<String, Object>();  
        headers.put("aaa", "01234");  
        Builder properties = new BasicProperties.Builder();  
        properties.headers(headers);  
        // 指定消息發送到的轉發器,綁定鍵值對headers鍵值對  
        channel.basicPublish(EXCHANGE_NAME, "",properties.build(),message.getBytes());

消費者示例:

 //聲明轉發器和類型headers  
        channel.exchangeDeclare(EXCHANGE_NAME, ExchangeTypes.HEADERS,false,true,null);  
        channel.queueDeclare(QUEUE_NAME,false, false, true,null);  

        Map<String, Object> headers = new Hashtable<String, Object>();  
        headers.put("x-match", "any");//all any  
        headers.put("aaa", "01234");  
        headers.put("bbb", "56789");  
        // 爲轉發器指定隊列,設置binding 綁定header鍵值對  
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME,"", headers);  
        QueueingConsumer consumer = new QueueingConsumer(channel);  
        // 指定接收者,第二個參數爲自動應答,無需手動應答  
        channel.basicConsume(QUEUE_NAME, true, consumer);  
        while (true) {  
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();  
            String message = new String(delivery.getBody());  
            System.out.println(message);  
        } 

示例代碼API介紹:
(1) channel.exchangeDeclare(exchange, type, durable, autoDelete, arguments);
exchange:交換機名
type:交換機類型
durable:持久化標誌
autoDelete:自動刪除
arguments:擴展參數,具體如下表

(2)channel.queueBind(queue, exchange, routingKey, arguments);
queue:隊列名
exchange:交換機名
routingKey:選擇鍵(路由鍵)
arguments:headers類型交換機綁定時指定的鍵值對

欲瞭解更多內容,請關注下方公衆號:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章