rabbitmq發佈訂閱
如果覺得還可以 記得關注一下公衆號哦!一起交流學習!
一、發佈訂閱模式
還記得我們上一個文章是如何發佈消息的嗎?
回顧一下以前是如何發送消息的:
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
對的,以前我們發送消息是直接由生產者將消息發送到隊列,可是這種方式官方是不推薦的!
RabbitMQ消息傳遞模型中的核心思想是生產者從不將任何消息直接發送到隊列。實際上,生產者經常甚至根本不知道是否將消息傳遞到任何隊列。
相反,生產者只能將消息發送到交換機。交流是一件非常簡單的事情。一方面,它接收來自生產者的消息,另一方面,將它們推入隊列。交易所必須確切知道如何處理收到的消息。是否應將其附加到特定隊列?是否應該將其附加到許多隊列中?還是應該丟棄它。規則由交換類型定義 。
你可以將交換機想象成一個分發器更好容易理解,**消息生產者你可以理解爲皇帝,他所下發的命令都由聖旨傳遞,皇帝當然不可能親自去送聖旨,所以這個工作由太監來承擔,這裏的太監就是交換機,由太監根據聖旨類型送到文武百官手裏,這裏文武百官也就是消費者。**大概看一下流程圖:
其中 X 就是交換機
交換機類型大概有:
- direct:
直連交換機
根據RouteKey轉發到隊列
- 任何發送到Direct Exchange的消息都會被轉發到指定RouteKey中指定的隊列Queue;
- 生產者生產消息的時候需要執行Routing Key路由鍵;
- 隊列綁定交換機的時候需要指定Binding Key,只有路由鍵與綁定鍵相同的話,才能將消息發送到綁定這個隊列的消費者;
- 如果vhost中不存在RouteKey中指定的隊列名,則該消息會被丟棄;
- topic:
通配符交換機
,滿足Route Key與Binding Key模糊匹配
- 任何發送到Topic Exchange的消息都會被轉發到所有滿足Route Key與Binding Key模糊匹配的隊列Queue上;
- 生產者發送消息的時候需要指定Route Key,同時綁定Exchange與Queue的時候也需要指定Binding Key;
- #” 表示0個或多個關鍵字,“*”表示匹配一個關鍵字;
- 如果Exchange沒有發現能夠與RouteKey模糊匹配的隊列Queue,則會拋棄此消息;
- 如果Binding中的Routing key *,#都沒有,則路由鍵跟綁定鍵相等的時候才轉發消息,類似Direct Exchange;如果Binding中的Routing key爲#或者#.#,則全部轉發,類似Fanout Exchange;
- fanout:
廣播式交換機
,所有發送到Fanout Exchange交換機上的消息,都會被髮送到綁定到該交換機上面的所有隊列上,這樣綁定到這些隊列的消費者就可以接收到該消息。header模式在實際使用中較少,本文只對前三種模式進行比較。
性能排序:fanout >> direct >> topic。比例大約爲11:10:6
我們本章專題會着重介紹fanout
類型的交換機!
生產者指定通道交換機類型
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
生產者不需要創建隊列,只需要創建交換機,並且指明該生產者對應的交換機即可,隊列的創建由消費者創建,所以發送消息的時候
channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes());
消費者需要創建隊列,並且綁定到交換機
//聲明隊列 channel.queueDeclare(QUEUE_NAME,false,false,false,null); //綁定給交換機 channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
完整代碼
生產者代碼
package com.ps;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.util.MqConnection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author huangfu
* 隊列 消息生產者
* 發佈 訂閱模式
*/
public class PSProducer {
private static String EXCHANGE_NAME = "ps";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
Connection connection = MqConnection.getConnection();
Channel channel = connection.createChannel();
/**
* 聲明交換機
* fanout 不處理路由,分發給所有隊列
* direct 處理路由 發送的時候需要發sing一個路由key
*/
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
String msg = "醉臥沙場君莫笑";
/**
* 第二各參數
* 匿名轉發,路由key
*/
channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes());
channel.close();
connection.close();
}
}
消費者1
package com.ps;
import com.rabbitmq.client.*;
import com.util.MqConnection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author Administrator
*/
public class PsCoummer {
private static final String QUEUE_NAME = "ps";
private static final String EXCHANGE_NAME = "ps";
public static void main(String[] args) throws IOException, TimeoutException {
//獲取連接
Connection connection = MqConnection.getConnection();
//創建頻道
final Channel channel = connection.createChannel();
//聲明隊列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
/**
* 告訴消費者每次只發一個給消費者
* 必須消費者發送確認消息之後我纔會發送下一條
*/
channel.basicQos(1);
//綁定給交換機
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
//定義一個消費者
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body,"UTF-8"));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("[1] done");
//發送回執
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
/**
* 第二個參數
* true:自動確認
* 一旦mq將消息分發給消費者 就會從內存中刪除,會出現消息丟失
* false:手動確認(默認)
* 如果消費者掛掉,我將此消息發送給其他消費者
* 支持消息應答,當消費者處理完成後發送給生產者回執,刪除消息
*
*
* 當消息隊列宕了 內存裏的數據依舊會丟失,此時需要將數據持久化
*/
channel.basicConsume(QUEUE_NAME,false,consumer);
}
}
消費者2
package com.ps;
import com.rabbitmq.client.*;
import com.util.MqConnection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author Administrator
*/
public class PsCoummer2 {
private static final String QUEUE_NAME = "ps2";
private static final String EXCHANGE_NAME = "ps";
public static void main(String[] args) throws IOException, TimeoutException {
//獲取連接
Connection connection = MqConnection.getConnection();
//創建頻道
final Channel channel = connection.createChannel();
//聲明隊列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
/**
* 告訴消費者每次只發一個給消費者
* 必須消費者發送確認消息之後我纔會發送下一條
*/
channel.basicQos(1);
//綁定給交換機
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
//定義一個消費者
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body,"UTF-8"));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("[2] done");
//發送回執
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
/**
* 第二個參數
* true:自動確認
* 一旦mq將消息分發給消費者 就會從內存中刪除,會出現消息丟失
* false:手動確認(默認)
* 如果消費者掛掉,我將此消息發送給其他消費者
* 支持消息應答,當消費者處理完成後發送給生產者回執,刪除消息
*
*
* 當消息隊列宕了 內存裏的數據依舊會丟失,此時需要將數據持久化
*/
channel.basicConsume(QUEUE_NAME,false,consumer);
}
}
完成流程圖
二、臨時隊列
我們創建隊列的方式一般是這樣:channel.queueDeclare(QUEUE_NAME,true,false,false,null);
,但是當我們不對全部的消息都感興趣,而只對一部分消息感興趣的情況下,獲取你應該瞭解一個概念:臨時隊列
爲了實現這個概念,我們應該去了解兩件事來實現這個臨時隊列
- 無論還說呢麼時候我們連接隊列的時候都需要一個新的隊列!所以我們應該創建一個有隨機名稱的隊列!
- 一旦斷開連接,隊列將自動刪除!
當然,rabbitmq的客戶端已經爲我們實現這個,納悶創建一個臨時隊列應該怎麼來做呢?
String queueName = channel.queueDeclare().getQueue();
- 這麼創建,他會創建一個臨時隊列,並且返回隊列的名字!
- 在Java客戶端中,當我們不向queueDeclare()提供任何參數時,我們將 使用生成的名稱創建一個非持久的,排他的,自動刪除的隊列