@QueueBinding RabbitMQ 多数据源 队列重复

一、问题描述
  • 项目代码中需要连接多个RabbitMQ,然后分别在不同的RabbitMQ中创建交换机、队列以及进行关系绑定;实现代码使用了注解@QueueBinding,但是在每个RabbitMQ中都创建了其他的RabbitMQ的交换机和队列
二、项目代码
  • RabbitMqConfig部分代码

    @Bean(name = "virConnectionFactory")
    @Primary
    public ConnectionFactory virConnectionFactory() {
        return connectionFactory(host, port, password, username, virtualHost);
    }
    @Bean(name = "sjVirConnectionFactory")
    public ConnectionFactory sjVirConnectionFactory() {
        return connectionFactory(sjHost, sjPort, sjPassword, sjUsername, sjVirtualHost);
    }


    @Bean(name = "virFactory")
    @Primary
    public SimpleRabbitListenerContainerFactory virFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer,
                                                           @Qualifier("virConnectionFactory") ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        configurer.configure(factory, connectionFactory);
        return factory;
    }
    @Bean(name = "sjVirFactory")
    public SimpleRabbitListenerContainerFactory sjVirFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer,
                                                             @Qualifier("sjVirConnectionFactory") ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        configurer.configure(factory, connectionFactory);
        return factory;
    }


    @Bean(name = "virRabbitTemplate")
    public RabbitTemplate virRabbitTemplate(@Qualifier("virConnectionFactory") ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate();
        rabbitTemplate.setConnectionFactory(connectionFactory);
        return rabbitTemplate;
    }
   @Bean(name = "sjVirRabbitTemplate")
    public RabbitTemplate sjVirRabbitTemplate(@Qualifier("sjVirConnectionFactory") ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate();
        rabbitTemplate.setConnectionFactory(connectionFactory);
        return rabbitTemplate;
    }


    @Bean(value = "virRabbitAdmin")
    public RabbitAdmin virRabbitAdmin(@Qualifier("virRabbitTemplate") RabbitTemplate rabbitTemplate) {
        return new RabbitAdmin(rabbitTemplate);
    }
    @Bean(value = "sjVirRabbitAdmin")
    public RabbitAdmin sjVirRabbitAdmin(@Qualifier("sjVirRabbitTemplate") RabbitTemplate rabbitTemplate) {
        return new RabbitAdmin(rabbitTemplate);
    }

  • Listener监听类部分代码(这部分代码有问题,下文中第五点解释说明)
    @RabbitListener(bindings = @QueueBinding(value =
    @Queue(value= RabbitMQReceiveBind.QUEUE_NAME_AFTER_EVENT_AUTO_DELAY_TLES_SHEET)
            , exchange = @Exchange(value = olesExchangeName, type = "topic")
            , key = RabbitMQSendConfig.AFTER_EVENT_AUTO_DELAY_TLES_SHEET_ROUTEKEY)
            , containerFactory = "sjVirFactory")
    public void afterEventWarn2Sheet(String msg, Channel channel, Message message) throws IOException {
        //...
    }
三、以上代码的思路
  • 根据配置创建ConnectionFactory
  • 根据ConnectionFactory创建SimpleRabbitListenerContainerFactory
  • 根据ConnectionFactory创建RabbitTemplate
  • 根据RabbitTemplate创建RabbitAdmin

注:也可以使用ConnectionFactory创建RabbitAdmin,写法不同,不必要纠结

四、ConnectionFactory、SimpleRabbitListenerContainerFactory、RabbitTemplate、RabbitAdmin的作用
  • ConnectionFactory:连接工厂,负责创建消费端与rabbitMQ之间的链接
  • SimpleRabbitListenerContainerFactory:负责监听的任务
  • RabbitTemplate:负责对rabbitMQ各种操作,如:用于创建、绑定、删除队列与交换机,发送消息等
  • RabbitAdmin:用于创建、绑定、删除队列与交换机,发送消息等;他的作用跟RabbitTemplate差不多,只不过他对RabbitTemplate进行了封装,内部的一些具体的操作还是依赖RabbitTemplate来完成的
五、正确的写法
  • 目前的错误写法
    @RabbitListener(bindings = @QueueBinding(value =
    @Queue(value= RabbitMQReceiveBind.QUEUE_NAME_AFTER_EVENT_AUTO_DELAY_TLES_SHEET)
            , exchange = @Exchange(value = olesExchangeName, type = "topic")
            , key = RabbitMQSendConfig.AFTER_EVENT_AUTO_DELAY_TLES_SHEET_ROUTEKEY)
            , containerFactory = "sjVirFactory")
    public void afterEventWarn2Sheet(String msg, Channel channel, Message message) throws IOException {
        //...
    }

注: 上述代码中没有指定admins,也就是RabbitAdmin,导致每个RabbitAdmin的初始化Bean获取所有交换机和队列时,没有过滤掉不属于自己的交换机和队列(见下文第六点的解释说明)

  • 正确的写法
    @RabbitListener(bindings = @QueueBinding(value =
    @Queue(value= RabbitMQReceiveBind.QUEUE_NAME_AFTER_EVENT_AUTO_DELAY_TLES_SHEET , admins = "sjVirRabbitAdmin")
            , exchange = @Exchange(value = olesExchangeName, type = "topic", admins = "sjVirRabbitAdmin")
            , key = RabbitMQSendConfig.AFTER_EVENT_AUTO_DELAY_TLES_SHEET_ROUTEKEY, admins = "sjVirRabbitAdmin")
            , containerFactory = "sjVirFactory")

注:@QueueBinding@Queue@Exchange都要进行绑定admins

误区:一开始陷入了一个误区,认为声明了containerFactory = "sjVirFactory",就可以找到对应的数据源了,其实这个containerFactory只是在监听环节起到了作用,创建时,与它无关,它是属于@RabbitListener的参数值,而不是@QueueBinding的参数值

六、分析源码
  • org.springframework.amqp.rabbit.core.RabbitAdmin#initialize的关键部分
public void initialize() {

// 反射获取当前所有Exchange、Queue、Binding对象
Collection<Exchange> contextExchanges = new LinkedList<Exchange>(
        this.applicationContext.getBeansOfType(Exchange.class).values());
Collection<Queue> contextQueues = new LinkedList<Queue>(
        this.applicationContext.getBeansOfType(Queue.class).values());
Collection<Binding> contextBindings = new LinkedList<Binding>(
        this.applicationContext.getBeansOfType(Binding.class).values());
Collection<DeclarableCustomizer> customizers =
        this.applicationContext.getBeansOfType(DeclarableCustomizer.class).values();

processDeclarables(contextExchanges, contextQueues, contextBindings);
// 进行过滤
final Collection<Exchange> exchanges = filterDeclarables(contextExchanges, customizers);
final Collection<Queue> queues = filterDeclarables(contextQueues, customizers);
final Collection<Binding> bindings = filterDeclarables(contextBindings, customizers);
}

注:可以看到通过反射获取了所有的ExchangeQueueBinding,以及通过方法filterDeclarables进行了过滤

  • 方法:filterDeclarabledeclarableByMe
private <T extends Declarable> Collection<T> filterDeclarables(Collection<T> declarables,
        Collection<DeclarableCustomizer> customizers) {

    return declarables.stream()
            // 通过declarableByMe进行过滤
            .filter(dec -> dec.shouldDeclare() && declarableByMe(dec))
            .map(dec -> {
                if (customizers.isEmpty()) {
                    return dec;
                }
                AtomicReference<T> ref = new AtomicReference<>(dec);
                customizers.forEach(cust -> ref.set((T) cust.apply(ref.get())));
                return ref.get();
            })
            .collect(Collectors.toList());
}

private <T extends Declarable> boolean declarableByMe(T dec) {
return (dec.getDeclaringAdmins().isEmpty() && !this.explicitDeclarationsOnly) // NOSONAR boolean complexity
        || dec.getDeclaringAdmins().contains(this)
        // dec.getDeclaringAdmins().contains(this.beanName))这里就是判断admins是否包含当前Bean
        || (this.beanName != null && dec.getDeclaringAdmins().contains(this.beanName));
}
七、最终结论
  • 使用注解@QueueBinding进行多数据源队列绑定时,需要指定admins,即RabbitAdmin
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
RabbitMQ 中,我们可以通过将多个队列绑定到同一个交换机上来实现一些特定的功能,比如消息广播或者消息负载均衡等。 在代码实现上,我们可以使用 RabbitMQJava 客户端库来创建多个队列并将其绑定到同一个交换机上。以下是一个简单的示例代码: ```java import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import java.io.IOException; import java.util.concurrent.TimeoutException; public class MultipleQueuesBindingExample { private static final String EXCHANGE_NAME = "my_exchange"; private static final String QUEUE_NAME_1 = "my_queue_1"; private static final String QUEUE_NAME_2 = "my_queue_2"; public static void main(String[] args) throws IOException, TimeoutException { // 创建连接工厂 ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); factory.setUsername("guest"); factory.setPassword("guest"); // 创建连接 Connection connection = factory.newConnection(); // 创建通道 Channel channel = connection.createChannel(); // 声明交换机 channel.exchangeDeclare(EXCHANGE_NAME, "fanout"); // 声明队列1 channel.queueDeclare(QUEUE_NAME_1, false, false, false, null); // 声明队列2 channel.queueDeclare(QUEUE_NAME_2, false, false, false, null); // 绑定队列1到交换机 channel.queueBind(QUEUE_NAME_1, EXCHANGE_NAME, ""); // 绑定队列2到交换机 channel.queueBind(QUEUE_NAME_2, EXCHANGE_NAME, ""); // 关闭连接和通道 channel.close(); connection.close(); } } ``` 以上代码中,我们首先创建了一个名为 `my_exchange` 的 fanout 类型的交换机,然后分别创建了两个队列 `my_queue_1` 和 `my_queue_2`,最后将这两个队列都绑定到了 `my_exchange` 交换机上。 需要注意的是,在绑定队列到交换机的时候,我们传递了一个空字符串 `""` 作为 routing key。这是因为在 fanout 类型的交换机中,消息会被广播到所有绑定的队列中,而不需要指定具体的 routing key。 当需要向这两个队列发送消息时,只需要向 `my_exchange` 交换机发送消息即可,消息会自动被广播到所有绑定的队列中。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值