1、 工作模式
RabbitMQ有以下幾種工作模式 :
1、Work queues
2、Publish/Subscribe
3、Routing
4、Topics
5、Header
6、RPC
1.1 Work queues(工作隊(duì)列)
work queues與入門程序相比,多了一個消費(fèi)端,兩個消費(fèi)端共同消費(fèi)同一個隊(duì)列中的消息。
應(yīng)用場景:對于 任務(wù)過重或任務(wù)較多情況使用工作隊(duì)列可以提高任務(wù)處理的速度。
測試:
1、使用入門程序,啟動多個消費(fèi)者。
2、生產(chǎn)者發(fā)送多個消息。
public class Producer01 {
private static final String QUEUE = "helloworld";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = null;
Channel channel = null;
try {
//創(chuàng)建連接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");//rabbitmq默認(rèn)虛擬機(jī)名稱為“/”,虛擬機(jī)相當(dāng)于一個獨(dú)立的mq服務(wù),一個mq有多個虛擬機(jī)
//創(chuàng)建與RabbitMQ服務(wù)的TCP連接
connection = connectionFactory.newConnection();
//創(chuàng)建與Exchange的通道,每個連接可以創(chuàng)建多個通道,每個通道代表一個會話任務(wù)
channel = connection.createChannel();
/**
* 聲明隊(duì)列,如果Rabbit中沒有此隊(duì)列將自動創(chuàng)建
* QUEUE:隊(duì)列名稱
* durable:是否持久化
* exclusive:隊(duì)列是否獨(dú)占此鏈接
* autoDelete:隊(duì)列不再使用時是否自動刪除此
* arguments:隊(duì)列參數(shù)
*/
channel.queueDeclare(QUEUE, true, false, false, null);
String message = "helloworld小明"+System.currentTimeMillis();
/**
* 消息發(fā)布方法
* exchange: Exchange的名稱,如果沒有指定,則使用Default Exchange
* routingKey:routingKey,消息的路由Key,是用于Exchange(交換機(jī))將消息轉(zhuǎn)發(fā)到指定的消息隊(duì)列
* props: 消息包含的屬性
* body: 消息體
* 這里沒有指定交換機(jī),消息將發(fā)送給默認(rèn)交換機(jī),每個隊(duì)列也會綁定那個默認(rèn)的交換機(jī),但是不能顯示綁定或解除綁定
* 默認(rèn)的交換機(jī),routingKey等于隊(duì)列名稱
*/
channel.basicPublish("", QUEUE, null, message.getBytes());
System.out.println("Send Message is:'" + message + "'");
}catch (Exception e){
e.printStackTrace();
}finally {
if(channel != null)
{
channel.close();
}
if(connection != null)
{
connection.close();
}
}
}
}
消費(fèi)者
public class Consumer01 {
private static final String QUEUE = "helloworld";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = null;
Channel channel = null;
//創(chuàng)建連接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");//rabbitmq默認(rèn)虛擬機(jī)名稱為“/”,虛擬機(jī)相當(dāng)于一個獨(dú)立的mq服務(wù),一個mq有多個虛擬機(jī)
//創(chuàng)建與RabbitMQ服務(wù)的TCP連接
connection = connectionFactory.newConnection();
//創(chuàng)建與Exchange的通道,每個連接可以創(chuàng)建多個通道,每個通道代表一個會話任務(wù)
channel = connection.createChannel();
/**
* 聲明隊(duì)列,如果Rabbit中沒有此隊(duì)列將自動創(chuàng)建
* QUEUE:隊(duì)列名稱
* durable:是否持久化
* exclusive:隊(duì)列是否獨(dú)占此鏈接
* autoDelete:隊(duì)列不再使用時是否自動刪除此
* arguments:隊(duì)列參數(shù)
*/
channel.queueDeclare(QUEUE, true, false, false, null);
/**
* 定義消費(fèi)方法
*/
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
/**
*
* @param consumerTag 消費(fèi)者的標(biāo)簽,在channel.basicConsume()去指定
* @param envelope 消息包的內(nèi)容,可從中獲取消息id,消息routingkey,交換機(jī),消息和重傳標(biāo)志(收到消息失敗后是否需要重新發(fā)送)
* @param properties properties
* @param body
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
//交換機(jī)
String exchange = envelope.getExchange();
//路由key
String routingKey = envelope.getRoutingKey();
//消息id
long deliveryTag = envelope.getDeliveryTag();
//消息內(nèi)容
String msg = new String(body,"UTF-8");
System.out.println("receive message.." + msg);
}
};
/**
*監(jiān)聽隊(duì)列String queue, boolean autoAck, Consumer callback
* queue 隊(duì)列名稱
* autoAck 是否自動回復(fù),設(shè)置為true為表示消息接收到自動向mq回復(fù)接收到了,mq接收到回復(fù)會刪除消息,設(shè)置為false則需要手動回復(fù)
* callback 消費(fèi)消息的方法,消費(fèi)者接收到消息后調(diào)用此方法
*/
channel.basicConsume(QUEUE,true,defaultConsumer);
}
}
結(jié)果:
1、一條消息只會被一個消費(fèi)者接收;
2、rabbit采用輪詢的方式將消息是平均發(fā)送給消費(fèi)者的;
3、消費(fèi)者在處理完某條消息后,才會收到下一條消息。
1.2 Publish/subscribe(發(fā)布訂閱)
1.2.1 工作模式
案例:
用戶通知,當(dāng)用戶充值成功或轉(zhuǎn)賬完成系統(tǒng)通知用戶,通知方式有短信、郵件多種方法
1、生產(chǎn)者
聲明Exchange_fanout_inform交換機(jī)。
聲明兩個隊(duì)列并且綁定到此交換機(jī),綁定時不需要指定routingkey
發(fā)送消息時不需要指定routingkey
public class Producer02_publish {
//隊(duì)列名稱
private static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
private static final String QUEUE_INFORM_SMS = "queue_inform_sms";
private static final String EXCHANGE_FANOUT_INFORM="exchange_fanout_inform";
public static void main(String[] args) {
//通過連接工廠創(chuàng)建新的連接和mq建立連接
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);//端口
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//設(shè)置虛擬機(jī),一個mq服務(wù)可以設(shè)置多個虛擬機(jī),每個虛擬機(jī)就相當(dāng)于一個獨(dú)立的mq
connectionFactory.setVirtualHost("/");
Connection connection = null;
Channel channel = null;
try {
//建立新連接
connection = connectionFactory.newConnection();
//創(chuàng)建會話通道,生產(chǎn)者和mq服務(wù)所有通信都在channel通道中完成
channel = connection.createChannel();
//聲明隊(duì)列,如果隊(duì)列在mq 中沒有則要創(chuàng)建
//參數(shù):String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
/**
* 參數(shù)明細(xì)
* 1、queue 隊(duì)列名稱
* 2、durable 是否持久化,如果持久化,mq重啟后隊(duì)列還在
* 3、exclusive 是否獨(dú)占連接,隊(duì)列只允許在該連接中訪問,如果connection連接關(guān)閉隊(duì)列則自動刪除,如果將此參數(shù)設(shè)置true可用于臨時隊(duì)列的創(chuàng)建
* 4、autoDelete 自動刪除,隊(duì)列不再使用時是否自動刪除此隊(duì)列,如果將此參數(shù)和exclusive參數(shù)設(shè)置為true就可以實(shí)現(xiàn)臨時隊(duì)列(隊(duì)列不用了就自動刪除)
* 5、arguments 參數(shù),可以設(shè)置一個隊(duì)列的擴(kuò)展參數(shù),比如:可設(shè)置存活時間
*/
channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null);
channel.queueDeclare(QUEUE_INFORM_SMS,true,false,false,null);
//聲明一個交換機(jī)
//參數(shù):String exchange, String type
/**
* 參數(shù)明細(xì):
* 1、交換機(jī)的名稱
* 2、交換機(jī)的類型
* fanout:對應(yīng)的rabbitmq的工作模式是 publish/subscribe
* direct:對應(yīng)的Routing 工作模式
* topic:對應(yīng)的Topics工作模式
* headers: 對應(yīng)的headers工作模式
*/
channel.exchangeDeclare(EXCHANGE_FANOUT_INFORM, BuiltinExchangeType.FANOUT);
//進(jìn)行交換機(jī)和隊(duì)列綁定
//參數(shù):String queue, String exchange, String routingKey
/**
* 參數(shù)明細(xì):
* 1、queue 隊(duì)列名稱
* 2、exchange 交換機(jī)名稱
* 3、routingKey 路由key,作用是交換機(jī)根據(jù)路由key的值將消息轉(zhuǎn)發(fā)到指定的隊(duì)列中,在發(fā)布訂閱模式中調(diào)協(xié)為空字符串
*/
channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_FANOUT_INFORM,"");
channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_FANOUT_INFORM,"");
//發(fā)送消息
//參數(shù):String exchange, String routingKey, BasicProperties props, byte[] body
/**
* 參數(shù)明細(xì):
* 1、exchange,交換機(jī),如果不指定將使用mq的默認(rèn)交換機(jī)(設(shè)置為"")
* 2、routingKey,路由key,交換機(jī)根據(jù)路由key來將消息轉(zhuǎn)發(fā)到指定的隊(duì)列,如果使用默認(rèn)交換機(jī),routingKey設(shè)置為隊(duì)列的名稱
* 3、props,消息的屬性
* 4、body,消息內(nèi)容
*/
for(int i=0;i<5;i++){
//消息內(nèi)容
String message = "send inform message to user";
channel.basicPublish(EXCHANGE_FANOUT_INFORM,"",null,message.getBytes());
System.out.println("send to mq "+message);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//關(guān)閉連接
//先關(guān)閉通道
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2、郵件發(fā)送消費(fèi)者
public class Consumer02_subscribe_sms {
//隊(duì)列名稱
private static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
private static final String QUEUE_INFORM_SMS = "queue_inform_sms";
private static final String EXCHANGE_FANOUT_INFORM="exchange_fanout_inform";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = null;
Channel channel = null;
//創(chuàng)建連接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");//rabbitmq默認(rèn)虛擬機(jī)名稱為“/”,虛擬機(jī)相當(dāng)于一個獨(dú)立的mq服務(wù),一個mq有多個虛擬機(jī)
//創(chuàng)建與RabbitMQ服務(wù)的TCP連接
connection = connectionFactory.newConnection();
//創(chuàng)建與Exchange的通道,每個連接可以創(chuàng)建多個通道,每個通道代表一個會話任務(wù)
channel = connection.createChannel();
/**
* String exchange, String type
* exchange:交換機(jī)名字
* type 交換機(jī)類型
*/
channel.exchangeDeclare(EXCHANGE_FANOUT_INFORM, BuiltinExchangeType.FANOUT);
/**
* 聲明隊(duì)列,如果Rabbit中沒有此隊(duì)列將自動創(chuàng)建
* QUEUE:隊(duì)列名稱
* durable:是否持久化
* exclusive:隊(duì)列是否獨(dú)占此鏈接
* autoDelete:隊(duì)列不再使用時是否自動刪除此
* arguments:隊(duì)列參數(shù)
*/
channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null);
/**
* 交換機(jī)和隊(duì)列綁定
* String queue, String exchange, String routingKey
* queue 隊(duì)列名稱
* exchange 交換機(jī)名稱
* routingKey 路由key
*/
channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_FANOUT_INFORM,"");
/**
* 定義消費(fèi)方法
*/
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
/**
*
* @param consumerTag 消費(fèi)者的標(biāo)簽,在channel.basicConsume()去指定
* @param envelope 消息包的內(nèi)容,可從中獲取消息id,消息routingkey,交換機(jī),消息和重傳標(biāo)志(收到消息失敗后是否需要重新發(fā)送)
* @param properties properties
* @param body
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//交換機(jī)
String exchange = envelope.getExchange();
//消息id,mq在channel中用來標(biāo)識消息的id,可用于確認(rèn)消息已接收
long deliveryTag = envelope.getDeliveryTag();
//消息內(nèi)容
String message= new String(body,"utf-8");
System.out.println("receive message:"+message);
}
};
/**
*監(jiān)聽隊(duì)列String queue, boolean autoAck, Consumer callback
* queue 隊(duì)列名稱
* autoAck 是否自動回復(fù),設(shè)置為true為表示消息接收到自動向mq回復(fù)接收到了,mq接收到回復(fù)會刪除消息,設(shè)置為false則需要手動回復(fù)
* callback 消費(fèi)消息的方法,消費(fèi)者接收到消息后調(diào)用此方法
*/
channel.basicConsume(QUEUE_INFORM_SMS,true,defaultConsumer);
}
}
按照上邊的代碼,編寫郵件通知的消費(fèi)代碼。
3、短信發(fā)送消費(fèi)者
參考上邊的郵件發(fā)送消費(fèi)者代碼編寫.
1.2.4 思考
1、publish/subscribe與work queues有什么區(qū)別。
區(qū)別:
1)work queues不用定義交換機(jī),而publish/subscribe需要定義交換機(jī)。
2)publish/subscribe的生產(chǎn)方是面向交換機(jī)發(fā)送消息,work queues的生產(chǎn)方是面向隊(duì)列發(fā)送消息(底層使用默認(rèn)交換機(jī))。
3)publish/subscribe需要設(shè)置隊(duì)列和交換機(jī)的綁定,work queues不需要設(shè)置,實(shí)質(zhì)上work queues會將隊(duì)列綁定到默認(rèn)的交換機(jī) 。
相同點(diǎn):
1、所以兩者實(shí)現(xiàn)的發(fā)布/訂閱的效果是一樣的,多個消費(fèi)端監(jiān)聽同一個隊(duì)列不會重復(fù)消費(fèi)消息。
2、實(shí)質(zhì)工作用什么 publish/subscribe還是work queues。
建議使用 publish/subscribe,發(fā)布訂閱模式比工作隊(duì)列模式更強(qiáng)大,并且發(fā)布訂閱模式可以指定自己專用的交換機(jī)。
1.3 Routing()
1.3.1 工作模式
路由模式:
1、每個消費(fèi)者監(jiān)聽自己的隊(duì)列,并且設(shè)置routingkey。
2、生產(chǎn)者將消息發(fā)給交換機(jī),由交換機(jī)根據(jù)routingkey來轉(zhuǎn)發(fā)消息到指定的隊(duì)列
1.3.2代碼
1、生產(chǎn)者
聲明exchange_routing_inform交換機(jī)。
聲明兩個隊(duì)列并且綁定到此交換機(jī),綁定時需要指定routingkey
發(fā)送消息時需要指定routingkey
public class Producer03_routing {
//隊(duì)列名稱
private static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
private static final String QUEUE_INFORM_SMS = "queue_inform_sms";
private static final String EXCHANGE_ROUTING_INFORM="exchange_routing_inform";
private static final String ROUTINGKEY_EMAIL="inform_email";
private static final String ROUTINGKEY_SMS="inform_sms";
public static void main(String[] args) {
//通過連接工廠創(chuàng)建新的連接和mq建立連接
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);//端口
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//設(shè)置虛擬機(jī),一個mq服務(wù)可以設(shè)置多個虛擬機(jī),每個虛擬機(jī)就相當(dāng)于一個獨(dú)立的mq
connectionFactory.setVirtualHost("/");
Connection connection = null;
Channel channel = null;
try {
//建立新連接
connection = connectionFactory.newConnection();
//創(chuàng)建會話通道,生產(chǎn)者和mq服務(wù)所有通信都在channel通道中完成
channel = connection.createChannel();
//聲明隊(duì)列,如果隊(duì)列在mq 中沒有則要創(chuàng)建
//參數(shù):String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
/**
* 參數(shù)明細(xì)
* 1、queue 隊(duì)列名稱
* 2、durable 是否持久化,如果持久化,mq重啟后隊(duì)列還在
* 3、exclusive 是否獨(dú)占連接,隊(duì)列只允許在該連接中訪問,如果connection連接關(guān)閉隊(duì)列則自動刪除,如果將此參數(shù)設(shè)置true可用于臨時隊(duì)列的創(chuàng)建
* 4、autoDelete 自動刪除,隊(duì)列不再使用時是否自動刪除此隊(duì)列,如果將此參數(shù)和exclusive參數(shù)設(shè)置為true就可以實(shí)現(xiàn)臨時隊(duì)列(隊(duì)列不用了就自動刪除)
* 5、arguments 參數(shù),可以設(shè)置一個隊(duì)列的擴(kuò)展參數(shù),比如:可設(shè)置存活時間
*/
channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null);
channel.queueDeclare(QUEUE_INFORM_SMS,true,false,false,null);
//聲明一個交換機(jī)
//參數(shù):String exchange, String type
/**
* 參數(shù)明細(xì):
* 1、交換機(jī)的名稱
* 2、交換機(jī)的類型
* fanout:對應(yīng)的rabbitmq的工作模式是 publish/subscribe
* direct:對應(yīng)的Routing 工作模式
* topic:對應(yīng)的Topics工作模式
* headers: 對應(yīng)的headers工作模式
*/
channel.exchangeDeclare(EXCHANGE_ROUTING_INFORM, BuiltinExchangeType.DIRECT);
//進(jìn)行交換機(jī)和隊(duì)列綁定
//參數(shù):String queue, String exchange, String routingKey
/**
* 參數(shù)明細(xì):
* 1、queue 隊(duì)列名稱
* 2、exchange 交換機(jī)名稱
* 3、routingKey 路由key,作用是交換機(jī)根據(jù)路由key的值將消息轉(zhuǎn)發(fā)到指定的隊(duì)列中,在發(fā)布訂閱模式中調(diào)協(xié)為空字符串
*/
channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_ROUTING_INFORM,ROUTINGKEY_EMAIL);
channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_ROUTING_INFORM,ROUTINGKEY_SMS);
//發(fā)送消息
//參數(shù):String exchange, String routingKey, BasicProperties props, byte[] body
/**
* 參數(shù)明細(xì):
* 1、exchange,交換機(jī),如果不指定將使用mq的默認(rèn)交換機(jī)(設(shè)置為"")
* 2、routingKey,路由key,交換機(jī)根據(jù)路由key來將消息轉(zhuǎn)發(fā)到指定的隊(duì)列,如果使用默認(rèn)交換機(jī),routingKey設(shè)置為隊(duì)列的名稱
* 3、props,消息的屬性
* 4、body,消息內(nèi)容
*/
for(int i=0;i<5;i++){
//發(fā)送消息的時候指定routingKey
String message = "send email inform message to user";
channel.basicPublish(EXCHANGE_ROUTING_INFORM,ROUTINGKEY_EMAIL,null,message.getBytes());
System.out.println("send to mq "+message);
}
for(int i=0;i<5;i++){
//發(fā)送消息的時候指定routingKey
String message = "send email inform message to user";
channel.basicPublish(EXCHANGE_ROUTING_INFORM,ROUTINGKEY_SMS,null,message.getBytes());
System.out.println("send to mq "+message);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//關(guān)閉連接
//先關(guān)閉通道
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2、郵件發(fā)送消費(fèi)者
public class Consumer03_routing_email {
//隊(duì)列名稱
private static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
private static final String EXCHANGE_ROUTING_INFORM="exchange_routing_inform";
private static final String ROUTINGKEY_EMAIL="inform_email";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = null;
Channel channel = null;
//創(chuàng)建連接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");//rabbitmq默認(rèn)虛擬機(jī)名稱為“/”,虛擬機(jī)相當(dāng)于一個獨(dú)立的mq服務(wù),一個mq有多個虛擬機(jī)
//創(chuàng)建與RabbitMQ服務(wù)的TCP連接
connection = connectionFactory.newConnection();
//創(chuàng)建與Exchange的通道,每個連接可以創(chuàng)建多個通道,每個通道代表一個會話任務(wù)
channel = connection.createChannel();
/**
* String exchange, String type
* exchange:交換機(jī)名字
* type 交換機(jī)類型
*/
channel.exchangeDeclare(EXCHANGE_ROUTING_INFORM, BuiltinExchangeType.DIRECT);
/**
* 聲明隊(duì)列,如果Rabbit中沒有此隊(duì)列將自動創(chuàng)建
* QUEUE:隊(duì)列名稱
* durable:是否持久化
* exclusive:隊(duì)列是否獨(dú)占此鏈接
* autoDelete:隊(duì)列不再使用時是否自動刪除此
* arguments:隊(duì)列參數(shù)
*/
channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
/**
* 交換機(jī)和隊(duì)列綁定
* String queue, String exchange, String routingKey
* queue 隊(duì)列名稱
* exchange 交換機(jī)名稱
* routingKey 路由key
*/
channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_ROUTING_INFORM,ROUTINGKEY_EMAIL);
/**
* 定義消費(fèi)方法
*/
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
/**
*
* @param consumerTag 消費(fèi)者的標(biāo)簽,在channel.basicConsume()去指定
* @param envelope 消息包的內(nèi)容,可從中獲取消息id,消息routingkey,交換機(jī),消息和重傳標(biāo)志(收到消息失敗后是否需要重新發(fā)送)
* @param properties properties
* @param body
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
//交換機(jī)
String exchange = envelope.getExchange();
//消息id,mq在channel中用來標(biāo)識消息的id,可用于確認(rèn)消息已接收
long deliveryTag = envelope.getDeliveryTag();
//消息內(nèi)容
String message= new String(body,"utf-8");
System.out.println("receive message:"+message);
}
};
/**
*監(jiān)聽隊(duì)列String queue, boolean autoAck, Consumer callback
* queue 隊(duì)列名稱
* autoAck 是否自動回復(fù),設(shè)置為true為表示消息接收到自動向mq回復(fù)接收到了,mq接收到回復(fù)會刪除消息,設(shè)置為false則需要手動回復(fù)
* callback 消費(fèi)消息的方法,消費(fèi)者接收到消息后調(diào)用此方法
*/
channel.basicConsume(QUEUE_INFORM_EMAIL,true,defaultConsumer);
}
}
3、短信發(fā)送消費(fèi)者
參考郵件發(fā)送消費(fèi)者的代碼流程,編寫短信通知的代碼
1.4.4思考
1、Routing模式和Publish/subscibe有啥區(qū)別?
Routing模式要求隊(duì)列在綁定交換機(jī)時要指定routingkey,消息會轉(zhuǎn)發(fā)到符合routingkey的隊(duì)列。
1.4 Topics(通配符模式)
1.4.1工作模式
路由模式:
1、每個消費(fèi)者監(jiān)聽自己的隊(duì)列,并且設(shè)置帶統(tǒng)配符的routingkey。
2、生產(chǎn)者將消息發(fā)給broker,由交換機(jī)根據(jù)routingkey來轉(zhuǎn)發(fā)消息到指定的隊(duì)列。
1.4.2代碼
案例:
根據(jù)用戶的通知設(shè)置去通知用戶,設(shè)置接收Email的用戶只接收Email,設(shè)置接收sms的用戶只接收sms,設(shè)置兩種通知類型都接收的則兩種通知都有效
1、生產(chǎn)者
聲明交換機(jī),指定topic類型:
1、每個消費(fèi)者監(jiān)聽自己的隊(duì)列,并且設(shè)置帶統(tǒng)配符的routingkey。
2、生產(chǎn)者將消息發(fā)給broker,由交換機(jī)根據(jù)routingkey來轉(zhuǎn)發(fā)消息到指定的隊(duì)列
1.4.2代碼
案例:
根據(jù)用戶的通知設(shè)置去通知用戶,設(shè)置接收Email的用戶只接收Email,設(shè)置接收sms的用戶只接收sms,設(shè)置兩種通知類型都接收的則兩種通知都有效。
1、生產(chǎn)者
聲明交換機(jī),指定topic類型:
public class Producer04_topics {
//隊(duì)列名稱
private static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
private static final String QUEUE_INFORM_SMS = "queue_inform_sms";
private static final String EXCHANGE_TOPICS_INFORM="exchange_topics_inform";
private static final String ROUTINGKEY_EMAIL="inform.#.email.#";
private static final String ROUTINGKEY_SMS="inform.#.sms.#";
public static void main(String[] args) {
//通過連接工廠創(chuàng)建新的連接和mq建立連接
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);//端口
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//設(shè)置虛擬機(jī),一個mq服務(wù)可以設(shè)置多個虛擬機(jī),每個虛擬機(jī)就相當(dāng)于一個獨(dú)立的mq
connectionFactory.setVirtualHost("/");
Connection connection = null;
Channel channel = null;
try {
//建立新連接
connection = connectionFactory.newConnection();
//創(chuàng)建會話通道,生產(chǎn)者和mq服務(wù)所有通信都在channel通道中完成
channel = connection.createChannel();
//聲明隊(duì)列,如果隊(duì)列在mq 中沒有則要創(chuàng)建
//參數(shù):String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
/**
* 參數(shù)明細(xì)
* 1、queue 隊(duì)列名稱
* 2、durable 是否持久化,如果持久化,mq重啟后隊(duì)列還在
* 3、exclusive 是否獨(dú)占連接,隊(duì)列只允許在該連接中訪問,如果connection連接關(guān)閉隊(duì)列則自動刪除,如果將此參數(shù)設(shè)置true可用于臨時隊(duì)列的創(chuàng)建
* 4、autoDelete 自動刪除,隊(duì)列不再使用時是否自動刪除此隊(duì)列,如果將此參數(shù)和exclusive參數(shù)設(shè)置為true就可以實(shí)現(xiàn)臨時隊(duì)列(隊(duì)列不用了就自動刪除)
* 5、arguments 參數(shù),可以設(shè)置一個隊(duì)列的擴(kuò)展參數(shù),比如:可設(shè)置存活時間
*/
channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null);
channel.queueDeclare(QUEUE_INFORM_SMS,true,false,false,null);
//聲明一個交換機(jī)
//參數(shù):String exchange, String type
/**
* 參數(shù)明細(xì):
* 1、交換機(jī)的名稱
* 2、交換機(jī)的類型
* fanout:對應(yīng)的rabbitmq的工作模式是 publish/subscribe
* direct:對應(yīng)的Routing 工作模式
* topic:對應(yīng)的Topics工作模式
* headers: 對應(yīng)的headers工作模式
*/
channel.exchangeDeclare(EXCHANGE_TOPICS_INFORM, BuiltinExchangeType.TOPIC);
//進(jìn)行交換機(jī)和隊(duì)列綁定
//參數(shù):String queue, String exchange, String routingKey
/**
* 參數(shù)明細(xì):
* 1、queue 隊(duì)列名稱
* 2、exchange 交換機(jī)名稱
* 3、routingKey 路由key,作用是交換機(jī)根據(jù)路由key的值將消息轉(zhuǎn)發(fā)到指定的隊(duì)列中,在發(fā)布訂閱模式中調(diào)協(xié)為空字符串
*/
channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_TOPICS_INFORM,ROUTINGKEY_EMAIL);
channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_TOPICS_INFORM,ROUTINGKEY_SMS);
//發(fā)送消息
//參數(shù):String exchange, String routingKey, BasicProperties props, byte[] body
/**
* 參數(shù)明細(xì):
* 1、exchange,交換機(jī),如果不指定將使用mq的默認(rèn)交換機(jī)(設(shè)置為"")
* 2、routingKey,路由key,交換機(jī)根據(jù)路由key來將消息轉(zhuǎn)發(fā)到指定的隊(duì)列,如果使用默認(rèn)交換機(jī),routingKey設(shè)置為隊(duì)列的名稱
* 3、props,消息的屬性
* 4、body,消息內(nèi)容
*/
for(int i=0;i<5;i++){
//發(fā)送消息的時候指定routingKey
String message = "send email inform message to user";
channel.basicPublish(EXCHANGE_TOPICS_INFORM,ROUTINGKEY_EMAIL,null,message.getBytes());
System.out.println("send to mq "+message);
}
/* for(int i=0;i<5;i++){
//發(fā)送消息的時候指定routingKey
String message = "send email inform message to user";
channel.basicPublish(EXCHANGE_TOPICS_INFORM,ROUTINGKEY_SMS,null,message.getBytes());
System.out.println("send to mq "+message);
}*/
} catch (Exception e) {
e.printStackTrace();
} finally {
//關(guān)閉連接
//先關(guān)閉通道
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2、消費(fèi)端
隊(duì)列綁定交換機(jī)指定通配符:
統(tǒng)配符規(guī)則:
中間以“.”分隔。
符號#可以匹配多個詞,符號*可以匹配一個詞語。
1.4.4思考
1、本案例的需求使用Routing工作模式能否實(shí)現(xiàn)?
使用Routing模式也可以實(shí)現(xiàn)本案例,共設(shè)置三個 routingkey,分別是email、sms、all,email隊(duì)列綁定email和all,sms隊(duì)列綁定sms和all,這樣就可以實(shí)現(xiàn)上邊案例的功能,實(shí)現(xiàn)過程比topics復(fù)雜。
Topic模式更多加強(qiáng)大,它可以實(shí)現(xiàn)Routing、publish/subscirbe模式的功能。