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的源码里找到了答案。
找到根本原因
RabbitAdmin的initialize()
这里会取出所有类型为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 的连接工厂接口,用于创建连接。