訂閱模式
c1、c2:消費者
紅色:消息隊列
x:交換機
這種模式猛地一看和Work模式很像,但是這個模式中的每個消費者都有自己的隊列,同時引入了一個新的概念——交換機。
交互機分別綁定生產者和消息隊列,生產者發送的消息會經過交換機到達隊列,這樣可以實現一個消息被多個消費者獲取的目的。
注意:
如果交換機沒有綁定隊列,生產者發送的消息會丟失,因爲交互機沒有存儲消息的能力,消息只能存在隊列之中。
生產者代碼:
public class Send {
private final static String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] argv) throws Exception {
// 獲取到連接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 聲明交換機(exchange)
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
String message = "Hello World"; // 消息內容
// 將消息發送到交換機
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
channel.close();
connection.close();
}
}
消費者一代碼:
public class Recv {
private final static String QUEUE_NAME = "test_queue_fanout_1";
private final static String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] argv) throws Exception {
// 獲取到連接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 聲明隊列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 綁定隊列到交換機
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
// 同一時刻服務器只會發一條消息給消費者
channel.basicQos(1);
// 定義隊列的消費者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 監聽隊列,手動返回完成
channel.basicConsume(QUEUE_NAME, false, consumer);
// 獲取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" 前臺系統: '" + message + "'");
Thread.sleep(10);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
消費者二代碼:
public class Recv2 {
private final static String QUEUE_NAME = "test_queue_fanout_2";
private final static String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] argv) throws Exception {
// 獲取到連接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 聲明隊列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 綁定隊列到交換機
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
// 同一時刻服務器只會發一條消息給消費者
channel.basicQos(1);
// 定義隊列的消費者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 監聽隊列,手動返回完成
channel.basicConsume(QUEUE_NAME, false, consumer);
// 獲取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" 搜索系統: '" + message + "'");
Thread.sleep(10);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
以上消費者一和消費者二種分別聲明瞭自己的隊列,都綁定到了同一個在生成者端聲明的交換機中。
生產者發送一條消息到交換機,綁定到了交換機的隊列都可以獲得這條消息,實現一條消息被多個消費者獲得的功能。
在RabbitMQ的管理工具中可以查看綁定到交換機的隊列,如下:
路由模式
路由模式中的元素和訂閱者模式中的元素一樣,但是路由模式是在訂閱者模式基礎上的升級。訂閱者模式中的隊列綁定了交換機後會接受生成者發送的所有消息,但是消費者可能並不想接受所有的消息,路由模式可以實現消息的訂製。
路由模式在生產者發送消息時會聲明一個key,消費者的隊列在綁定交換機是會表明自己接受什麼key值的消息,下面來看一下代碼:
生產者代碼:
public class Send {
private final static String EXCHANGE_NAME = "test_exchange_direct";
public static void main(String[] argv) throws Exception {
// 獲取到連接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 聲明exchange
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
// 消息內容
String message1 = "刪除信息, id = 1001";
String message2 = "更新信息, id = 1002";
// 發送消息,聲明消息的key
channel.basicPublish(EXCHANGE_NAME, "delete", null, message1.getBytes());
channel.basicPublish(EXCHANGE_NAME, "update", null, message2.getBytes());
System.out.println(" [x] Sent '" + message1 + "'");
System.out.println(" [x] Sent '" + message2 + "'");
channel.close();
connection.close();
}
}
消費者代碼:
public class Recv {
private final static String QUEUE_NAME = "test_queue_direct_1";
private final static String EXCHANGE_NAME = "test_exchange_direct";
public static void main(String[] argv) throws Exception {
// 獲取到連接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 聲明隊列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 綁定隊列到交換機,聲明接受消息的key
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "delete");
// 同一時刻服務器只會發一條消息給消費者
channel.basicQos(1);
// 定義隊列的消費者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 監聽隊列,手動返回完成
channel.basicConsume(QUEUE_NAME, false, consumer);
// 獲取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" 前臺系統: '" + message + "'");
Thread.sleep(10);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
生產者發送了兩條消息,一條key爲delete,另一條key爲update。在消費者端,隊列綁定交互機時聲明瞭delete這個key,這樣當前消費者只會接受到delete消息,不會接收到奧update消息。
通配符模式
通配符模式中的元素和訂閱者模式及路由模式中的元素一樣,而通配符模式是在路由模式基礎上的升級。路由模式需要消費者端的隊列在綁定交換機時聲明完整的key,如果需要接受多個key的消息則需要綁定多條。通配符模式就類似模糊匹配,只需要聲明部分關鍵字即可。
#:可以匹配一個或多個詞
*:只能匹配一個詞
例:
item.category.add,使用#寫爲item.#即可匹配到,使用*則需寫爲item.*.*
生產者代碼:
public class Send {
private final static String EXCHANGE_NAME = "test_exchange_topic";
public static void main(String[] argv) throws Exception {
// 獲取到連接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 聲明exchange
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
// 消息內容
String message1 = "刪除,id = 1001";
String message2 = "更新,id = 1002";
channel.basicPublish(EXCHANGE_NAME, "item.delete", null, message1.getBytes());
channel.basicPublish(EXCHANGE_NAME, "item.update", null, message2.getBytes());
System.out.println(" [x] Sent '" + message1 + "'");
System.out.println(" [x] Sent '" + message2 + "'");
channel.close();
connection.close();
}
}
消費者代碼:
public class Recv2 {
private final static String QUEUE_NAME = "test_queue_topic_2";
private final static String EXCHANGE_NAME = "test_exchange_topic";
public static void main(String[] argv) throws Exception {
// 獲取到連接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 聲明隊列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 綁定隊列到交換機
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "item.#");
// 同一時刻服務器只會發一條消息給消費者
channel.basicQos(1);
// 定義隊列的消費者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 監聽隊列,手動返回完成
channel.basicConsume(QUEUE_NAME, false, consumer);
// 獲取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" 搜索系統: '" + message + "'");
Thread.sleep(10);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
消費者端的的隊列在綁定交換機時只需要寫item.#即可匹配到item.delete和item.update。
小結
本篇文章中的三種消息隊列包含的元素相同,功能是越來越靈活的,之所以能實現這種效果,是因爲交換機有不同的模式,三種模式的生產者端在聲明交換機時分別使用了不同的模式。
訂閱模式——Fanout
路由模式——Direct通配模式——Topic