4.rabbitmq--路由模式

RabbitMQ路由模式详解
本文深入探讨了RabbitMQ的路由模式,介绍了如何通过路由键实现精确的消息投递,使生产者能够针对不同的消费者需求发送特定类型的消息,提高了消息传递的效率和准确性。

rabbitmq--路由模式

想象之前的订阅发布模式: 一个生产者,多个消费者,每一个消费者都有自己的一个队列,生产者没有将消息直接发送到队列,而是发送到了交换机,每个队列绑定交换机,生产者发送的消息经过交换机,到达队列,实现一个消息被多个消费者获取的目的。

需要注意的是,如果将消息发送到一个没有队列绑定的exchange上面,那么该消息将会丢失,这是因为在rabbitMQ中exchange不具备存储消息的能力,只有队列具备存储消息的能力。

但是如果想象下这样的一个场景:生产者有新增商品,修改商品,删除商品的消息,消费者包含前台系统和搜索系统,要求前台系统接收修改和删除商品的消息,搜索系统接收新增商品、修改商品和删除商品的消息。所以使用这种订阅模式实现商品数据的同步并不合理。因此我们介绍下一种模式:路由模式。

这种模式添加了一个路由键,生产者发布消息的时候添加路由键,消费者绑定队列到交换机时添加键值,这样就可以接收到需要接收的消息。

 生产者:

 1 package com.maozw.mq.routing;
 2 
 3 import com.maozw.mq.config.RabbitConfig;
 4 import com.rabbitmq.client.Channel;
 5 import org.slf4j.Logger;
 6 import org.slf4j.LoggerFactory;
 7 import org.springframework.amqp.rabbit.connection.Connection;
 8 import org.springframework.amqp.rabbit.connection.ConnectionFactory;
 9 import org.springframework.beans.factory.annotation.Autowired;
10 import org.springframework.web.bind.annotation.PathVariable;
11 import org.springframework.web.bind.annotation.RequestMapping;
12 import org.springframework.web.bind.annotation.RestController;
13 
14 import java.io.IOException;
15 import java.util.concurrent.TimeoutException;
16 
17 import static org.apache.log4j.varia.ExternallyRolledFileAppender.OK;
18 
19 /**
20  * work 模式
21  * 两种分发: 轮询分发 + 公平分发
22  * 轮询分发:消费端:自动确认消息;boolean autoAck = true;
23  * 公平分发: 消费端:手动确认消息 boolean autoAck = false; channel.basicAck(envelope.getDeliveryTag(),false);
24  *
25  * @author MAOZW
26  * @Description: ${todo}
27  * @date 2018/11/26 15:06
28  */
29 @RestController
30 @RequestMapping("/routing")
31 public class RoutingProducer {
32     private static final Logger LOGGER = LoggerFactory.getLogger(RoutingProducer.class);
33     @Autowired
34     RabbitConfig rabbitConfig;
35 
36 
37     @RequestMapping("/send")
38     public String send() throws IOException, TimeoutException {
39         Connection connection = null;
40         Channel channel= null;
41         try {
42             ConnectionFactory connectionFactory = rabbitConfig.connectionFactory();
43             connection = connectionFactory.createConnection();
44             channel = connection.createChannel(false);
45 
46             /**
47              * 申明交换机 以及type
48              */
49             channel.exchangeDeclare(RabbitConfig.EXCHANGE_ROUTE,"direct");
50 
51             /**
52              * 发送消息
53              * 每个消费者 发送确认消息之前,消息队列不会发送下一个消息给消费者,一次只处理一个消息
54              * 自动模式无需设置下面设置
55              */
56             int prefetchCount = 1;
57             channel.basicQos(prefetchCount);
58 
59             String Hello = ">>>> Hello EXCHANGE_ROUTE <<<<";
60             for (int i = 0; i < 5; i++) {
61                 String message = Hello + i;
62                 if (i == 3){
63                     channel.basicPublish(RabbitConfig.EXCHANGE_ROUTE, RabbitConfig.ROUTINGKEY_A, null, message.getBytes());
64                 }else{
65                     channel.basicPublish(RabbitConfig.EXCHANGE_ROUTE, RabbitConfig.ROUTINGKEY_B, null, message.getBytes());
66                 }
67                 LOGGER.info("生产消息: " + message);
68             }
69             return "OK";
70         }catch (Exception e) {
71 
72         } finally {
73             connection.close();
74             channel.close();
75             return OK;
76         }
77     }
78 }

消费者1 

 1 package com.maozw.mq.routing;
 2 
 3 import com.maozw.mq.config.RabbitConfig;
 4 import com.rabbitmq.client.AMQP;
 5 import com.rabbitmq.client.Channel;
 6 import com.rabbitmq.client.DefaultConsumer;
 7 import com.rabbitmq.client.Envelope;
 8 import org.slf4j.Logger;
 9 import org.slf4j.LoggerFactory;
10 import org.springframework.amqp.rabbit.connection.Connection;
11 import org.springframework.amqp.rabbit.connection.ConnectionFactory;
12 
13 import java.io.IOException;
14 
15 /**
16  * @author MAOZW
17  * @Description: ${todo}
18  * @date 2018/11/26 15:06
19  */
20 
21 public class RoutingConsumer {
22     private static final Logger LOGGER = LoggerFactory.getLogger(RoutingConsumer.class);
23 
24     public static void main(String[] args) throws IOException {
25         ConnectionFactory connectionFactory = RabbitConfig.getConnectionFactory();
26         Connection connection = connectionFactory.createConnection();
27         Channel channel = connection.createChannel(false);
28         /**
29          * 创建队列申明
30          */
31         boolean durable = true;
32         channel.queueDeclare(RabbitConfig.QUEUE_ROUTE, durable, false, false, null);
33         /**
34          * 绑定队列到交换机
35          */
36         channel.queueBind(RabbitConfig.QUEUE_ROUTE, RabbitConfig.EXCHANGE_ROUTE,RabbitConfig.ROUTINGKEY_A);
37 
38         /**
39          * 改变分发规则
40          */
41         channel.basicQos(1);
42         DefaultConsumer consumer = new DefaultConsumer(channel) {
43             @Override
44             public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
45                 super.handleDelivery(consumerTag, envelope, properties, body);
46                 System.out.println("[1] 接口数据 : " + new String(body, "utf-8"));
47                 try {
48                     Thread.sleep(100);
49                 } catch (InterruptedException e) {
50                     e.printStackTrace();
51                 } finally {
52                     System.out.println("[1] done!");
53                     //消息应答:手动回执,手动确认消息
54                     channel.basicAck(envelope.getDeliveryTag(),false);
55                 }
56             }
57         };
58         //监听队列
59         /**
60          * autoAck 消息应答
61          *  默认轮询分发打开:true :这种模式一旦rabbitmq将消息发送给消费者,就会从内存中删除该消息,不关心客户端是否消费正常。
62          *  使用公平分发需要关闭autoAck:false  需要手动发送回执
63          */
64         boolean autoAck = false;
65         channel.basicConsume(RabbitConfig.QUEUE_ROUTE,autoAck, consumer);
66     }
67     
68 }
 1 package com.maozw.mq.routing;
 2 
 3 import com.maozw.mq.config.RabbitConfig;
 4 import com.rabbitmq.client.AMQP;
 5 import com.rabbitmq.client.Channel;
 6 import com.rabbitmq.client.DefaultConsumer;
 7 import com.rabbitmq.client.Envelope;
 8 import org.slf4j.Logger;
 9 import org.slf4j.LoggerFactory;
10 import org.springframework.amqp.rabbit.connection.Connection;
11 import org.springframework.amqp.rabbit.connection.ConnectionFactory;
12 
13 import java.io.IOException;
14 
15 /**
16  * @author MAOZW
17  * @Description: ${todo}
18  * @date 2018/11/26 15:06
19  */
20 
21 public class RoutingConsumer2 {
22     private static final Logger LOGGER = LoggerFactory.getLogger(RoutingConsumer2.class);
23 
24     public static void main(String[] args) throws IOException {
25         ConnectionFactory connectionFactory = RabbitConfig.getConnectionFactory();
26         Connection connection = connectionFactory.createConnection();
27         Channel channel = connection.createChannel(false);
28         /**
29          * 创建队列申明
30          */
31         boolean durable = true;
32         channel.queueDeclare(RabbitConfig.QUEUE_ROUTE2, durable, false, false, null);
33         /**
34          * 绑定队列到交换机
35          */
36         channel.queueBind(RabbitConfig.QUEUE_ROUTE2, RabbitConfig.EXCHANGE_ROUTE,RabbitConfig.ROUTINGKEY_B);
37 
38         /**
39          * 改变分发规则
40          */
41         channel.basicQos(1);
42         DefaultConsumer consumer = new DefaultConsumer(channel) {
43             @Override
44             public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
45                 super.handleDelivery(consumerTag, envelope, properties, body);
46                 System.out.println("[2] 接口数据 : " + new String(body, "utf-8"));
47                 try {
48                     Thread.sleep(100);
49                 } catch (InterruptedException e) {
50                     e.printStackTrace();
51                 } finally {
52                     System.out.println("[2] done!");
53                     //消息应答:手动回执,手动确认消息
54                     channel.basicAck(envelope.getDeliveryTag(),false);
55                 }
56             }
57         };
58         //监听队列
59         /**
60          * autoAck 消息应答
61          *  默认轮询分发打开:true :这种模式一旦rabbitmq将消息发送给消费者,就会从内存中删除该消息,不关心客户端是否消费正常。
62          *  使用公平分发需要关闭autoAck:false  需要手动发送回执
63          */
64         boolean autoAck = false;
65         channel.basicConsume(RabbitConfig.QUEUE_ROUTE2,autoAck, consumer);
66     }
67 }

 

转载于:https://www.cnblogs.com/Mao-admin/p/10034095.html

Overview Messages Message rates +/- Virtual host Name Type Features State Ready Unacked Total incoming deliver / get ack / data.calced.domian-linkage-consumer classic D Lim Args idle 0 0 0 / data.calced.dsf-xazz-consumer classic D Lim Args idle 0 0 0 / data.calced.location-his-consumer classic D Lim Args idle 0 0 0 / data.calced.location-rt-consumer classic D Lim Args idle 0 0 0 / data.calced.location-rt-his-consumer classic D Lim Args idle 0 0 0 / data.mqtt.device-alarm-change-consumer classic D Lim Args idle 0 0 0 / data.mqtt.pad-up-consumer classic D Lim Args idle 0 0 0 / data.status.device-card-consumer classic D Lim Args idle 0 0 0 / data.status.device-hisfinish-consumer classic D Lim Args idle 0 0 0 / data.status.device-pwr-consumer classic D Lim Args idle 0 0 0 / data.status.device-sta-consumer classic D Lim Args idle 0 0 0 / data.status.domian-linkage-consumer classic D Lim Args idle 0 0 0 / data.status.location-hisfinish-consumer classic D Lim Args idle 0 0 0 / data.status.unique-att-consumer classic D Lim Args idle 0 0 0 / data.switch.location-switch-consumer classic D Lim Args idle 0 0 0 / domian.event.area-inout-screen-consumer classic D Lim Args idle 0 0 0 / domian.event.device-alarm-call-consumer classic D Lim Args idle 0 0 0 / domian.event.location-area-att-consumer classic D Lim Args idle 0 0 0 / domian.event.location-mine-att-consumer classic D Lim Args idle 0 0 0 / domian.event.location-mine-consumer classic D Lim Args idle 0 0 0 / domian.event.location-mine-device-consumer classic D Lim Args idle 0 0 0 / locationCarSta.anonymous.dQ5O4xX-R5Ki4AlOFzYfnA classic AD Excl ML Args idle 0 0 0 /这几个队列怎么绑定的是AMQ.Topic这个交换机
最新发布
09-23
<think> 我们注意到在RabbitMQ管理界面中,这些队列的Features列显示为“D Lim Args”,其中: - D:Durable(持久化队列) - Lim:表示队列设置了最大长度(x-max-length)或最大字节大小(x-max-length-bytes) - Args:表示队列有其他参数 但是,从提供的信息中,并没有直接显示这些队列绑定到哪个交换机。在RabbitMQ中,队列必须绑定到交换机才能接收消息。默认情况下,队列会绑定到默认交换机(名称为空字符串),类型为direct,绑定路由键等于队列名称。 然而,您提到这些队列绑定到了AMQ.Topic交换机。在RabbitMQ中,AMQ.Topic是内置的topic类型交换机。队列绑定到AMQ.Topic需要通过绑定键(binding key)进行绑定。 由于管理界面提供的信息有限,我们无法直接看到绑定关系。通常,在RabbitMQ管理界面的“Queues”标签页,点击队列名称,进入队列详情,可以看到该队列的所有绑定(Bindings),包括绑定的交换机和绑定键。 但是,根据您提供的信息,这些队列都是classic类型,且状态为idle,消息数都为0。如果要确认它们是否绑定到AMQ.Topic,我们可以通过RabbitMQ的管理API或者命令行工具查询。 以下是通过命令行查看队列绑定的示例(使用rabbitmqadmin): ```bash rabbitmqadmin list bindings ``` 或者通过HTTP API: ```bash curl -u guest:guest http://localhost:15672/api/bindings ``` 如果确定这些队列需要绑定到AMQ.Topic,那么通常在创建队列后,需要显式地将队列绑定到AMQ.Topic交换机,并指定绑定键。 下面是一个使用Java客户端创建队列并绑定到AMQ.Topic交换机的示例: ```java import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; public class BindQueueToTopicExchange { private static final String EXCHANGE_NAME = "amq.topic"; public static void main(String[] args) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) { // 假设队列名为"data.calced.domian-linkage-consumer" String queueName = "data.calced.domian-linkage-consumer"; // 声明队列(如果队列已存在,此操作不会重复创建,但参数必须一致) // 参数根据队列实际参数设置,比如持久化、最大长度等 Map<String, Object> arguments = new HashMap<>(); arguments.put("x-max-length", 10000); // 例如,设置最大消息数量为10000 channel.queueDeclare(queueName, true, false, false, arguments); // 绑定队列到AMQ.Topic交换机,并指定绑定键(binding key) String bindingKey = "data.calced.domain-linkage.#"; // 根据实际路由模式设置 channel.queueBind(queueName, EXCHANGE_NAME, bindingKey); System.out.println("Queue '" + queueName + "' bound to exchange '" + EXCHANGE_NAME + "' with key '" + bindingKey + "'"); } } } ``` ### 解释: 1. **声明队列**:使用`channel.queueDeclare`声明队列。参数包括: - `queueName`:队列名称 - `true`:持久化队列(durable) - `false`:非排他(exclusive) - `false`:非自动删除(auto-delete) - `arguments`:队列的其他参数,例如设置最大长度(`x-max-length`)等。 2. **绑定队列**:使用`channel.queueBind`将队列绑定到交换机。参数包括: - `queueName`:要绑定的队列 - `EXCHANGE_NAME`:交换机名称(这里为"amq.topic") - `bindingKey`:绑定键,用于匹配消息的路由键(可以使用通配符) ### 如何确保队列正确绑定到AMQ.Topic? 1. **检查绑定关系**: - 通过管理界面查看队列的绑定。 - 或者使用命令行:`rabbitmqctl list_bindings` 2. **绑定键模式**: - 绑定键的模式应该与生产者发送消息时使用的路由键匹配。例如,如果生产者发送的路由键是`data.calced.domain-linkage.event`,那么绑定键可以是`data.calced.domain-linkage.#`(匹配多级)或`data.calced.domain-linkage.*`(匹配一级)。 ### 注意事项: - **内置交换机**:`amq.topic`是RabbitMQ内置的topic类型交换机,默认已存在,不需要再次声明。但注意,不能删除内置交换机。 - **权限**:确保连接RabbitMQ的用户有权限操作该交换机和队列(包括绑定权限)。 - **已存在的队列**:如果队列已经存在,重新绑定需要先解绑原有的绑定(如果需要改变绑定键)。 ### 相关问题: §§相关问题§§: 1. RabbitMQ中如何查看队列与交换机的绑定关系? 2. 内置交换机`amq.topic`的默认特性是什么? 3. 如何设置队列的最大长度(x-max-length)以及超过最大长度后的行为? 4.RabbitMQ中,如何使用通配符绑定键实现灵活的消息路由? </think> ### RabbitMQ 队列绑定机制深度解析 根据您提供的队列列表和关注点,我将详细解释这些队列如何绑定到 AMQ.Topic 交换机,并提供完整的解决方案。 #### 问题核心:绑定机制分析 这些队列(如 `data.calced.domain-linkage-consumer` 等)都是经典的 RabbitMQ 队列(classic),它们绑定到 AMQ.Topic 交换机的方式主要通过: 1. **声明队列时指定默认绑定**(最常见方式) 2. **使用 `queueBind` 方法显式绑定** 3. **通过 Spring AMQP 配置自动绑定** ```text /data.calced.domain-linkage-consumer -> AMQ.Topic (binding key: data.calced.domain-linkage.#) /data.status.device-sta-consumer -> AMQ.Topic (binding key: data.status.device-sta.*) ``` ### 完整绑定解决方案 #### 方法 1:使用 RabbitMQ Java 客户端显式绑定 ```java import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import java.util.HashMap; import java.util.Map; public class TopicQueueBinder { private static final String EXCHANGE_NAME = "amq.topic"; public static void main(String[] args) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) { // 定义所有需要绑定的队列 String[] queues = { "data.calced.domain-linkage-consumer", "data.status.device-sta-consumer", "domian.event.location-area-att-consumer", // 添加其他队列... }; // 为每个队列定义对应的绑定模式 Map<String, String> bindingPatterns = new HashMap<>(); bindingPatterns.put("data.calced.domain-linkage-consumer", "data.calced.domain-linkage.#"); bindingPatterns.put("data.status.device-sta-consumer", "data.status.device-sta.*"); bindingPatterns.put("domian.event.location-area-att-consumer", "domian.event.location-area-att.#"); // 添加其他绑定模式... // 声明并绑定所有队列 for (String queue : queues) { // 声明队列(持久化,非独占,非自动删除) Map<String, Object> args = new HashMap<>(); args.put("x-max-length", 10000); // 对应 Lim 特性 channel.queueDeclare(queue, true, false, false, args); // 绑定到 AMQ.Topic 交换机 String bindingKey = bindingPatterns.get(queue); channel.queueBind(queue, EXCHANGE_NAME, bindingKey); System.out.println("Queue '" + queue + "' bound to AMQ.Topic with key: " + bindingKey); } } } } ``` #### 方法 2:使用 Spring Boot 自动配置绑定 ```java @Configuration public class RabbitMQConfig { @Bean public Declarables topicBindings() { return new Declarables( // 定义队列 new Queue("data.calced.domain-linkage-consumer", true, false, false, queueArgs()), new Queue("data.status.device-sta-consumer", true, false, false, queueArgs()), // ... 其他队列 // 绑定到内置的 amq.topic 交换机 new Binding("data.calced.domain-linkage-consumer", Binding.DestinationType.QUEUE, "amq.topic", "data.calced.domain-linkage.#", null), new Binding("data.status.device-sta-consumer", Binding.DestinationType.QUEUE, "amq.topic", "data.status.device-sta.*", null) // ... 其他绑定 ); } private Map<String, Object> queueArgs() { Map<String, Object> args = new HashMap<>(); args.put("x-max-length", 10000); // 限制队列长度 args.put("x-overflow", "reject-publish"); // 超过长度拒绝新消息 return args; } } ``` #### 方法 3:使用 RabbitMQ HTTP API 批量绑定 ```bash #!/bin/bash # 批量绑定队列到 AMQ.Topic RABBIT_HOST="localhost" CREDS="guest:guest" declare -A BINDINGS=( ["data.calced.domain-linkage-consumer"]="data.calced.domain-linkage.#" ["data.status.device-sta-consumer"]="data.status.device-sta.*" ["domian.event.location-area-att-consumer"]="domian.event.location-area-att.#" # 添加其他队列绑定... ) for queue in "${!BINDINGS[@]}"; do bindingKey=${BINDINGS[$queue]} curl -u $CREDS -X POST \ -H "Content-Type: application/json" \ -d "{\"routing_key\":\"$bindingKey\"}" \ http://$RABBIT_HOST:15672/api/bindings/%2F/e/amq.topic/q/$queue done ``` ### 队列特性详解(D Lim Args) | 特性 | 含义 | 配置方式 | |------|------|----------| | **D (Durable)** | 队列持久化,Broker重启后保留 | `queueDeclare(queue, true, ...)` | | **Lim (Length Limit)** | 队列长度限制 | `args.put("x-max-length", 10000)` | | **Args (Arguments)** | 额外队列参数 | 自定义参数Map | ### 绑定验证方法 1. **RabbitMQ 管理界面验证** ```bash # 查看特定队列的绑定 curl -u guest:guest http://localhost:15672/api/queues/%2F/data.calced.domain-linkage-consumer/bindings ``` 2. **Java 代码验证绑定** ```java // 检查队列绑定 AMQP.Queue.BindOk ok = channel.queueBindCheck( "data.calced.domain-linkage-consumer", "amq.topic", "data.calced.domain-linkage.#"); System.out.println("Binding exists: " + (ok != null)); ``` 3. **命令行工具验证** ```bash rabbitmqadmin list bindings exchange name='amq.topic' ``` ### 最佳实践建议 1. **命名一致性**: ```java // 队列名与绑定键保持关联 String queueName = "data.calced.domain-linkage-consumer"; String bindingKey = "data.calced.domain-linkage.#"; ``` 2. **通配符使用规范**: - 使用 `#` 匹配多级路径 (`data.calced.#`) - 使用 `*` 匹配单级路径 (`data.calced.*.metric`) 3. **异常处理机制**: ```java try { channel.queueBind(queue, "amq.topic", bindingKey); } catch (AlreadyClosedException e) { log.error("Binding failed: {}", e.getMessage()); // 重试或降级处理 } ``` 4. **监控与告警**: ```bash # 监控未绑定队列 rabbitmqadmin list queues name consumers | grep "0$" ``` ### 生产环境应用示例 **智能设备消息路由系统**: ```java public void routeDeviceMessage(String deviceType, String eventType, String message) { String routingKey = String.format("device.%s.event.%s", deviceType, eventType); channel.basicPublish("amq.topic", routingKey, null, message.getBytes()); // 自动创建消费队列(如果不存在) String queueName = "device." + deviceType + ".consumer"; if (!queueExists(queueName)) { createQueueWithBinding(queueName, "device." + deviceType + ".#"); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值