RabbitMq多数据源创建队列混乱

Rabbitmq是做什么的?

这个不是今天要关注的问题核心,不多介绍,网上搜一下,都是介绍

使用场景

由于业务需求,一个服务需要连接多个rabbitmq数据源,且需要动态创建交换机,队列和绑定,并针对其中部分队列进行监听消费。

代码实现

1、多数据源配置

    package com.example.multirabbitmqsource.config;
    
    
    import lombok.Data;
    import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
    import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
    import org.springframework.amqp.rabbit.connection.ConnectionFactory;
    import org.springframework.amqp.rabbit.core.RabbitAdmin;
    import org.springframework.amqp.rabbit.core.RabbitTemplate;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * rabbitmq多数据源
     * @since 2023/11/14 10:03:19
     */
    @Configuration
    @ConfigurationProperties("spring.rabbitmq")
    @Data
    public class RabbitMQConfig {
        //数据源一
        @Value("${spring.rabbitmq.first-host}")
        private String firstHost;
        @Value("${spring.rabbitmq.first-port}")
        private int firstPort;
        @Value("${spring.rabbitmq.first-username}")
        private String firstUsername;
        @Value("${spring.rabbitmq.first-password}")
        private String firstPassword;
        @Value("${spring.rabbitmq.first-virtualhost}")
        private String firstVirtualHost;
    
        //数据源二
        @Value("${spring.rabbitmq.second-host}")
        private String secondHost;
        @Value("${spring.rabbitmq.second-port}")
        private int secondPort;
        @Value("${spring.rabbitmq.second-username}")
        private String secondUsername;
        @Value("${spring.rabbitmq.second-password}")
        private String secondPassword;
        @Value("${spring.rabbitmq.second-virtualhost}")
        private String secondVirtualHost;
    
        //数据源3
        @Value("${spring.rabbitmq.third-host}")
        private String thirdHost;
        @Value("${spring.rabbitmq.third-port}")
        private int thirdPort;
        @Value("${spring.rabbitmq.third-username}")
        private String thirdUsername;
        @Value("${spring.rabbitmq.third-password}")
        private String thirdPassword;
        @Value("${spring.rabbitmq.third-virtual-host}")
        private String thirdVirtualHost;
    
        public ConnectionFactory connectionFactory(String host, int port, String password, String username,
                                                   String virtualHost) {
            CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
            connectionFactory.setHost(host);
            connectionFactory.setPort(port);
            connectionFactory.setUsername(username);
            connectionFactory.setPassword(password);
            connectionFactory.setVirtualHost(virtualHost);
            return connectionFactory;
        }
    
        @Bean(name = "rabbitTemplate")
        public RabbitTemplate rabbitTemplate() {
            RabbitTemplate rabbitTemplate = new RabbitTemplate();
            rabbitTemplate.setConnectionFactory(connectionFactory(firstHost, firstPort, firstPassword, firstUsername, firstVirtualHost));
            return rabbitTemplate;
        }
        //数据源一
        @Bean(name = "firstRabbitTemplate")
        public RabbitTemplate firstRabbitTemplate() {
            RabbitTemplate rabbitTemplate = new RabbitTemplate();
            rabbitTemplate.setConnectionFactory(connectionFactory(firstHost, firstPort, firstPassword, firstUsername, firstVirtualHost));
            return rabbitTemplate;
        }
    
        //数据源二
        @Bean(name = "secondRabbitTemplate")
        public RabbitTemplate secondRabbitTemplate() {
            RabbitTemplate rabbitTemplate = new RabbitTemplate();
            rabbitTemplate.setConnectionFactory(connectionFactory(secondHost, secondPort, secondPassword, secondUsername, secondVirtualHost));
            return rabbitTemplate;
        }
    
        //数据源三
        @Bean(name = "thirdRabbitTemplate")
        public RabbitTemplate thirdRabbitTemplate() {
            RabbitTemplate rabbitTemplate = new RabbitTemplate();
            rabbitTemplate.setConnectionFactory(connectionFactory(thirdHost, thirdPort, thirdPassword, thirdUsername, thirdVirtualHost));
            return rabbitTemplate;
        }
    
        @Bean(name = "factory")
        public SimpleRabbitListenerContainerFactory factory(SimpleRabbitListenerContainerFactoryConfigurer configurer,
                                                            ConnectionFactory connectionFactory) {
            SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
            configurer.configure(factory, connectionFactory(firstHost, firstPort, firstPassword, firstUsername, firstVirtualHost));
            return factory;
        }
    
        @Bean(name = "firstFactory")
        public SimpleRabbitListenerContainerFactory firstFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer,
                                                               ConnectionFactory connectionFactory) {
            SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
            configurer.configure(factory, connectionFactory(firstHost, firstPort, firstPassword, firstUsername, firstVirtualHost));
            return factory;
        }
    
        @Bean(name = "secondFactory")
        public SimpleRabbitListenerContainerFactory secondFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer,
                                                              ConnectionFactory connectionFactory) {
            SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
            configurer.configure(factory, connectionFactory(secondHost, secondPort, secondPassword, secondUsername, secondVirtualHost));
            return factory;
        }
    
        @Bean(name = "thirdFactory")
        public SimpleRabbitListenerContainerFactory thirdFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer) {
            SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
            configurer.configure(factory, connectionFactory(thirdHost, thirdPort, thirdPassword, thirdUsername,
                    thirdVirtualHost));
            return factory;
        }
    
        @Bean(value = "rabbitAdmin")
        public RabbitAdmin rabbitAdmin() {
            return new RabbitAdmin(connectionFactory(firstHost, firstPort, firstPassword, firstUsername, "/"));
        }
    
        @Bean(value = "firstRabbitAdmin")
        public RabbitAdmin firstRabbitAdmin() {
            return new RabbitAdmin(connectionFactory(firstHost, firstPort, firstPassword, firstUsername, firstVirtualHost));
        }
    
        @Bean(value = "secondRabbitAdmin")
        public RabbitAdmin secondRabbitAdmin() {
            return new RabbitAdmin(connectionFactory(secondHost, secondPort, secondPassword, secondUsername, secondVirtualHost));
        }
    
    
        @Bean(value = "thirdRabbitAdmin")
        public RabbitAdmin thirdRabbitAdmin() {
            return new RabbitAdmin(connectionFactory(thirdHost, thirdPort, thirdPassword, thirdUsername, thirdVirtualHost));
        }
    
    }

2、参数设置

 server.port=8098
    spring.rabbitmq.publisher-confirm-type=correlated
    spring.rabbitmq.listener.simple.acknowledge-mode=manual
    
    #数据源1
    spring.rabbitmq.first-host=110.42.194.96
    spring.rabbitmq.first-virtualhost=test
    spring.rabbitmq.first-port=5072
    spring.rabbitmq.first-username=admin
    spring.rabbitmq.first-password=123456
    #数据源2
    spring.rabbitmq.second-host=110.42.194.96
    spring.rabbitmq.second-virtualhost=test
    spring.rabbitmq.second-port=5073
    spring.rabbitmq.second-username=admin
    spring.rabbitmq.second-password=123456
    #数据源3
    spring.rabbitmq.third-host=110.42.194.96
    spring.rabbitmq.third-virtualhost=test
    spring.rabbitmq.third-port=5074
    spring.rabbitmq.third-username=admin
    spring.rabbitmq.third-password=123456

3、动态创建针对不同数据源的交换机,队列和绑定


    package com.example.multirabbitmqsource.config;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.amqp.core.Binding;
    import org.springframework.amqp.core.Queue;
    import org.springframework.amqp.core.TopicExchange;
    import org.springframework.amqp.rabbit.core.RabbitAdmin;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * @Description: 接收方,队列,交换机绑定配置
     * @Date: 2023/11/14 10:32:34
     */
    @Slf4j
    @Configuration
    public class RabbitMQReceiveBind {
    
        public final static  String EXCHANGE_NAME ="test1";
        public final static  String EXCHANGE_NAME2 ="test2";
    
        /**
         * 06001队列一
         */
        public final static String QUEUE_NAME_06001_01 = "06001.queue1";
    
        /**
         * 06001routekey1
         */
        public final static String ROUTEKEY_06001_01 = "06001_01";
    
    
        /**
         * 06001队列二
         */
        public final static String QUEUE_NAME_06001_02 = "06001.queue2";
    
        /**
         * 06001routekey2
         */
        public final static String ROUTEKEY_06001_02 = "06001_02";
    
        /**
         * 06001队列三
         */
        public final static String QUEUE_NAME_06001_03 = "06001.queue3";
    
        /**
         * 06001routekey3
         */
        public final static String ROUTEKEY_06001_03 = "06001_03";
    
        /**
         * 06002队列一
         */
        public final static String QUEUE_NAME_06002_01 = "06002.queue1";
        /**
         * 06002routekey1
         */
        public final static String ROUTEKEY_06002_01 = "06002_01";
    
        /**
         * 06002队列二
         */
        public final static String QUEUE_NAME_06002_02 = "06002.queue2";
    
        /**
         * 06002routekey2
         */
        public final static String ROUTEKEY_06002_02 = "06002_02";
    
        /**
         * 06003队列一
         */
        public final static String QUEUE_NAME_06003_01 = "06003.queue1";
    
        /**
         * 06003routekey1
         */
        public final static String ROUTEKEY_06003_01 = "06003_01";
    
        /**
         * 06003队列二
         */
        public final static String QUEUE_NAME_06003_02 = "06003.queue2";
    
        /**
         * 06003routekey2
         */
        public final static String ROUTEKEY_06003_02 = "06003_02";
    
    
        //创建交换机,创建队列,绑定
        @Bean("firstSource")
        public String firstSource(@Qualifier("firstRabbitAdmin") RabbitAdmin firstRabbitAdmin) {
            try {
                //1、****************************创建交换机****************************
                firstRabbitAdmin.declareExchange(new TopicExchange(EXCHANGE_NAME));
                //2、****************************创建队列****************************
                firstRabbitAdmin.declareQueue(new Queue(QUEUE_NAME_06001_01, true, false, false, null));
                firstRabbitAdmin.declareQueue(new Queue(QUEUE_NAME_06001_02, true, false, false, null));
    
    
                //3、****************************绑定****************************
    
                firstRabbitAdmin.declareBinding(new Binding(
                        QUEUE_NAME_06001_01,
                        Binding.DestinationType.QUEUE,
                        EXCHANGE_NAME,
                        ROUTEKEY_06001_01,
                        null));
    
                firstRabbitAdmin.declareBinding(new Binding(
                        QUEUE_NAME_06001_02,
                        Binding.DestinationType.QUEUE,
                        EXCHANGE_NAME,
                        ROUTEKEY_06001_02,
                        null));
            } catch (Exception e) {
                log.error("创建数据源一交换机和队列报错{}", e.getMessage());
                e.printStackTrace();
            } finally {
                return EXCHANGE_NAME;
            }
        }
    
    
        @Bean("secondSource")
        public String secondSource(@Qualifier("secondRabbitAdmin") RabbitAdmin secondRabbitAdmin) {
            try {
                boolean autoDelete = false;
                //1、****************************创建交换机****************************
                secondRabbitAdmin.declareExchange(new TopicExchange(EXCHANGE_NAME));
                //2、****************************创建队列****************************
                secondRabbitAdmin.declareQueue(new Queue(QUEUE_NAME_06002_01, true, false, false, null));
                secondRabbitAdmin.declareQueue(new Queue(QUEUE_NAME_06002_02, true, false, false, null));
    
    
                //3、****************************绑定****************************
    
                secondRabbitAdmin.declareBinding(new Binding(
                        QUEUE_NAME_06002_01,
                        Binding.DestinationType.QUEUE,
                        EXCHANGE_NAME,
                        ROUTEKEY_06002_01,
                        null));
    
                secondRabbitAdmin.declareBinding(new Binding(
                        QUEUE_NAME_06002_02,
                        Binding.DestinationType.QUEUE,
                        EXCHANGE_NAME,
                        ROUTEKEY_06002_02,
                        null));
            } catch (Exception e) {
                log.error("创建数据源二交换机和队列报错{}", e.getMessage());
                e.printStackTrace();
            } finally {
                return EXCHANGE_NAME;
            }
        }
    
        @Bean("thirdSource")
        public String thirdSource(@Qualifier("thirdRabbitAdmin") RabbitAdmin thirdRabbitAdmin) {
            try {
                boolean autoDelete = false;
                //1、****************************创建交换机****************************
                thirdRabbitAdmin.declareExchange(new TopicExchange(EXCHANGE_NAME));
                //2、****************************创建队列****************************
                thirdRabbitAdmin.declareQueue(new Queue(QUEUE_NAME_06003_01, true, false, false, null));
                thirdRabbitAdmin.declareQueue(new Queue(QUEUE_NAME_06003_02, true, false, false, null));
    
    
                //3、****************************绑定****************************
    
                thirdRabbitAdmin.declareBinding(new Binding(
                        QUEUE_NAME_06003_01,
                        Binding.DestinationType.QUEUE,
                        EXCHANGE_NAME,
                        ROUTEKEY_06003_01,
                        null));
    
                thirdRabbitAdmin.declareBinding(new Binding(
                        QUEUE_NAME_06003_02,
                        Binding.DestinationType.QUEUE,
                        EXCHANGE_NAME,
                        ROUTEKEY_06003_02,
                        null));
            } catch (Exception e) {
                log.error("创建数据源三交换机和队列报错{}", e.getMessage());
                e.printStackTrace();
            } finally {
                return EXCHANGE_NAME;
            }
        }
    
    }

4、监听部分队列

    package com.example.multirabbitmqsource.config;
    
    
    import com.rabbitmq.client.Channel;
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.amqp.core.Message;
    import org.springframework.amqp.rabbit.annotation.Exchange;
    import org.springframework.amqp.rabbit.annotation.Queue;
    import org.springframework.amqp.rabbit.annotation.QueueBinding;
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.stereotype.Component;
    
    import java.io.IOException;
    
    /**
     * 消息接收处理
     *
     * @since 2023/11/14 10:21:40
     */
    @Slf4j
    @Component
    @RequiredArgsConstructor
    public class RabbitMQReceiver {
    
    
        /**
         * 06001-01队列监听
         * @param msg
         * @param channel
         * @param message
         * @since 2023/11/14 10:31:38
         */
        @RabbitListener(bindings = @QueueBinding(value =
        @Queue(value = RabbitMQReceiveBind.QUEUE_NAME_06001_01)
                , exchange = @Exchange(value = RabbitMQReceiveBind.EXCHANGE_NAME, type = "topic")
                , key = RabbitMQReceiveBind.ROUTEKEY_06001_01)
                , containerFactory = "firstFactory")
        public void listenQueue0600101(String msg, Channel channel, Message message) throws IOException {
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            log.info("【06001-01】接收的数据" + msg);
        }
    
    
        /**
         * 06001-02队列监听
         *
         * @param msg
         * @param channel
         * @param message
         * @since 2023/11/14 11:18:33
         */
        @RabbitListener(queues = RabbitMQReceiveBind.QUEUE_NAME_06001_02,
                containerFactory = "firstFactory")
        public void listenQueue0600102(String msg, Channel channel, Message message) throws IOException {
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            log.info("【06001-02】接收的数据" + msg);
        }
    
    
        /**
         * 06002-01队列监听
         *
         * @param msg
         * @param channel
         * @param message
         * @since 2023/11/14 11:18:30
         */
        @RabbitListener(queues = RabbitMQReceiveBind.QUEUE_NAME_06002_01,
                containerFactory = "secondFactory")
        public void listenQueue0600201(String msg, Channel channel, Message message) throws IOException {
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            log.info("【06002-01】接收的数据" + msg);
        }
    }

发现问题

1、理想状况

每个数据源里只动态创建该数据源的队列,交换机,绑定关系。

数据源一

在这里插入图片描述

数据源二

在这里插入图片描述

数据源三

在这里插入图片描述

2、实际情况

发现不和谐因素,在数据源二和数据源三中创建了数据源一的队列06001.queue1。

数据源一

在这里插入图片描述

数据源二

在这里插入图片描述

数据源三

在这里插入图片描述

定位问题心路历程

阶段一

测试中发现,如果不加载监听代码就不会出现队列创建混乱问题。

在监听处加入该注解可解决问题,意思是在各数据源创建完队列,交换机以后再加载监听配置类。
在这里插入图片描述

至此,算是解决了问题。但是还没有找到真正的原因。

盲猜是在监听方法中有频繁指定不同连接工厂,rabbitmq的监听机制会不会是根据连接工厂去检测该数据源内是否存在所有使用了@Queue注释的队列,如果没有,就会默认在该数据源内创建这些队列?

阶段二

在小伙伴的帮助下,发现在消费监听队列06001.queue1的地方不恰当使用了注解

@QueueBinding, @Queue,@Exchange内部都有一个admins参数,使用时需要指定使用的RabbitAdmin。
在这里插入图片描述

@QueueBinding,@Queue,@Exchange实际上等同于通过RabbitAdmin.declare这种方式创建队列,交换机和绑定,二选一使用就可以了。

合理猜测如果这里不指定RabbitAdmin,会在每一个RabbitAdmin加载时把这些队列,交换机等全部创建一遍?

错误用法

   /**
         * 06001.queue1队列监听
         * @param msg
         * @param channel
         * @param message
         * @since 2023/11/14 10:31:38
         */
        @RabbitListener(bindings = @QueueBinding(value =
        @Queue(value = RabbitMQReceiveBind.QUEUE_NAME_06001_01)
                , exchange = @Exchange(value = RabbitMQReceiveBind.EXCHANGE_NAME, type = "topic")
                , key = RabbitMQReceiveBind.ROUTEKEY_06001_01)
                , containerFactory = "firstFactory")
        public void listenQueue0600101(String msg, Channel channel, Message message) throws IOException {
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            log.info("【06001-01】接收的数据" + msg);
        }

正确用法

  @RabbitListener(bindings = @QueueBinding(value =
        @Queue(value = RabbitMQReceiveBind.QUEUE_NAME_06001_01,admins = "firstRabbitAdmin")
                , exchange = @Exchange(value = RabbitMQReceiveBind.EXCHANGE_NAME, type = "topic",admins = "firstRabbitAdmin")
                , key = RabbitMQReceiveBind.ROUTEKEY_06001_01,admins = "firstRabbitAdmin")
                , containerFactory = "firstFactory")
        public void listenQueue0600101(String msg, Channel channel, Message message) throws IOException {
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            log.info("【06001-01】接收的数据" + msg);
        }

至此,算是从根本上解决了问题!

但是也带来了新的思考,就是rabbitmq是怎么动态管理队列,交换机,绑定和通道的,什么时候去创建的。带着这样的疑问去深入学习了下,终于在rabbitadmin的源码里找到了答案。

找到根本原因

RabbitAdmininitialize()

这里会取出所有类型为Exchange,Queue,Binding的bean。
在这里插入图片描述

这里会对Exchange,Queue,Binding进行过滤,确认最终需要创建的交换机,队列和绑定关系。
在这里插入图片描述在这里插入图片描述

这段代码印证了我和小伙伴的猜想,当使用注解创建队列,交换机,绑定关系时,如果不声明使用的RabbitAdmin时会默认被创建!!!

      private <T extends Declarable> boolean declarableByMe(T dec) {
            return dec.getDeclaringAdmins().isEmpty() && !this.explicitDeclarationsOnly || dec.getDeclaringAdmins().contains(this) || this.beanName != null && dec.getDeclaringAdmins().contains(this.beanName);
        }

终于真相大白了!!!

其他备注:

RabbitTemplate: 是Spring集成RabbitMQ而提供的一个工具类,跟JdbcTemplate一样,可以通过它进行消息的发送和接收。

RabbitAdmin:AmqpAdmin的实现,封装了对RabbitMQ的基础管理操作,比如对交换机、队列、绑定的声明和删除等。
为什么我们在配置文件(Spring)或者配置类(SpringBoot)里面定义了交换机、队列、绑定关系,并没有直接调用Channel的declare的方法,Spring在启动的时候就可以帮我们创建这些元数据?这些事情就是由RabbitAdmin完成的。
RabbitAdmin 实现了 InitializingBean 接口 , 里面有唯一的一 个方法afterPropertiesSet(),这个方法会在 RabbitAdmin的属性值设置完的时候被调用。在 afterPropertiesSet ()方法中,调用了一个 initialize()方法。这里面创建了三个Collection,用来盛放交换机、队列、绑定关系。

最后依次声明返回类型为 Exchange、Queue 和 Binding 这些 Bean,底层还是调用了Channel的declare的方法。
参考:Spring AMQP核心组件解析

ConnectionFactory:Spring AMQP 的连接工厂接口,用于创建连接。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值