SpringBoot 连接多个Rabbitmq数据源
前言
最近工作上需要在一个项目里面使用两个RabbitMq作为数据源,记录一下大概的实现过程,加深影响。
一、单个RabbitMq数据源
一般情况下,都是依赖单个RabbitMQ作为单个数据,以SpringBoot官网实例为例,在SpringBoot项目的application.properties中配置rabbitmq的连接信息即可,实例代码如下:
package com.example.messagingrabbitmq;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class MessagingRabbitmqApplication {
static final String topicExchangeName = "spring-boot-exchange";
static final String queueName = "spring-boot";
@Bean
Queue queue() {
return new Queue(queueName, false);
}
@Bean
TopicExchange exchange() {
return new TopicExchange(topicExchangeName);
}
@Bean
Binding binding(Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("foo.bar.#");
}
@RabbitListener(queues = "${spring.queue}")
public void receiveQueueFromXalarm(Message message) {
handlerMessage(message);
}
public static void main(String[] args) throws InterruptedException {
SpringApplication.run(MessagingRabbitmqApplication.class, args).close();
}
}
二、多个RabbitMQ数据源
从单个RabbitMQ的数据源,我们了解到SpringBoot使用约定优于配置的方式,简化了RabbitMQ开发。在spring-boot-starter-amqp中,根据application.properties中的rabbitmq连接信息,自动创建了连接信息,创建@Queue,@Exchange,@Binding。但是要在一个项目里面使用多个RabbitMQ数据源,需要手动创建其他的RabbitMQ数据源的连接工作,大概的步骤如下:
2.1 增加配置信息
spring.rabbitmq.main.host=192.168.10.223
spring.rabbitmq.main.port=5672
spring.rabbitmq.main.username=admin
spring.rabbitmq.main.password=admin
spring.rabbitmq.second.used=true
spring.rabbitmq.second.host=192.168.10.224
spring.rabbitmq.second.port=5672
spring.rabbitmq.second.username=admin
spring.rabbitmq.second.password=admin
@Configuration
public class RabbitMqConfig {
@Value("${direct.exchange.alarm}")
private String directExchangeAlarm;
@Value("${queue.alarm}")
private String queueAlarm;
@Value("${routing.key.alarm}")
private String routingKeyAlarm;
@Value("${direct.exchange.alarm.second}")
private String directExchangeAlarmSecond;
@Value("${queue.alarm.second}")
private String queueAlarmSecond;
@Value("${routing.key.alarm.second}")
private String routingKeyAlarmSecond;
@Value("${spring.rabbitmq.main.ip}")
private String mainHost;
@Value("${spring.rabbitmq.main.port}")
private int mainPort;
@Value("${spring.rabbitmq.main.username}")
private String mainUserName;
@Value("${spring.rabbitmq.main.password}")
private String mainPassword;
@Value("${spring.rabbitmq.second.ip}")
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;
@Bean(name = "mainConnectionFactory")
@Primary
public ConnectionFactory mainConnectionFactory(){
return connectionFactory(mainHost, mainPort, mainUserName, mainPassword);
}
@Bean(name = "secondConnectionFactory")
@ConditionalOnProperty(prefix = "spring.rabbitmq.second", value = "used", havingValue = "true")
public ConnectionFactory secondConnectionFactory(){
return connectionFactory(secondHost, secondPort, secondUserName, secondPassword);
}
@Bean(name = "mainFactory")
@Primary
public SimpleRabbitListenerContainerFactory mainFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer,
@Qualifier("mainConnectionFactory") ConnectionFactory connectionFactory){
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
configurer.configure(factory, connectionFactory);
return factory;
}
@Bean(name = "secondFactory")
@ConditionalOnProperty(prefix = "spring.rabbitmq.second", value = "used", havingValue = "true")
public SimpleRabbitListenerContainerFactory secondFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer,
@Qualifier("secondConnectionFactory") ConnectionFactory connectionFactory){
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
configurer.configure(factory, connectionFactory);
return factory;
}
//xalarm行为分析
@Bean
public Queue alarmQueue() {
return new Queue(queueAlarm);
}
@Bean
DirectExchange alarmExchange() {
return new DirectExchange(directExchangeAlarm);
}
@Bean
Binding alarmBinding() {
return BindingBuilder.bind(alarmQueue()).to(alarmExchange()).with(routingKeyAlarm);
}
@Bean
@ConditionalOnProperty(prefix = "spring.rabbitmq.second", value = "used", havingValue = "true")
public String alarmTQueueSecond(@Qualifier("secondConnectionFactory") ConnectionFactory connectionFactory) throws IOException {
HashMap<String, Object> map = new HashMap<>();
map.put("x-expires", 86400000); // 当队列在指定的时间没有被访问(consume, basicGet, queueDeclare…)就会被删除,Features=Exp
map.put("x-max-length", 100000); // 限定队列的消息的最大值长度,超过指定长度将会把最早的几条删除掉
map.put("x-max-length-bytes", 209715200); // 限定队列最大占用的空间大小, 一般受限于内存、磁盘的大小, Features=Lim B, 设置为200M
connectionFactory.createConnection().createChannel(false).queueDeclare(queueAlarmSecond, true, false, false, map);
connectionFactory.createConnection().createChannel(false).queueBind(queueAlarmSecond, directExchangeAlarmSecond, routingKeyAlarmSecond);
return "alarmTQueueSecond";
}
private CachingConnectionFactory connectionFactory(String host, int port, String username, String password){
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setHost(host);
connectionFactory.setPort(port);
connectionFactory.setUsername(username);
connectionFactory.setPassword(password);
return connectionFactory;
}
}
2.2 代码实现
将两个数据处理的公共部分抽取到抽象类中,具体如下所示:
public abstract class AbstractAlarmConsumer {
@Autowired
private XXXService xxxService;
protected void handlerMessage(Message message) {
try {
String strFromRMQ = new String(message.getBody());
// TODO 公共业务逻辑
transformsMessage(analysisResult);
// TODO 公共业务逻辑
} catch (Exception e) {
throw new ImmediateAcknowledgeAmqpException(e);
}
}
protected abstract void transformsMessage(AnalysisResult analysisResult) throws ExecutionException;
}
@Component
public class XXXAlarmConsumer extends AbstractAlarmConsumer {
@Override
protected void transformsMessage(AnalysisResult analysisResult) throws ExecutionException {
// TODO
}
@RabbitListener(queues = "${queue.xalarm}")
public void receiveQueueFromXalarm(Message message) {
handlerMessage(message);
}
}
@Component
@ConditionalOnProperty(prefix = "spring.rabbitmq.second", value = "used", havingValue = "true")
public class ThirdAiBehaviorConsumer extends AbstractAiBehaviorConsumer {
@RabbitListener(queues = "${queue.xalarm.second}", containerFactory = "secondFactory")
public void receiveSecondQueueFromXalarm(Message message) {
handlerMessage(message);
}
@Override
protected void transformsMessage(AnalysisResult analysisResult) throws ExecutionException {
// TODO
}
}
三、RabbitMQ的optional queue arguments 和 policies
3.1 optional queue agruments
queueDeclare(String queue, boolean durable, boolean exclusive, Map<String, Object> arguments);
是指该方法中设置arguments,主要用于设置单个queue,主要类型如下:
- Auto Expire(x-expires): 当队列在指定的时间没有被访问(consume, basicGet, queueDeclare…)就会被删除,Features=Exp
- Max Length(x-max-length): 限定队列的消息的最大值长度,超过指定长度将会把最早的几条删除掉, 类似于mongodb中的固定集合,例如保存最新的100条消息, Feature=Lim
- Max Length Bytes(x-max-length-bytes): 限定队列最大占用的空间大小, 一般受限于内存、磁盘的大小, Features=Lim B
- Dead letter exchange(x-dead-letter-exchange): 当队列消息长度大于最大长度、或者过期的等,将从队列中删除的消息推送到指定的交换机中去而不是丢弃掉,Features=DLX
- Dead letter routing key(x-dead-letter-routing-key):将删除的消息推送到指定交换机的指定路由键的队列中去, Feature=DLK
- Maximum priority(x-max-priority):优先级队列,声明队列时先定义最大优先级值(定义最大值一般不要太大),在发布消息的时候指定该消息的优先级, 优先级更高(数值更大的)的消息先被消费
- Lazy mode(x-queue-mode=lazy): Lazy Queues: 先将消息保存到磁盘上,不放在内存中,当消费者开始消费的时候才加载到内存中
3.2 policies
主要用来设置一组queue,属于全局设置的参数,主要包括:
- expires
- message-ttl
- max-length
- max-length-bytes
应对磁盘满或rabbitmq消息严重堆积策略:
rabbitmqctl set_policy Main-policy ".*" '{"max-length-bytes":10485760000,"max-length":500000,"message-ttl":259200000}' --apply-to queues
Main-policy:策略名称
“.*”:通配符,所有队列
max-length-bytes:队列最大堆积上线(配置为10G 10485760000)
max-length:最大堆积数量(配置的为50w条)
message-ttl:消息过期时间(默认3天 259200000)
该策略配置后,消息堆积以最先到达条件为准(10G或者50w条),若到达消息堆积上限,默认会将最早的消息丢弃(若到达50w条后,队列中就只会堆积50w条,再有消息进来老的消息会被丢弃)。