RabbitMQ 之 Exchange
一、交换机相关概念
1、交换机的作用
RabbitMQ 在传递的消息过程中,生产者和队列之间是没有直接联系的,生产者生产的消息要推送到队列中需要借助于交换机,交换机就是生产者和队列的中间桥梁.交换机的工作内容非常简单,一方面它接收来自生产者的消息,另外一方面是将消息推送到队列中.
2、交换机的类型
交换机在接收到生产者生产的消息之后必须要知道该如何处理这些消息,是将消息推送到特定的队列上还是说丢弃他们,消息具体该如何处理就由交换机的类型来决定了,常用的交换机类型通常有 default、fanout、direct、headers、topic
声明(AMQP default) 默认的交换机
// 第一个参数如果是空字符串("")则代表使用默认交换机 (AMQP default)
channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
声明 fanout、direct、headers、topic 类型的交换机
// 声明一个 fanout 类型的交换机 exchange01
channel.exchangeDeclare("exchange01", BuiltinExchangeType.FANOUT);
// 声明一个 direct 类型的交换机 exchange02
channel.exchangeDeclare("exchange02", BuiltinExchangeType.DIRECT);
// 声明一个 headers 类型的交换机 exchange03
channel.exchangeDeclare("exchange03", BuiltinExchangeType.HEADERS);
// 声明一个 topic 类型的交换机 exchange04
channel.exchangeDeclare("exchange04", BuiltinExchangeType.TOPIC);
虽然交换机的类型有很多,但是在实际的应用中 headers 不常用,所以我们下面主要看一下 fanout、direct、topic 的用法
二、fanout
fanout 这种类型非常简单,它是将接收到的所有消息广播到它所知道的所有队列中,交换机和队列使用的 routingkey (binding key) 为空字符串("")
1、原理图
可以通过如下方式声明临时队列
// 声明一个临时的队列
String queue = channel.queueDeclare().getQueue();
声明的临时队列如下,它是一个 AutoDelete、Exclusive 类型的队列
2、工具类
public class RabbitmqUtils {
private static final String HOST_ADDRESS = "192.168.59.130";
private static final String USER_NAME = "admin";
private static final String PASSWORD = "admin123";
public static Channel getChannel() throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(HOST_ADDRESS);
factory.setUsername(USER_NAME);
factory.setPassword(PASSWORD);
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
return channel;
}
}
3、Consumer01
public class Consumer01 {
private static final String EXCHANGE_NAME = "exchange_xiaomaomao";
public static void main(String[] args) throws Exception {
// 自定义工具类获取信道
Channel channel = RabbitmqUtils.getChannel();
// 声明交换机
// 消费者端需要将交换机和队列进行绑定,这样交换机便能将消息推送到指定的队列
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
// 声明一个临时的队列
String queue = channel.queueDeclare().getQueue();
// 将交换机和队列进行绑定
// routingKey 使用空字符串("")
channel.queueBind(queue, EXCHANGE_NAME, "", null);
// 消息成功之后的回调
DeliverCallback deliverCallback = (String consumerTag, Delivery message) -> {
String msg = new String(message.getBody());
System.out.println(msg);
};
// 取消消费者的回调
CancelCallback cancelCallback = consumerTag -> {
System.out.println("取消消费者时的回调接口");
};
// 消费者消费消息
channel.basicConsume(queue, deliverCallback, cancelCallback);
System.out.println("Consumer01 开始消费");
}
}
4、Consumer02
public class Consumer02 {
private static final String EXCHANGE_NAME = "exchange_xiaomaomao";
public static void main(String[] args) throws Exception {
// 自定义工具类获取信道
Channel channel = RabbitmqUtils.getChannel();
// 声明交换机
// 消费者端需要将交换机和队列进行绑定,这样交换机便能将消息推送到指定的队列
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
// 声明一个临时的队列
String queue = channel.queueDeclare().getQueue();
// 将交换机和队列进行绑定
// routingKey 使用空字符串("")
channel.queueBind(queue, EXCHANGE_NAME, "", null);
// 消息成功之后的回调
DeliverCallback deliverCallback = (String consumerTag, Delivery message) -> {
String msg = new String(message.getBody());
System.out.println(msg);
};
// 取消消费者的回调
CancelCallback cancelCallback = consumerTag -> {
System.out.println("取消消费者时的回调接口");
};
// 消费者消费消息
channel.basicConsume(queue, deliverCallback, cancelCallback);
System.out.println("Consumer02 开始消费");
}
}
5、Producer
public class Producer {
private static final String EXCHANGE_NAME = "exchange_xiaomaomao";
public static void main(String[] args) throws Exception {
// 自定义工具类获取信道
Channel channel = RabbitmqUtils.getChannel();
// 声明一个 fanout 类型的交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
// 要发送的消息
String message = "xiaomaomao";
for (int i = 1; i < 6; i++) {
// 生产者推送消息到队列
// MessageProperties.PERSISTENT_TEXT_PLAIN : 持久化消息
channel.basicPublish(EXCHANGE_NAME, "", MessageProperties.PERSISTENT_TEXT_PLAIN, (message + i).getBytes(StandardCharsets.UTF_8));
}
System.out.println("Producer send message successfully...");
}
}
6、测试及结果
首先启动 Consumer01、Consumer02、然后再启动 Producer 发送消息
RabbitMQ 控制台 Exchanges
选择该交换机点进去查看详情
Consumer01、Consumer02 的消费情况如下
三、direct
通过上面的案例,我们知道 fanout 类型的交换机是通过广播的方式将它接收到的消息发送给它知道的所有队列,也就是说所有的队列都能收到相同的消息,但是在某些场景下是不适用的,例如我想把重要的消息和普通的消息分离开来,分别发送给不同的队列,使用 fanout 是做不到的,这个时候我们就可以通过 direct 类型的交换机来实现
1、原理图
2、Consumer01
public class Consumer01 {
private static final String EXCHANGE_NAME = "exchange_xiaomaomao";
private static final String IMPORTANT_QUEUE_NAME = "Queue01";
private static final String IMPORTANT_ROUTING_KEY = "important";
public static void main(String[] args) throws Exception {
// 自定义工具类获取信道
Channel channel = RabbitmqUtils.getChannel();
// 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
// 声明一个临时的队列
channel.queueDeclare(IMPORTANT_QUEUE_NAME, true, false, false, null);
// 将交换机和队列进行绑定
channel.queueBind(IMPORTANT_QUEUE_NAME, EXCHANGE_NAME, IMPORTANT_ROUTING_KEY, null);
// 消息成功之后的回调
DeliverCallback deliverCallback = (String consumerTag, Delivery message) -> {
String msg = new String(message.getBody());
System.out.println(msg);
};
// 取消消费者的回调
CancelCallback cancelCallback = consumerTag -> {
System.out.println("取消消费者时的回调接口");
};
// 消费者消费消息
channel.basicConsume(IMPORTANT_QUEUE_NAME, deliverCallback, cancelCallback);
System.out.println("Consumer01 开始消费");
}
}
3、Consumer02
public class Consumer02 {
private static final String EXCHANGE_NAME = "exchange_xiaomaomao";
private static final String NORMAL_QUEUE_NAME = "Queue02";
private static final String NORMAL_ROUTING_KEY = "normal";
public static void main(String[] args) throws Exception {
// 自定义工具类获取信道
Channel channel = RabbitmqUtils.getChannel();
// 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
// 声明一个临时的队列
channel.queueDeclare(NORMAL_QUEUE_NAME, true, false, false, null);
// 将交换机和队列进行绑定
channel.queueBind(NORMAL_QUEUE_NAME, EXCHANGE_NAME, NORMAL_ROUTING_KEY, null);
// 消息成功之后的回调
DeliverCallback deliverCallback = (String consumerTag, Delivery message) -> {
String msg = new String(message.getBody());
System.out.println(msg);
};
// 取消消费者的回调
CancelCallback cancelCallback = consumerTag -> {
System.out.println("取消消费者时的回调接口");
};
// 消费者消费消息
channel.basicConsume(NORMAL_QUEUE_NAME, deliverCallback, cancelCallback);
System.out.println("Consumer02 开始消费");
}
}
4、Producer
public class Producer {
private static final String EXCHANGE_NAME = "exchange_xiaomaomao";
private static final String IMPORTANT_QUEUE_NAME = "Queue01";
private static final String IMPORTANT_ROUTING_KEY = "important";
private static final String NORMAL_QUEUE_NAME = "Queue02";
private static final String NORMAL_ROUTING_KEY = "normal";
public static void main(String[] args) throws Exception {
// 自定义工具类获取信道
Channel channel = RabbitmqUtils.getChannel();
// 声明一个 fanout 类型的交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
// 要发送的消息
List<String> strList = new ArrayList<>();
strList.add("important1");
strList.add("important2");
strList.add("important3");
strList.add("normal1");
strList.add("normal2");
strList.add("normal3");
if (!CollectionUtils.isEmpty(strList)) {
strList.forEach((item) -> {
if (item.contains("important")) {
try {
channel.basicPublish(EXCHANGE_NAME, IMPORTANT_ROUTING_KEY, MessageProperties.PERSISTENT_TEXT_PLAIN, item.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
}
} else {
try {
channel.basicPublish(EXCHANGE_NAME, NORMAL_ROUTING_KEY, MessageProperties.PERSISTENT_TEXT_PLAIN, item.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
System.out.println("Producer send message successfully...");
}
}
5、测试及结果
查看交换机详情
查看队列消费情况
Consumer01、Consumer02 消费情况
四、topic
从上面的例子可以看出,direct 类型的交换机已经可以比较灵活的处理消息了,但是 direct 模式的 routing key 一旦给定就无法再发生变化了,为了使交换机适用于更加灵活的场景,我们引入了 topic 模式
topic 类型交换机的 routing key 是不能随意编写的,它需要满足一定的规范,首先它必须是一个单词列表,以点号(.)分隔开,这些单词可以是任意的单词,例如 ncu.tech.north、stock.query.show、make.use.toy 等等,当然这些单词的列表总长度不能超过 255 个字节,在规则列表中可以使用通配符来代替
* : 代替一个单词
# : 代替 0 个或者多个单词
1、原理图
2、Consumer01
public class Consumer01 {
private static final String EXCHANGE_NAME = "exchange_topic_demo";
private static final String FIRST_QUEUE_NAME = "Queue01";
private static final String FIRST_ROUTING_KEY = "*.rabbit.*";
public static void main(String[] args) throws Exception {
// 自定义工具类获取信道
Channel channel = RabbitmqUtils.getChannel();
// 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
// 声明一个临时的队列
channel.queueDeclare(FIRST_QUEUE_NAME, true, false, false, null);
// 将交换机和队列进行绑定
channel.queueBind(FIRST_QUEUE_NAME, EXCHANGE_NAME, FIRST_ROUTING_KEY, null);
// 消息成功之后的回调
DeliverCallback deliverCallback = (String consumerTag, Delivery message) -> {
String msg = new String(message.getBody());
System.out.println(message.getEnvelope().getRoutingKey() + " " + msg);
};
// 取消消费者的回调
CancelCallback cancelCallback = consumerTag -> {
System.out.println("取消消费者时的回调接口");
};
// 消费者消费消息
channel.basicConsume(FIRST_QUEUE_NAME, deliverCallback, cancelCallback);
System.out.println("Consumer01 开始消费,Consumer01 的 routingKey 为" + FIRST_ROUTING_KEY);
}
}
3、Consumer02
public class Consumer02 {
private static final String EXCHANGE_NAME = "exchange_topic_demo";
private static final String SECOND_QUEUE_NAME = "Queue02";
private static final String SECOND_ROUTING_KEY_1 = "#.clever";
private static final String SECOND_ROUTING_KEY_2 = "java.*.*";
public static void main(String[] args) throws Exception {
// 自定义工具类获取信道
Channel channel = RabbitmqUtils.getChannel();
// 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
// 声明一个临时的队列
channel.queueDeclare(SECOND_QUEUE_NAME, true, false, false, null);
// 将交换机和队列进行绑定(多重绑定)
channel.queueBind(SECOND_QUEUE_NAME, EXCHANGE_NAME, SECOND_ROUTING_KEY_1, null);
channel.queueBind(SECOND_QUEUE_NAME, EXCHANGE_NAME, SECOND_ROUTING_KEY_2, null);
// 消息成功之后的回调
DeliverCallback deliverCallback = (String consumerTag, Delivery message) -> {
String msg = new String(message.getBody());
System.out.println(message.getEnvelope().getRoutingKey() + " " + msg);
};
// 取消消费者的回调
CancelCallback cancelCallback = consumerTag -> {
System.out.println("取消消费者时的回调接口");
};
// 消费者消费消息
channel.basicConsume(SECOND_QUEUE_NAME, deliverCallback, cancelCallback);
System.out.println("Consumer02 开始消费,Consumer02 的 routingKey 为" + SECOND_ROUTING_KEY_1 + " " + SECOND_ROUTING_KEY_2);
}
}
4、Producer
public class Producer {
private static final String EXCHANGE_NAME = "exchange_topic_demo";
public static void main(String[] args) throws Exception {
// 自定义工具类获取信道
Channel channel = RabbitmqUtils.getChannel();
// 声明一个 fanout 类型的交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
// "*.rabbit.*" "#.clever" "java.*.*"
Map<String, String> map = new HashMap<>();
map.put("python.rabbit.nothing", "被队列 Q1 接收到");
map.put("java.python.hello", "被队列 Q2 接收到");
map.put("quick.rabbit.clever", "被队列 Q1 Q2 接收到");
map.put("java.rabbit.fox", "被队列 Q1 Q2 接收到");
map.entrySet().forEach((item) -> {
try {
// 发送消息
channel.basicPublish(EXCHANGE_NAME, item.getKey(), MessageProperties.PERSISTENT_TEXT_PLAIN, item.getValue().getBytes(StandardCharsets.UTF_8));
System.out.println("发送的消息为; " + item.getKey()+"----" + item.getValue());
} catch (IOException e) {
e.printStackTrace();
}
});
System.out.println("Producer send message successfully...");
}
}
5、测试及结果
查看交换机详情
查看队列情况
Consumer01、Consumer02 消费情况