RabbitMQ 之 Exchange
一、交换机相关概念
1、交换机的作用
RabbitMQ 在传递的消息过程中,生产者和队列之间是没有直接联系的,生产者生产的消息要推送到队列中需要借助于交换机,交换机就是生产者和队列的中间桥梁.交换机的工作内容非常简单,一方面它接收来自生产者的消息,另外一方面是将消息推送到队列中.
2、交换机的类型
交换机在接收到生产者生产的消息之后必须要知道该如何处理这些消息,是将消息推送到特定的队列上还是说丢弃他们,消息具体该如何处理就由交换机的类型来决定了,常用的交换机类型通常有 default、fanout、direct、headers、topic
声明(AMQP default) 默认的交换机
1 2 | // 第一个参数如果是空字符串("")则代表使用默认交换机 (AMQP default) channel.basicPublish( "" , QUEUE_NAME, null , message.getBytes(StandardCharsets.UTF_8)); |
声明 fanout、direct、headers、topic 类型的交换机
1 2 3 4 5 6 7 8 | // 声明一个 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、原理图
可以通过如下方式声明临时队列
1 2 | // 声明一个临时的队列 String queue = channel.queueDeclare().getQueue(); |
声明的临时队列如下,它是一个 AutoDelete、Exclusive 类型的队列
2、工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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 消费情况
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2020-11-11 Springboot thymeleaf <thymeleaf‐layout‐dialect.version>2.2.2</thymeleaf‐layout‐dialect.version>标签导入报错