RabbitMQ工作模式介紹
1. simple簡單模式
模式介紹
- 消息產生著將消息放入隊列
- 消息的消費者(consumer) 監聽(while) 消息隊列,如果隊列中有消息,就消費掉,消息被拿走后,自動從隊列中刪除(隱患 消息可能沒有被消費者正確處理,已經從隊列中消失了,造成消息的丟失)應用場景:聊天(中間有一個過度的服務器;p端,c端)
代碼演示
生產者端
public class Producer {
static final String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws Exception {
//創建連接
Connection connection = ConnectionUtil.getConnection();
// 創建頻道
Channel channel = connection.createChannel();
// 聲明(創建)隊列
/**
* 參數1:隊列名稱
* 參數2:是否定義持久化隊列
* 參數3:是否獨占本次連接
* 參數4:是否在不使用的時候自動刪除隊列
* 參數5:隊列其它參數
*/
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
for (int i = 1; i <= 30; i++) {
// 發送信息
String message = "你好;小兔子!work模式--" + i;
/**
* 參數1:交換機名稱,如果沒有指定則使用默認Default Exchage
* 參數2:路由key,簡單模式可以傳遞隊列名稱
* 參數3:消息其它屬性
* 參數4:消息內容
*/
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println("已發送消息:" + message);
}
// 關閉資源
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();
// 創建頻道
Channel channel = connection.createChannel();
// 聲明(創建)隊列
/**
* 參數1:隊列名稱
* 參數2:是否定義持久化隊列
* 參數3:是否獨占本次連接
* 參數4:是否在不使用的時候自動刪除隊列
* 參數5:隊列其它參數
*/
channel.queueDeclare(Producer.QUEUE_NAME, true, false, false, null);
//一次只能接收并處理一個消息
channel.basicQos(1);
//創建消費者;并設置消息處理
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
/**
* consumerTag 消息者標簽,在channel.basicConsume時候可以指定
* envelope 消息包的內容,可從中獲取消息id,消息routingkey,交換機,消息和重傳標志(收到消息失敗后是否需要重新發送)
* 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();
}
}
};
//監聽消息
/**
* 參數1:隊列名稱
* 參數2:是否自動確認,設置為true為表示消息接收到自動向mq回復接收到了,mq接收到回復會刪除消息,設置為false則需要手動確認
* 參數3:消息接收到后回調
*/
channel.basicConsume(Producer.QUEUE_NAME, false, consumer);
}
}
2. work工作模式(資源的競爭)
模式介紹
- 消息產生者將消息放入隊列消費者可以有多個,消費者1,消費者2,同時監聽同一個隊列,消息被消費?C1 C2共同爭搶當前的消息隊列內容,誰先拿到誰負責消費消息(隱患,高并發情況下,默認會產生某一個消息被多個消費者共同使用,可以設置一個開關(syncronize,與同步鎖的性能不一樣) 保證一條消息只能被一個消費者使用)
- 應用場景:紅包;大項目中的資源調度(任務分配系統不需知道哪一個任務執行系統在空閑,直接將任務扔到消息隊列中,空閑的系統自動爭搶)
代碼實現
就是比簡單模式多了一個消費者
生產者端
public class Producer {
static final String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws Exception {
//創建連接
Connection connection = ConnectionUtil.getConnection();
// 創建頻道
Channel channel = connection.createChannel();
// 聲明(創建)隊列
/**
* 參數1:隊列名稱
* 參數2:是否定義持久化隊列
* 參數3:是否獨占本次連接
* 參數4:是否在不使用的時候自動刪除隊列
* 參數5:隊列其它參數
*/
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
for (int i = 1; i <= 30; i++) {
// 發送信息
String message = "你好;小兔子!work模式--" + i;
/**
* 參數1:交換機名稱,如果沒有指定則使用默認Default Exchage
* 參數2:路由key,簡單模式可以傳遞隊列名稱
* 參數3:消息其它屬性
* 參數4:消息內容
*/
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println("已發送消息:" + message);
}
// 關閉資源
channel.close();
connection.close();
}
}
消費者1端
**
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();
// 創建頻道
Channel channel = connection.createChannel();
// 聲明(創建)隊列
/**
* 參數1:隊列名稱
* 參數2:是否定義持久化隊列
* 參數3:是否獨占本次連接
* 參數4:是否在不使用的時候自動刪除隊列
* 參數5:隊列其它參數
*/
channel.queueDeclare(Producer.QUEUE_NAME, true, false, false, null);
//一次只能接收并處理一個消息
channel.basicQos(1);
//創建消費者;并設置消息處理
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
/**
* consumerTag 消息者標簽,在channel.basicConsume時候可以指定
* envelope 消息包的內容,可從中獲取消息id,消息routingkey,交換機,消息和重傳標志(收到消息失敗后是否需要重新發送)
* 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();
}
}
};
//監聽消息
/**
* 參數1:隊列名稱
* 參數2:是否自動確認,設置為true為表示消息接收到自動向mq回復接收到了,mq接收到回復會刪除消息,設置為false則需要手動確認
* 參數3:消息接收到后回調
*/
channel.basicConsume(Producer.QUEUE_NAME, false, consumer);
}
}
消費者2端
**
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();
// 創建頻道
Channel channel = connection.createChannel();
// 聲明(創建)隊列
/**
* 參數1:隊列名稱
* 參數2:是否定義持久化隊列
* 參數3:是否獨占本次連接
* 參數4:是否在不使用的時候自動刪除隊列
* 參數5:隊列其它參數
*/
channel.queueDeclare(Producer.QUEUE_NAME, true, false, false, null);
//一次只能接收并處理一個消息
channel.basicQos(1);
//創建消費者;并設置消息處理
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
/**
* consumerTag 消息者標簽,在channel.basicConsume時候可以指定
* envelope 消息包的內容,可從中獲取消息id,消息routingkey,交換機,消息和重傳標志(收到消息失敗后是否需要重新發送)
* 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();
}
}
};
//監聽消息
/**
* 參數1:隊列名稱
* 參數2:是否自動確認,設置為true為表示消息接收到自動向mq回復接收到了,mq接收到回復會刪除消息,設置為false則需要手動確認
* 參數3:消息接收到后回調
*/
channel.basicConsume(Producer.QUEUE_NAME, false, consumer);
}
}
結果
Consumer_queue2控制臺輸出
Consumer_queue1控制臺輸出
**
可以看到兩個消費者都去隊列中拿消息了
**
小結
如果一個隊列中有多個消費者,那么消費者之間對于同一消息的關系是競爭關系
Work Queues對于任務過重或任務較多情況使用工作隊列可以提高任務處理的速度,例如短信服務部署多個只要一個發送成功即可
3. publish/subscribe發布訂閱(共享資源)
模式介紹
前面2個案例中,只有3個角色:
**
P:生產者,也就是要發送消息的程序
C:消費者:消息的接受者,會一直等待消息到來。
queue:消息隊列,圖中紅色部分
而在訂閱模型中,多了一個exchange角色,而且過程略有變化:
P:生產者,也就是要發送消息的程序,但是不再發送到隊列中,而是發給X(交換機)
C:消費者,消息的接受者,會一直等待消息到來。
Queue:消息隊列,接收消息、緩存消息。
-
Exchange:交換機,圖中的X。一方面,接收生產者發送的消息。另一方面,知道如何處理消息,例如遞交給某個特別隊列、遞交給所有隊列、或是將消息丟棄。到底如何操作,取決于Exchange的類型。Exchange有常見以下3種類型:
Fanout:廣播,將消息交給所有綁定到交換機的隊列
Direct:定向,把消息交給符合指定routing key 的隊列
Topic:通配符,把消息交給符合routing pattern(路由模式) 的隊列
Exchange(交換機)只負責轉發消息,不具備存儲消息的能力,因此如果沒有任何隊列與Exchange綁定,或者沒有符合路由規則的隊列,那么消息會丟失!
應用場景
消息產生者將消息放入交換機,交換機發布訂閱把消息發送到所有消息隊列中,對應消息隊列的消費者拿到消息進行消費
相關場景:郵件群發,群聊天,廣播(廣告)
發布訂閱模式:
1、每個消費者監聽自己的隊列。
2、生產者將消息發給broker,由交換機將消息轉發到綁定此交換機的每個隊列,每個綁定交換機的隊列都將接收到消息
代碼實現
生產者
package com.pjh;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Switch {
//交換機名稱
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 IOException, TimeoutException {
/*使用工具類獲取連接*/
Connection connection = ConnectionUtil.getConnection();
/*創建頻道*/
Channel channel = connection.createChannel();
/**
* 聲明交換機
* 參數1:交換機名稱
* 參數2:交換機類型,fanout、topic、direct、headers
*/
channel.exchangeDeclare(FANOUT_EXCHAGE, BuiltinExchangeType.FANOUT);
/*聲明創建隊列*/
/**
* 參數1:隊列名稱
* 參數2:是否定義持久化隊列
* 參數3:是否獨占本次連接
* 參數4:是否在不使用的時候自動刪除隊列
* 參數5:隊列其它參數
*/
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++) {
// 發送信息
String message = "第" + i+"條消息";
/**
* 參數1:交換機名稱,如果沒有指定則使用默認Default Exchage
* 參數2:路由key,簡單模式可以傳遞隊列名稱
* 參數3:消息其它屬性
* 參數4:消息內容
*/
channel.basicPublish(FANOUT_EXCHAGE, "", null, message.getBytes());
System.out.println("已發送消息:" + message);
}
/*關閉資源*/
channel.close();
connection.close();
}
}
消費者1
package com.pjh;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Switch_Consumer {
//交換機名稱
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 IOException, TimeoutException {
/*使用工具類獲取連接*/
Connection connection = ConnectionUtil.getConnection();
/*獲取channel*/
Channel channel = connection.createChannel();
/*聲明交換機*/
channel.exchangeDeclare(FANOUT_EXCHAGE, BuiltinExchangeType.FANOUT);
// 聲明(創建)隊列
/**
* 參數1:隊列名稱
* 參數2:是否定義持久化隊列
* 參數3:是否獨占本次連接
* 參數4:是否在不使用的時候自動刪除隊列
* 參數5:隊列其它參數
*/
channel.queueDeclare(FANOUT_QUEUE_1,true,false,false,null);
/*隊列綁定交換機*/
channel.queueBind(FANOUT_QUEUE_1, FANOUT_EXCHAGE,"");
/*創建消費者并設置消息處理*/
Consumer consumer = new DefaultConsumer(channel) {
@Override
/**
* consumerTag 消息者標簽,在channel.basicConsume時候可以指定
* envelope 消息包的內容,可從中獲取消息id,消息routingkey,交換機,消息和重傳標志(收到消息失敗后是否需要重新發送)
* 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"));
}
};
//監聽消息
/**
* 參數1:隊列名稱
* 參數2:是否自動確認,設置為true為表示消息接收到自動向mq回復接收到了,mq接收到回復會刪除消息,設置為false則需要手動確認
* 參數3:消息接收到后回調
*/
channel.basicConsume(FANOUT_QUEUE_1,true,consumer);
}
}
消費者2
package com.pjh;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Switch_Consumer2 {
//交換機名稱
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 IOException, TimeoutException {
/*使用工具類獲取連接*/
Connection connection = ConnectionUtil.getConnection();
/*獲取channel*/
Channel channel = connection.createChannel();
/*聲明交換機*/
channel.exchangeDeclare(FANOUT_EXCHAGE, BuiltinExchangeType.FANOUT);
// 聲明(創建)隊列
/**
* 參數1:隊列名稱
* 參數2:是否定義持久化隊列
* 參數3:是否獨占本次連接
* 參數4:是否在不使用的時候自動刪除隊列
* 參數5:隊列其它參數
*/
channel.queueDeclare(FANOUT_QUEUE_2,true,false,false,null);
/*隊列綁定交換機*/
channel.queueBind(FANOUT_QUEUE_2, FANOUT_EXCHAGE,"");
/*創建消費者并設置消息處理*/
Consumer consumer = new DefaultConsumer(channel) {
@Override
/**
* consumerTag 消息者標簽,在channel.basicConsume時候可以指定
* envelope 消息包的內容,可從中獲取消息id,消息routingkey,交換機,消息和重傳標志(收到消息失敗后是否需要重新發送)
* 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"));
}
};
//監聽消息
/**
* 參數1:隊列名稱
* 參數2:是否自動確認,設置為true為表示消息接收到自動向mq回復接收到了,mq接收到回復會刪除消息,設置為false則需要手動確認
* 參數3:消息接收到后回調
*/
channel.basicConsume(FANOUT_QUEUE_2,true,consumer);
}
}
查看管控臺可以發現多了一個交換機
測試
啟動所有消費者,然后使用生產者發送消息;在每個消費者對應的控制臺可以查看到生產者發送的所有消息;到達廣播的效果。
消費者1控制臺輸出
消費者2控制臺輸出
在執行完測試代碼后,其實到RabbitMQ的管理后臺找到Exchanges
選項卡,點擊 fanout_exchange
的交換機,可以查看到如下的綁定:
小結
交換機需要與隊列進行綁定,綁定之后:一個消息可以被多個消費者都收到
發布訂閱模式與工作隊列模式的區別:
** 1.工作隊列模式不用定義交換機,而發布/訂閱模式需要訂閱交換機
** 2.發布/訂閱模式生產方向是面向交換機發送消息,工作隊列模式的生產是面向隊列發送消息(底層使用默認交換機)
3.發布/訂閱模式需要設置隊列和交換機的綁定,工作隊列模式不需要設置,實際上工作隊列模式會將隊列綁定到默認的交換機
4.Routing路由模式
模式介紹
路由模式特點:
隊列與交換機的綁定,不能是任意綁定了,而是要指定一個
RoutingKey
(路由key)消息的發送方在 向 Exchange發送消息時,也必須指定消息的
RoutingKey
。Exchange不再把消息交給每一個綁定的隊列,而是根據消息的
Routing Key
進行判斷,只有隊列的Routingkey
與消息的Routing key
完全一致,才會接收到消息
P:生產者,向Exchange發送消息,發送消息時,會指定一個routing key。
X:Exchange(交換機),接收生產者的消息,然后把消息遞交給 與routing key完全匹配的隊列
C1:消費者,其所在隊列指定了需要routing key 為 error 的消息
C2:消費者,其所在隊列指定了需要routing key 為 info、error、warning 的消息
代碼實現
在編碼上與 Publish/Subscribe發布與訂閱模式
的區別是交換機的類型為:Direct,還有隊列綁定交換機的時候需要指定routing key。
生產者
package com.pjh;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Routing {
//交換機名稱
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 IOException, TimeoutException {
/*使用工具類獲取連接*/
Connection connection = ConnectionUtil.getConnection();
/*創建頻道*/
Channel channel = connection.createChannel();
/**
* 聲明交換機
* 參數1:交換機名稱
* 參數2:交換機類型,fanout、topic、direct、headers
*/
channel.exchangeDeclare(DIRECT_EXCHAGE, BuiltinExchangeType.DIRECT);
/*聲明創建隊列*/
/**
* 參數1:隊列名稱
* 參數2:是否定義持久化隊列
* 參數3:是否獨占本次連接
* 參數4:是否在不使用的時候自動刪除隊列
* 參數5:隊列其它參數
*/
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");
// 發送信息
String message = "新增了商品。路由模式;routing key 為 insert " ;
/**
* 參數1:交換機名稱,如果沒有指定則使用默認Default Exchage
* 參數2:路由key,簡單模式可以傳遞隊列名稱
* 參數3:消息其它屬性
* 參數4:消息內容
*/
channel.basicPublish(DIRECT_EXCHAGE, "insert", null, message.getBytes());
System.out.println("已發送消息:" + message);
// 發送信息
message = "修改了商品。路由模式;routing key 為 update" ;
/**
* 參數1:交換機名稱,如果沒有指定則使用默認Default Exchage
* 參數2:路由key,簡單模式可以傳遞隊列名稱
* 參數3:消息其它屬性
* 參數4:消息內容
*/
channel.basicPublish(DIRECT_EXCHAGE, "update", null, message.getBytes());
System.out.println("已發送消息:" + message);
/*關閉資源*/
channel.close();
connection.close();
}
}
消費者1
package com.pjh;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Routing_Consumer {
//交換機名稱
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 IOException, TimeoutException {
/*使用工具類獲取連接*/
Connection connection = ConnectionUtil.getConnection();
/*獲取channel*/
Channel channel = connection.createChannel();
/*聲明交換機*/
channel.exchangeDeclare(DIRECT_EXCHAGE, BuiltinExchangeType.DIRECT);
// 聲明(創建)隊列
/**
* 參數1:隊列名稱
* 參數2:是否定義持久化隊列
* 參數3:是否獨占本次連接
* 參數4:是否在不使用的時候自動刪除隊列
* 參數5:隊列其它參數
*/
channel.queueDeclare(DIRECT_QUEUE_INSERT,true,false,false,null);
/*隊列綁定交換機*/
channel.queueBind( DIRECT_QUEUE_INSERT,DIRECT_EXCHAGE,"insert");
/*創建消費者并設置消息處理*/
Consumer consumer = new DefaultConsumer(channel) {
@Override
/**
* consumerTag 消息者標簽,在channel.basicConsume時候可以指定
* envelope 消息包的內容,可從中獲取消息id,消息routingkey,交換機,消息和重傳標志(收到消息失敗后是否需要重新發送)
* 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"));
}
};
//監聽消息
/**
* 參數1:隊列名稱
* 參數2:是否自動確認,設置為true為表示消息接收到自動向mq回復接收到了,mq接收到回復會刪除消息,設置為false則需要手動確認
* 參數3:消息接收到后回調
*/
channel.basicConsume(DIRECT_QUEUE_INSERT,true,consumer);
}
}
消費者二
package com.pjh;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Routing_Consumer2 {
//交換機名稱
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 IOException, TimeoutException {
/*使用工具類獲取連接*/
Connection connection = ConnectionUtil.getConnection();
/*獲取channel*/
Channel channel = connection.createChannel();
/*聲明交換機*/
channel.exchangeDeclare(DIRECT_EXCHAGE, BuiltinExchangeType.DIRECT);
// 聲明(創建)隊列
/**
* 參數1:隊列名稱
* 參數2:是否定義持久化隊列
* 參數3:是否獨占本次連接
* 參數4:是否在不使用的時候自動刪除隊列
* 參數5:隊列其它參數
*/
channel.queueDeclare(DIRECT_QUEUE_UPDATE,true,false,false,null);
/*隊列綁定交換機*/
channel.queueBind( DIRECT_QUEUE_UPDATE,DIRECT_EXCHAGE,"update");
/*創建消費者并設置消息處理*/
Consumer consumer = new DefaultConsumer(channel) {
@Override
/**
* consumerTag 消息者標簽,在channel.basicConsume時候可以指定
* envelope 消息包的內容,可從中獲取消息id,消息routingkey,交換機,消息和重傳標志(收到消息失敗后是否需要重新發送)
* 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"));
}
};
//監聽消息
/**
* 參數1:隊列名稱
* 參數2:是否自動確認,設置為true為表示消息接收到自動向mq回復接收到了,mq接收到回復會刪除消息,設置為false則需要手動確認
* 參數3:消息接收到后回調
*/
channel.basicConsume(DIRECT_QUEUE_UPDATE,true,consumer);
}
}
測試
消費者1客戶端輸出
消費者2客戶端輸出
在執行完測試代碼后,其實到RabbitMQ的管理后臺找到Exchanges
選項卡,點擊 direct_exchange
的交換機,可以查看到如下的綁定:
小結
Routing模式要求隊列在綁定交換機時要指定routing key,消息會轉發到符合routing key的隊列。
5.Topics通配符模式
模式說明
Topic
類型與Direct
相比,都是可以根據RoutingKey
把消息路由到不同的隊列。只不過Topic
類型Exchange
可以讓隊列在綁定Routing key
的時候使用通配符!
Routingkey
一般都是有一個或多個單詞組成,多個單詞之間以”.”分割,例如: item.insert
通配符規則:
#
:匹配一個或多個詞
*
:匹配不多不少恰好1個詞
舉例:
item.#
:能夠匹配item.insert.abc
或者 item.insert
item.*
:只能匹配item.insert
紅色Queue:綁定的是
usa.#
,因此凡是以usa.
開頭的routing key
都會被匹配到黃色Queue:綁定的是
#.news
,因此凡是以.news
結尾的routing key
都會被匹配
代碼實現
案例:
在生產者端插入倆個以“pjh.insert”,“pjh.update”為路由key的消息
在消費者端使用“pjh.*”為通配符接受消息
生產者
package com.pjh;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Topic {
//交換機名稱
static final String TOPIC_EXCHAGE = "TOPIC_exchange";
//隊列名稱
static final String TOPIC_QUEUE_INSERT = "TOPIC_queue_insert";
//隊列名稱
static final String TOPIC_QUEUE_UPDATE = "TOPIC_queue_update";
public static void main(String[] args) throws IOException, TimeoutException {
/*使用工具類獲取連接*/
Connection connection = ConnectionUtil.getConnection();
/*創建頻道*/
Channel channel = connection.createChannel();
/**
* 聲明交換機
* 參數1:交換機名稱
* 參數2:交換機類型,fanout、topic、direct、headers
*/
channel.exchangeDeclare(TOPIC_EXCHAGE, BuiltinExchangeType.TOPIC);
/*聲明創建隊列*/
/**
* 參數1:隊列名稱
* 參數2:是否定義持久化隊列
* 參數3:是否獨占本次連接
* 參數4:是否在不使用的時候自動刪除隊列
* 參數5:隊列其它參數
*/
channel.queueDeclare(TOPIC_QUEUE_INSERT,true,false,false,null);
channel.queueDeclare(TOPIC_QUEUE_UPDATE,true,false,false,null);
/*隊列綁定交換機*/
/*
queue 隊列名稱
exchange 交換機名稱
routingKey 路由key
*/
channel.queueBind(TOPIC_QUEUE_INSERT,TOPIC_EXCHAGE, "pjh.insert");
channel.queueBind(TOPIC_QUEUE_UPDATE, TOPIC_EXCHAGE, "pjh.update");
// 發送信息
String message = "新增了商品。TOPIC模式;routing key 為 insert " ;
/**
* 參數1:交換機名稱,如果沒有指定則使用默認Default Exchage
* 參數2:路由key,簡單模式可以傳遞隊列名稱
* 參數3:消息其它屬性
* 參數4:消息內容
*/
channel.basicPublish(TOPIC_EXCHAGE, "pjh.insert", null, message.getBytes());
System.out.println("已發送消息:" + message);
// 發送信息
message = "修改了商品。TOPIC模式;routing key 為 update" ;
/**
* 參數1:交換機名稱,如果沒有指定則使用默認Default Exchage
* 參數2:路由key,簡單模式可以傳遞隊列名稱
* 參數3:消息其它屬性
* 參數4:消息內容
*/
channel.basicPublish(TOPIC_EXCHAGE, "pjh.update", null, message.getBytes());
System.out.println("已發送消息:" + message);
/*關閉資源*/
channel.close();
connection.close();
}
}
消費者
package com.pjh;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Topic_Consumer2 {
//交換機名稱
static final String TOPIC_EXCHAGE = "TOPIC_exchange";
//隊列名稱
static final String TOPIC_QUEUE_INSERT = "TOPIC_queue_insert";
//隊列名稱
static final String TOPIC_QUEUE_UPDATE = "TOPIC_queue_update";
public static void main(String[] args) throws IOException, TimeoutException {
/*使用工具類獲取連接*/
Connection connection = ConnectionUtil.getConnection();
/*獲取channel*/
Channel channel = connection.createChannel();
/*聲明交換機*/
channel.exchangeDeclare(TOPIC_EXCHAGE, BuiltinExchangeType.TOPIC);
// 聲明(創建)隊列
/**
* 參數1:隊列名稱
* 參數2:是否定義持久化隊列
* 參數3:是否獨占本次連接
* 參數4:是否在不使用的時候自動刪除隊列
* 參數5:隊列其它參數
*/
channel.queueDeclare(TOPIC_QUEUE_UPDATE,true,false,false,null);
/*隊列綁定交換機*/
channel.queueBind( TOPIC_QUEUE_UPDATE,TOPIC_EXCHAGE,"pjh.*");
/*創建消費者并設置消息處理*/
Consumer consumer = new DefaultConsumer(channel) {
@Override
/**
* consumerTag 消息者標簽,在channel.basicConsume時候可以指定
* envelope 消息包的內容,可從中獲取消息id,消息routingkey,交換機,消息和重傳標志(收到消息失敗后是否需要重新發送)
* 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"));
}
};
//監聽消息
/**
* 參數1:隊列名稱
* 參數2:是否自動確認,設置為true為表示消息接收到自動向mq回復接收到了,mq接收到回復會刪除消息,設置為false則需要手動確認
* 參數3:消息接收到后回調
*/
channel.basicConsume(TOPIC_QUEUE_UPDATE,true,consumer);
}
}
測試
成功接受消息
模式總結
1、簡單模式 HelloWorld
一個生產者、一個消費者,不需要設置交換機(使用默認的交換機)
2、工作隊列模式 Work Queue
一個生產者、多個消費者(競爭關系),不需要設置交換機(使用默認的交換機)
3、發布訂閱模式 Publish/subscribe
需要設置類型為fanout的交換機,并且交換機和隊列進行綁定,當發送消息到交換機后,交換機會將消息發送到綁定的隊列
4、路由模式 Routing
需要設置類型為direct的交換機,交換機和隊列進行綁定,并且指定routing key,當發送消息到交換機后,交換機會根據routing key將消息發送到對應的隊列
5、通配符模式 Topic
需要設置類型為topic的交換機,交換機和隊列進行綁定,并且指定通配符方式的routing key,當發送消息到交換機后,交換機會根據routing key將消息發送到對應的隊列