基本概念
发布订阅
发布订阅模式和之前两个模式的区别是允许将同一消息发送给多个消费者,实现方式是加入了exchange(交换机)
交换机
RabbitMQ 消息传递模型的核心思想是:生产者生产的消息从不会直接发送到队列,只能将消息发送到交换机(exchange),简单模式和工作模式使用的是默认交换机 AMQP default (空字符串表示)
临时队列
生成临时队列,当消费者断开和队列的连接时,队列就自动删除,示例代码如下:
String queueName = channel.queueDeclare().getQueue(); // 返回的是队列的随机名称
Fanout
扇出模式,也叫广播模式,此模式下交换机会将生产者发送的消息全都路由到每一个跟其绑定的队列,示意图如下:
代码实现(消费者 1):
Channel channel = RabbitMqUtils.getChannel();
// 声明一个交换机
channel.exchangeDeclare("log", "fanout");
// 声明一个临时队列
String queueName = channel.queueDeclare().getQueue();
//绑定交换机和队列
channel.queueBind(queueName, "log", "");
System.out.println("ReceiveLogs01等待接收消息,即将打印接收到的消息............");
channel.basicConsume(queueName, true, ((consumerTag, message) -> {
System.out.println("ReceiveLogs01打印接收到的消息:" + new String(message.getBody()));
}),consumerTag -> {
// 消费者被关闭时执行的操作
});
代码实现(消费者 2):
Channel channel = RabbitMqUtils.getChannel();
// 声明一个交换机
channel.exchangeDeclare("log", "fanout");
// 声明一个临时队列
String queueName = channel.queueDeclare().getQueue();
//绑定交换机和队列
channel.queueBind(queueName, "log", "");
System.out.println("ReceiveLogs02等待接收消息,即将打印接收到的消息............");
channel.basicConsume(queueName, true, ((consumerTag, message) -> {
System.out.println("ReceiveLogs02打印接收到的消息:" + new String(message.getBody()));
}),consumerTag -> {
// 消费者被关闭时执行的操作
});
代码实现(生产者):
Channel channel = RabbitMqUtils.getChannel();
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String message = scanner.next();
channel.basicPublish("log", "", null, message.getBytes());
System.out.println("生产者发出消息:" + message);
}
Direct
路由模式,也叫直接模式,消息只去到它绑定的 routingKey 队列,如果 exchange 绑定的多个队列的 key 都相同,此时表现的作用就与 fanout 模式一致,示意图如下:
需求
生产者可以将日志消息发送到交换机,并根据不同的日志级别(路由键)将消息路由到相应的队列,消费者 1 可以处理 “info” 和 “warning” 级别的日志消息,而消费者 2 可以处理 “error” 级别的日志消息
代码实现
消费者 1:
Channel channel = RabbitMqUtils.getChannel();
// 声明一个交换机
channel.exchangeDeclare("direct_log", BuiltinExchangeType.DIRECT);
// 声明一个队列
channel.queueDeclare("console", false, false, false, null);
// 绑定
channel.queueBind("console", "direct_log", "info");
channel.queueBind("console", "direct_log", "warning");
// 消费
channel.basicConsume("console", true, (consumerTag, message) -> {
System.out.println("ReceiveLogsDirect01打印接收到的消息:" + new String(message.getBody()));
}, consumerTag -> {
// 消费者被关闭时执行的操作
});
消费者 2:
Channel channel = RabbitMqUtils.getChannel();
// 声明一个交换机
channel.exchangeDeclare("direct_log", BuiltinExchangeType.DIRECT);
// 声明一个队列
channel.queueDeclare("disk", false, false, false, null);
// 绑定
channel.queueBind("disk", "direct_log", "error");
// 消费
channel.basicConsume("disk", true, (consumerTag, message) -> {
System.out.println("ReceiveLogsDirect02打印接收到的消息:" + new String(message.getBody()));
}, consumerTag -> {
// 消费者被关闭时执行的操作
});
生产者:
Channel channel = RabbitMqUtils.getChannel();
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String message = scanner.next();
// 指定不同 routingKey 通过交换机路由到不同的队列
channel.basicPublish("direct_log", "error", null, message.getBytes());
// channel.basicPublish("direct_log", "info", null, message.getBytes());
// channel.basicPublish("direct_log", "warning", null, message.getBytes());
System.out.println("生产者发出消息:" + message);
}
Topic
Direct 的路由规则是精确匹配,而 Topic 交换机则支持通配符匹配,满足多个条件的话也只会被队列接收一次,其中 routingKey
必须是多个单词的组合,并且用 . 分割,与 Direct 对比更加的灵活
- #:代表 0 个或多个单词.
- *:代表 1 个单词

特殊情况:
- 当一个队列绑定键是 #,那么这个队列将接收所有数据,作用与 fanout 模式相同
- 如果队列绑定键当中没有 # 和 * 出现,作用与 direct 模式相同
代码实现
消费者 1:
Channel channel = RabbitMqUtils.getChannel();
// 声明交换机
channel.exchangeDeclare("topic_logs", BuiltinExchangeType.TOPIC);
// 声明队列
channel.queueDeclare("Q1", false, false, false, null);
// 绑定
channel.queueBind("Q1", "topic_logs", "*.orange.*");
System.out.println("ReceiveLogsTopic01等待接收消息..............");
// 消费消息
channel.basicConsume("Q1", true, ((consumerTag, message) -> {
System.out.println("ReceiveLogsTopic01打印接收到的消息:" + new String(message.getBody()));
System.out.println("routingKey为{}" + message.getEnvelope().getRoutingKey());
}),consumerTag -> {
// 消费者被关闭时执行的操作
});
消费者 2:
Channel channel = RabbitMqUtils.getChannel();
// 声明交换机
channel.exchangeDeclare("topic_logs", BuiltinExchangeType.TOPIC);
// 声明队列
channel.queueDeclare("Q2", false, false, false, null);
// 绑定
channel.queueBind("Q2", "topic_logs", "*.*.rabbit");
channel.queueBind("Q2", "topic_logs", "lazy.#");
System.out.println("ReceiveLogsTopic02等待接收消息..............");
// 消费消息
channel.basicConsume("Q2", true, ((consumerTag, message) -> {
System.out.println("ReceiveLogsTopic02打印接收到的消息:" + new String(message.getBody()));
System.out.println("routingKey为{}" + message.getEnvelope().getRoutingKey());
}),consumerTag -> {
// 消费者被关闭时执行的操作
});
生产者:
Channel channel = RabbitMqUtils.getChannel();
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String routingKey = scanner.next();
channel.basicPublish("topic_logs", routingKey, null, routingKey.getBytes());
System.out.println(("生产者发出消息:" + routingKey)); // 这里为了方便演示把 routingKey 作为消息
}