RabbitMQ 消息模型
RabbitMQ 最新版本推荐的消息模型一共有七种,这七种消息模型也代表了七种如何使用RabbitMQ 的方式。学习这七种消息模型是更好的了解RabbitMQ的必备技能。
Simple Queue 模型
简单理解
如果把使用 RabbitMQ 进行消息发送的过程比喻成邮寄邮件。那么简单队列的场景是,只有一个邮箱、一个邮局、一个投递员,。消息通过 RabbitMQ 进行一对一发送,发送过程最简单。
简单队列模型示意图
代码实现
消息发送者
import cn.hutool.core.util.CharsetUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* @desc RabbitMQ 简单队列实战
* @date 2021/1/29
*/
public class Send {
private final static String QUEUE_NAME = "hello";
public static void main(String[] args) {
// 1、创建 rabbitmq 连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.56.10");
factory.setPort(5672);
factory.setVirtualHost("/");
// 2、创建 rabbitmq 连接和信道
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel();) {
/**
* queue: 队列名称
* durable: 消息是否持久化
* exclusive: 消息是否排他
* autoDelete: 是否自动删除队列
* arguments: 其他参数(例如:死信队列等信息)
*/
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
String message = "hello!";
/**
* 参数:String exchange, String routingKey, BasicProperties props, byte[] body
* exchange: 交换机名称:不写则是默认的交换机,那路由健需要和队列名称一样才可以被路由
* routingKey: 路由键
* props: 配置信息
* body: 发送的消息
*/
channel.basicPublish("", QUEUE_NAME, null, message.getBytes(CharsetUtil.UTF_8));
System.out.println("Send message:" + message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
消息接收者
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @desc RabbitMQ 简单队列实战
* @date 2021/1/30
*/
public class Recv {
private final static String QUEUE_NAME = "hello";
public static void main(String[] args) throws IOException, TimeoutException {
// 1、创建 rabbitmq 连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.56.10");
factory.setPort(5672);
factory.setVirtualHost("/");
// 2、创建连接,建立信道
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 3、指定要消费的队列,注意这里的配置必须与消息发送方配置的一直,否则无法消费
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
// 4、接收处理消息并自动确认
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] Received '" + message + "'");
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
}
}
Worker Queue 模型
简单理解
在前面我们学了Simple Queue 模型,Simple Queue 模型消息生产者和消费者是一 一对应的,消息由生产者发送到队列并由消费者消费。假设有一种消息,消息生产者一次性发送了10条消息,消费者消费一条消息执行耗时1秒,如果使用简单队列模式总共耗时10秒;如果使用工作队列模式有3个消费者共同消费这些消息理想情况下只需要3秒就可以处理完这些消息。
工作队列模型示意图
代码实现
消息生产者
import cn.hutool.core.util.CharsetUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* @desc RabbitMQ 工作队列实战
* <p>
* 消息生产能力大于消费能力,增加多几个消费节点
* 和简单队列类似,增加多个几个消费节点,处于竞争关系
* 默认策略:round robin 轮训
* @date 2021/1/29
*/
public class WorkerSend {
private final static String QUEUE_NAME = "work_queue";
public static void main(String[] args) {
// 1、创建 rabbitmq 连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.56.10");
factory.setPort(5672);
factory.setVirtualHost("/");
// 2、创建 rabbitmq 连接和信道
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel();) {
/**
* 参数:String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
* queue: 队列名称
* durable: 消息是否持久化
* exclusive: 消息是否排他
* autoDelete: 是否自动删除队列
* arguments: 其他参数(例如:死信队列等信息)
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
for (int i = 0; i < 10; i++) {
/**
* 参数:String exchange, String routingKey, BasicProperties props, byte[] body
* exchange: 交换机名称:不写则是默认的交换机,那路由健需要和队列名称一样才可以被路由
* routingKey: 路由键
* props: 配置信息
* body: 发送的消息
*/
String message = "Hello world !" + i;
channel.basicPublish("", QUEUE_NAME, null, message.getBytes(CharsetUtil.UTF_8));
System.out.println("--- Send Message: " + message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
消息消费者
因为工作队列模型有多个消费者示例,这里给出其中一个消费者代码
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @desc RabbitMQ 工作队列实战
* RabbitMQ 工作队列模型,是创建多个消费者共同消费消息,每个消息只可以被一个消费者处理
* 默认是轮询策略
*
* 简单队列模型和工作队列模型对比:
* 区别:
* 1、简单队列模型:是一个消费者和一个生产者
* 2、工作队列模型:由一个生产者生产消息,多个消费者共同消费消息,但是每个消息只可以被一个消费者处理
*
* 共同点:两种队列模型:都是直接将消息发送到Queue队列中,并没有Exchange交换机参与
* @email huawei_code@163.com
*/
public class WorkerRecv2 {
private final static String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
// 1、创建 rabbitmq 连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.56.10");
factory.setPort(5672);
factory.setVirtualHost("/");
// 2、创建连接,建立信道
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 3、指定要消费的队列,注意这里的配置必须与消息发送方配置的一直,否则无法消费
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
// 3、创建回调消费消息,并手动确认收到消息
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println("--- Recv 2 Received Message: '" + message + "'");
};
//关闭自动确认消息
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
}
}
问题1:如果某个消费者在处理消息过程中宕机,消息丢失如何处理?
为了确保消息永不丢失,RabbitMQ支持 消息确认。消费者发送回确认,以告知RabbitMQ已经接收,处理了特定的消息,并且RabbitMQ可以自由删除它。
如果使用者死了(其通道已关闭,连接已关闭或TCP连接丢失)而没有发送确认,RabbitMQ将了解消息未完全处理,并将重新排队。如果同时有其他消费者在线,它将很快将其重新分发给另一个消费者。这样,您可以确保即使工人偶尔死亡也不会丢失任何消息。
package com.youlai.common.rabbitmq.demo.work;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author huawei
* @desc RabbitMQ 工作队列实战
* RabbitMQ 工作队列模型,是创建多个消费者共同消费消息,每个消息只可以被一个消费者处理
* 默认是轮询策略
*
* 简单队列模型和工作队列模型对比:
* 区别:
* 1、简单队列模型:是一个消费者和一个生产者
* 2、工作队列模型:由一个生产者生产消息,多个消费者共同消费消息,但是每个消息只可以被一个消费者处理
*
* 共同点:两种队列模型:都是直接将消息发送到Queue队列中,并没有Exchange交换机参与
* @email huawei_code@163.com
* @date 2021/1/30
*/
public class WorkerRecv2 {
private final static String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
// 1、创建 rabbitmq 连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.56.10");
factory.setPort(5672);
factory.setVirtualHost("/");
// 2、创建连接,建立信道
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 3、指定要消费的队列,注意这里的配置必须与消息发送方配置的一直,否则无法消费
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
// 模拟轮询策略,请将这部分注释
// 4、限制消费者每次消费消息数量,每次消息处理完成后才能消费下一条消息
int fetchCount = 1;
channel.basicQos(fetchCount);
// 4、创建回调消费消息,并手动确认收到消息
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] Received '" + message + "'");
try {
doWork(message);
} finally {
System.out.println(" [x] Done");
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
};
// 5、关闭自动确认消息
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
}
private static void doWork(String task) {
for (char ch : task.toCharArray()) {
if (ch == '.') {
try {
Thread.sleep(1000);
} catch (InterruptedException _ignored) {
Thread.currentThread().interrupt();
}
}
}
}
}
问题2:如果RabbitMQ突然宕机,队列中未处理消息丢失如何解决?
解决方案:在创建队列的方法中有一个参数 durable 表示是否持久化消息,默认为false,将该参数设置为false,就代表将队列中的消息持久化到磁盘,这样即使 RabbitMQ 突然宕机消息也不会丢失
/**
* 参数:String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
* queue: 队列名称
* durable: 消息是否持久化
* exclusive: 消息是否排他
* autoDelete: 是否自动删除队列
* arguments: 其他参数(例如:死信队列等信息)
*/
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
问题3:如果某些消费者性能高某些消费者性能低,如何更新消息处理能力分配消息?
解决方案:RabbitMQ消费者处理消息默认使用自动确认模式,消费者会一次性将所有消息从队列中并逐一处理。通过限制消费者一次只能从队列中获取一条消息,处理完成后手动确认消息处理完成,才可以继续处理下一条消息,这样就可以根据消费者处理能力分配消息
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class WorkerRecv2 {
private final static String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
// 1、创建 rabbitmq 连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.56.10");
factory.setPort(5672);
factory.setVirtualHost("/");
// 2、创建连接,建立信道
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 3、指定要消费的队列,注意这里的配置必须与消息发送方配置的一直,否则无法消费
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
// 模拟轮询策略,请将这部分注释
// 4、限制消费者每次消费消息数量,每次消息处理完成后才能消费下一条消息
int fetchCount = 1;
channel.basicQos(fetchCount);
// 4、创建回调消费消息,并手动确认收到消息
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] Received '" + message + "'");
try {
doWork(message);
} finally {
System.out.println(" [x] Done");
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
};
// 5、关闭自动确认消息
channel.basicConsume(QUEUE_NAME, false, deliverCallback, consumerTag -> { });
}
private static void doWork(String task) {
for (char ch : task.toCharArray()) {
if (ch == '.') {
try {
Thread.sleep(1000);
} catch (InterruptedException _ignored) {
Thread.currentThread().interrupt();
}
}
}
}
}
Pub/Sub模型
简单理解
在前面我们学习了简单队列模型和工作队列模型,其实不管是简单队列模型还是工作队列模式只用到了 RabbitMQ 中的 Queue 队列,消息直接发送到队列中,消费者直接从队列中获取消息。
现在我们学习另一种消息的处理方式,消息生产者将消息发送到 Exchange 交换机中,再由交换机将消息投递到 Queue队列中,交换机和队列的关系是多对多的关系,表示一个交换机可以通过一定的规矩将消息投递到多个队列中,同样一个队列也可以接收多个交换机投递的消息
交换机投递消息到队列由哪几种方式?
- Fanout: 不处理路由键。你只需要简单的将队列绑定到交换机上。一个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。Fanout交换机转发消息是最快的
- Direct:处理路由键。需要将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全匹配。这是一个完整的匹配。如果一个队列绑定到该交换机上要求路由键
test
,则只有被标记为test
的消息才被转发,不会转发test.aaa,也不会转发dog.123,只会转发test。- Topic: 将路由键和某模式进行匹配。此时队列需要绑定要一个模式上。符号
#
匹配一个或多个词,符号*
匹配不多不少一个词。因此audit.#
能够匹配到audit.irs.corporate
,但是audit.*
只会匹配到audit.irs
- Headers:Headers类型的exchange使用的比较少,它也是忽略routingKey的一种路由方式。是使用Headers来匹配的。Headers是一个键值对,可以定义成Hashtable。发送者在发送的时候定义一些键值对,接收者也可以再绑定时候传入一些键值对,两者匹配的话,则对应的队列就可以收到消息。匹配有两种方式all和any。这两种方式是在接收端必须要用键值"x-mactch"来定义。all代表定义的多个键值对都要满足,而any则代码只要满足一个就可以了。fanout,direct,topic exchange的routingKey都需要要字符串形式的,而headers exchange则没有这个要求,因为键值对的值可以是任何类型
发布订阅模型示例图
Fanout Exchange中的消息流转过程
- 没有路由键,一个或多个队列绑定到Fanout交换。
- 生产者向Exchange发送消息。
- 然后,Exchange将消息无条件转发到队列。
代码实现
消息生产者
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.youlai.common.rabbitmq.demo.manager.ConnectionManager;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;
/**
* @desc RabbitMQ 发布订阅模型
* 简单说明:
* 1、之前工作模型中,我们虽然由多个消费者但是每个消息只能被一个消费者消费;在发布订阅模型中只要订阅了这个消息的消费者都可以接收到消息
* 2、正规的 RabbitMQ 使用方式,消息生产者先将消息发送到 Exchange 交换机中,在根据一定的策略将消息投递到队列中,消息生产者甚至不用知道队列的存在
* 3、Exchange 交换机需要做两件事:第一、接收来自生产者发送的消息;第二、将消息投递到队列中
* 4、Exchange 交换机必须知道如何正确的将消息投递到队列中(Direct exchange、Fanout exchange、Topic exchange、Headers exchange)
*/
public class PubSubSend {
public static void main(String[] args) {
try (Channel channel = ConnectionManager.getConnection().createChannel()) {
PubSubSend.declareExchange();
String message = "Hello world !";
channel.basicPublish("my-fanout-exchange", "", null, message.getBytes(StandardCharsets.UTF_8));
System.out.println("Send message :" + message);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void declareExchange() throws IOException, TimeoutException {
Channel channel = ConnectionManager.getConnection().createChannel();
//Declare my-fanout-exchange
channel.exchangeDeclare("my-fanout-exchange", BuiltinExchangeType.FANOUT, true);
channel.close();
}
}
消息消费者
import com.rabbitmq.client.Channel;
import com.youlai.common.rabbitmq.demo.manager.ConnectionManager;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class PubSubRecv {
public static void main(String[] args) throws IOException, TimeoutException {
PubSubSend.declareExchange();
PubSubRecv.declareQueues();
PubSubRecv.declareBindings();
Channel channel = ConnectionManager.getConnection().createChannel();
channel.basicConsume("WebClientQueue", true, (consumerTag, message) -> {
System.out.println(consumerTag);
System.out.println("WebClientQueue:" + new String(message.getBody()));
}, consumerTag -> {
System.out.println(consumerTag);
});
channel.basicConsume("AppClientQueue", true, (consumerTag, message) -> {
System.out.println(consumerTag);
System.out.println("AppClientQueue:" + new String(message.getBody()));
}, consumerTag -> {
System.out.println(consumerTag);
});
}
public static void declareQueues() throws IOException, TimeoutException {
//Create a channel - do no't share the Channel instance
Channel channel = ConnectionManager.getConnection().createChannel();
//queueDeclare - (queueName, durable, exclusive, autoDelete, arguments)
channel.queueDeclare("WebClientQueue", true, false, false, null);
channel.queueDeclare("AppClientQueue", true, false, false, null);
channel.close();
}
public static void declareBindings() throws IOException, TimeoutException {
Channel channel = ConnectionManager.getConnection().createChannel();
//Create bindings - (queue, exchange, routingKey)
channel.queueBind("WebClientQueue", "my-fanout-exchange", "");
channel.queueBind("AppClientQueue", "my-fanout-exchange", "");
channel.close();
}
}
Direct Exchange 模型
简单理解
Direct Exchange 消息模型 是交换机将消息投递到队列中的另一种方式,需要将交换机类型设置为 direct。那么什么是 direct 类型交换机?
其中Direct:处理路由键。需要将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全匹配。这是一个完整的匹配。如果一个队列绑定到该交换机上要求路由键
test
,则只有被标记为test
的消息才被转发,不会转发test.aaa,也不会转发dog.123,只会转发test。
Direct Exchange 消息模型示意图
Direct Exchange 中的消息流转过程
- 生产者向Exchange发送消息。
- 队列使用路由密钥绑定到Exchange。
- 通常,使用相同/不同的路由密钥有多个队列绑定到Exchange。
- 发送到Exchange的消息包含路由密钥。根据路由密钥,消息将转发到一个或多个队列。
- 订阅队列的使用者接收消息并进行处理。
代码实现
创建 RabbitMQ 单例连接对象
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ConnectionManager {
private static Connection connection = null;
public static Connection getConnection() {
if (connection == null) {
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.56.10");
factory.setPort(5672);
factory.setVirtualHost("/");
connection = factory.newConnection();
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
return connection;
}
}
消息生产者
package com.youlai.common.rabbitmq.demo.routing;
import cn.hutool.core.util.CharsetUtil;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.youlai.common.rabbitmq.demo.manager.ConnectionManager;
/**
* @desc RabbitMQ 路由模型
* 简单说明:
* 1、路由模型需要指定交换机类型为 direct,交换机和队列之间通过路由键绑定,交换机只会把消息推送到符合路由键的队列中
*/
public class RoutingSend {
private final static String EXCHANGE_NAME = "my_exchange_direct";
public static void main(String[] args) {
// 1、创建 rabbitmq 连接和信道
try (Channel channel = ConnectionManager.getConnection().createChannel()) {
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true, false, false, null);
String message = "Direct exchange,这条消息路由键是:info";
channel.basicPublish(EXCHANGE_NAME, "info", null, message.getBytes(CharsetUtil.UTF_8));
message = "Direct exchange,这条消息路由键是:error";
channel.basicPublish(EXCHANGE_NAME, "error", null, message.getBytes(CharsetUtil.UTF_8));
message = "Direct exchange,这条消息路由键是:warning";
channel.basicPublish(EXCHANGE_NAME, "warning", null, message.getBytes(CharsetUtil.UTF_8));
} catch (Exception e) {
e.printStackTrace();
}
}
}
消息消费者
import com.rabbitmq.client.*;
import com.youlai.common.rabbitmq.demo.manager.ConnectionManager;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @desc RabbitMQ 路由模型实战
*/
public class RoutingRecv {
public static void main(String[] args) throws IOException, TimeoutException {
// 1、创建队列,并建立队列与路由器绑定关系
RoutingRecv.declareQueues();
RoutingRecv.declareBindings();
// 2、消费消息查看消费结果
Channel channel = ConnectionManager.getConnection().createChannel();
channel.basicConsume("WebClientQueue", true, (consumerTag, message) -> {
System.out.println(consumerTag);
System.out.println("WebClientQueue:" + new String(message.getBody()));
}, consumerTag -> {
System.out.println(consumerTag);
});
channel.basicConsume("AppClientQueue", true, (consumerTag, message) -> {
System.out.println(consumerTag);
System.out.println("AppClientQueue:" + new String(message.getBody()));
}, consumerTag -> {
System.out.println(consumerTag);
});
}
public static void declareQueues() throws IOException, TimeoutException {
//Create a channel - do no't share the Channel instance
Channel channel = ConnectionManager.getConnection().createChannel();
//queueDeclare - (queueName, durable, exclusive, autoDelete, arguments)
channel.queueDeclare("WebClientQueue", true, false, false, null);
channel.queueDeclare("AppClientQueue", true, false, false, null);
channel.close();
}
public static void declareBindings() throws IOException, TimeoutException {
Channel channel = ConnectionManager.getConnection().createChannel();
//Create bindings - (queue, exchange, routingKey)
channel.queueBind("WebClientQueue", "my_exchange_direct", "info");
channel.queueBind("WebClientQueue", "my_exchange_direct", "error");
channel.queueBind("WebClientQueue", "my_exchange_direct", "warning");
channel.queueBind("AppClientQueue", "my_exchange_direct", "error");
channel.close();
}
}
Topic Exchange 模型
简单理解
Topic Exchange 消息模型需要指定路由器类型设置为Topic
,那么什么是 Topic
类型?
Topic: 将路由键和某模式进行匹配。此时队列需要绑定要一个模式上。符号
#
匹配一个或多个词,符号*
匹配不多不少一个词。因此audit.#
能够匹配到audit.irs.corporate
,但是audit.*
只会匹配到audit.irs
Topic Exchange 消息模型示意图
Topic Exchange 中的路由键设置规则
- Topic Exchange中的路由关键字必须包含零个或多个由
.
点分隔的单词,例如health.education
。 - Topic Exchange中的路由键通常称为路由模式。
- 路由键允许只包含 星号(
*
)和 井号 (#
)的正则表达式组成 - 星号(*****)表示正好允许一个字。
- 同样,井号(#)表示允许的单词数为零或更多。
- 点号(
.
)表示–单词定界符。多个关键术语用点定界符分隔。 - 如果路由模式为
health.\*
,则意味着以第一个单词为首的路由键运行状况发送的任何消息都将到达队列。例如,health.education
将到达此队列,但sports.health
将不起作用。
Topic Exchange中的消息流转过程
- 一个Queue队列通过路由键(P)绑定到 Exchange。
- Producer 将带有P路由键(K)的消息发送到 Topic Exchange。
- 如果P与K匹配,则消息被传递到队列。路由密钥匹配的确定如下所述。
- 订阅队列的使用者将收到消息。
代码实现
创建 RabbitMQ 单例连接对象
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ConnectionManager {
private static Connection connection = null;
public static Connection getConnection() {
if (connection == null) {
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.56.10");
factory.setPort(5672);
factory.setVirtualHost("/");
connection = factory.newConnection();
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
return connection;
}
}
消息生产者
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.youlai.common.rabbitmq.demo.manager.ConnectionManager;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @desc RabbitMQ 路由模型 - 通配符模式
* 简单说明:
* 1、路由模型需要指定交换机类型为 direct,交换机和队列之间通过路由键绑定,交换机只会把消息推送到符合路由键的队列中
*/
public class TopicSend {
public static void main(String[] args) throws IOException, TimeoutException {
TopicSend.declareExchange();
try (Channel channel = ConnectionManager.getConnection().createChannel()) {
String message = "Drink a lot of Water and stay Healthy!";
channel.basicPublish("my-topic-exchange", "health.education", null, message.getBytes());
message = "Learn something new everyday";
channel.basicPublish("my-topic-exchange", "education", null, message.getBytes());
message = "Stay fit in Mind and Body";
channel.basicPublish("my-topic-exchange", "education.health", null, message.getBytes());
} catch (Exception e) {
e.printStackTrace();
}
}
public static void declareExchange() throws IOException, TimeoutException {
Channel channel = ConnectionManager.getConnection().createChannel();
//Declare my-fanout-exchange
channel.exchangeDeclare("my-topic-exchange", BuiltinExchangeType.TOPIC, true);
channel.close();
}
}
消息消费者
import com.rabbitmq.client.*;
import com.youlai.common.rabbitmq.demo.manager.ConnectionManager;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class TopicRecv {
public static void main(String[] args) throws IOException, TimeoutException {
TopicSend.declareExchange();
TopicRecv.declareQueues();
TopicRecv.declareBindings();
Channel channel = ConnectionManager.getConnection().createChannel();
channel.basicConsume("HealthQueue", true, (consumerTag, message) -> {
System.out.println("\n\n=========== Health Queue ==========");
System.out.println(consumerTag);
System.out.println("HealthQueue: " + new String(message.getBody()));
System.out.println(message.getEnvelope());
}, consumerTag -> {
System.out.println(consumerTag);
});
channel.basicConsume("SportsQueue", true, (consumerTag, message) -> {
System.out.println("\n\n=========== Sports Queue ==========");
System.out.println(consumerTag);
System.out.println("SportsQueue: " + new String(message.getBody()));
System.out.println(message.getEnvelope());
}, consumerTag -> {
System.out.println(consumerTag);
});
channel.basicConsume("EducationQueue", true, (consumerTag, message) -> {
System.out.println("\n\n=========== Education Queue ==========");
System.out.println(consumerTag);
System.out.println("EducationQueue: " + new String(message.getBody()));
System.out.println(message.getEnvelope());
}, consumerTag -> {
System.out.println(consumerTag);
});
}
public static void declareQueues() throws IOException, TimeoutException {
//Create a channel - do no't share the Channel instance
Channel channel = ConnectionManager.getConnection().createChannel();
//queueDeclare - (queueName, durable, exclusive, autoDelete, arguments)
channel.queueDeclare("HealthQueue", true, false, false, null);
channel.queueDeclare("SportsQueue", true, false, false, null);
channel.queueDeclare("EducationQueue", true, false, false, null);
channel.close();
}
public static void declareBindings() throws IOException, TimeoutException {
Channel channel = ConnectionManager.getConnection().createChannel();
//Create bindings - (queue, exchange, routingKey)
channel.queueBind("HealthQueue", "my-topic-exchange", "health.*");
channel.queueBind("SportsQueue", "my-topic-exchange", "#.sports.*");
channel.queueBind("EducationQueue", "my-topic-exchange", "#.education");
channel.close();
}
}
Headers Exchange 模型
简单理解
Headers Exchange 模型需要指定路由器类型设置为Headers
,那么什么是 HeadersHeaders
类型?
Headers:Headers类型的exchange使用的比较少,它也是忽略routingKey的一种路由方式。是使用Headers来匹配的。Headers是一个键值对,可以定义成Hashtable。发送者在发送的时候定义一些键值对,接收者也可以再绑定时候传入一些键值对,两者匹配的话,则对应的队列就可以收到消息。匹配有两种方式all和any。这两种方式是在接收端必须要用键值"x-mactch"来定义。all代表定义的多个键值对都要满足,而any则代码只要满足一个就可以了。fanout,direct,topic exchange的routingKey都需要要字符串形式的,而headers exchange则没有这个要求,因为键值对的值可以是任何类型
Headers Exchange 消息模型示意图
Headers Exchange 中的消息流转过程
- 一个或多个队列使用标头属性(H)绑定(链接)到标头交换。
- 生产者将带有标头属性(MH)的消息发送到此Exchange。
- 如果MH与H匹配,则消息将转发到队列。
- 监听队列的使用者接收消息并对其进行处理。
代码实现
创建 RabbitMQ 单例连接对象
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ConnectionManager {
private static Connection connection = null;
public static Connection getConnection() {
if (connection == null) {
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.56.10");
factory.setPort(5672);
factory.setVirtualHost("/");
connection = factory.newConnection();
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
return connection;
}
}
消息生产者
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.youlai.common.rabbitmq.demo.manager.ConnectionManager;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
public class HeadersSend {
public static void main(String[] args) throws IOException, TimeoutException {
HeadersSend.declareExchange();
try (Channel channel = ConnectionManager.getConnection().createChannel()) {
String message = "Header Exchange example 1";
Map<String, Object> headerMap = new HashMap<>();
headerMap.put("h1", "Header1");
headerMap.put("h3", "Header3");
AMQP.BasicProperties properties = new AMQP.BasicProperties()
.builder().headers(headerMap).build();
channel.basicPublish("my-header-exchange", "", properties, message.getBytes());
message = "Header Exchange example 2";
headerMap.put("h2", "Header2");
properties = new AMQP.BasicProperties()
.builder().headers(headerMap).build();
channel.basicPublish("my-header-exchange", "", properties, message.getBytes());
} catch (Exception e) {
e.printStackTrace();
}
}
public static void declareExchange() throws IOException, TimeoutException {
Channel channel = ConnectionManager.getConnection().createChannel();
//Declare my-fanout-exchange
channel.exchangeDeclare("my-header-exchange", BuiltinExchangeType.HEADERS, true);
channel.close();
}
}
消息消费者
import com.rabbitmq.client.Channel;
import com.youlai.common.rabbitmq.demo.manager.ConnectionManager;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
public class HeadersRecv {
public static void main(String[] args) throws IOException, TimeoutException {
HeadersSend.declareExchange();
HeadersRecv.declareQueues();
HeadersRecv.declareBindings();
Channel channel = ConnectionManager.getConnection().createChannel();
channel.basicConsume("HealthQueue", true, ((consumerTag, message) -> {
System.out.println("\n\n=========== Health Queue ==========");
System.out.println(consumerTag);
System.out.println("HealthQueue: " + new String(message.getBody()));
System.out.println(message.getEnvelope());
}), consumerTag -> {
System.out.println(consumerTag);
});
channel.basicConsume("SportsQueue", true, ((consumerTag, message) -> {
System.out.println("\n\n ============ Sports Queue ==========");
System.out.println(consumerTag);
System.out.println("SportsQueue: " + new String(message.getBody()));
System.out.println(message.getEnvelope());
}), consumerTag -> {
System.out.println(consumerTag);
});
channel.basicConsume("EducationQueue", true, ((consumerTag, message) -> {
System.out.println("\n\n ============ Education Queue ==========");
System.out.println(consumerTag);
System.out.println("EducationQueue: " + new String(message.getBody()));
System.out.println(message.getEnvelope());
}), consumerTag -> {
System.out.println(consumerTag);
});
}
public static void declareQueues() throws IOException, TimeoutException {
//Create a channel - do no't share the Channel instance
Channel channel = ConnectionManager.getConnection().createChannel();
//queueDeclare - (queueName, durable, exclusive, autoDelete, arguments)
channel.queueDeclare("HealthQueue", true, false, false, null);
channel.queueDeclare("SportsQueue", true, false, false, null);
channel.queueDeclare("EducationQueue", true, false, false, null);
channel.close();
}
public static void declareBindings() throws IOException, TimeoutException {
Channel channel = ConnectionManager.getConnection().createChannel();
//Create bindings - (queue, exchange, routingKey, headers) - routingKey != null
Map<String, Object> healthArgs = new HashMap<>();
healthArgs.put("x-match", "any"); //Match any of the header
healthArgs.put("h1", "Header1");
healthArgs.put("h2", "Header2");
channel.queueBind("HealthQ", "my-header-exchange", "", healthArgs);
Map<String, Object> sportsArgs = new HashMap<>();
sportsArgs.put("x-match", "all"); //Match all of the header
sportsArgs.put("h1", "Header1");
sportsArgs.put("h2", "Header2");
channel.queueBind("SportsQ", "my-header-exchange", "", sportsArgs);
Map<String, Object> educationArgs = new HashMap<>();
educationArgs.put("x-match", "any"); //Match any of the header
educationArgs.put("h1", "Header1");
educationArgs.put("h2", "Header2");
channel.queueBind("EducationQ", "my-header-exchange", "", educationArgs);
channel.close();
}
}