背景
公司多个研发团队,多套 RabbitMQ 地址,同一套地址中,也区分为多个 VirtualHost。如何控制某个 exchange/queue 只创建于某一个 virtualhost 中。
采用 Spring 的 @Configuration 配置方式。🔗 Spring AMQP
单数据源配置
-
org.springframework.amqp.rabbit.connection.CachingConnectionFactory
-
org.springframework.amqp.rabbit.core.RabbitTemplate
-
org.springframework.amqp.rabbit.core.RabbitAdmin
-
org.springframework.amqp.support.converter.SimpleMessageConverter
-
Listener
-
org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer
-
org.springframework.amqp.core.TopicExchange(DirectExchange、FanoutExchange、HeadersExchange)
-
org.springframework.amqp.core.Queue
-
org.springframework.amqp.core.Binding
假设需要配置一个用户创建的消息监听器,代码如下:
@Configuration
public class RabbitConfig {
private static final String ADDRESSES = "localhost:5672";
private static final String VIRTUALHOST = "vhost";
private static final String USERNAME = "guest";
private static final String PASSWORD = "guest";
private static final String EXCHANGE_NAME = "exchange.biz.user";
private static final String QUEUE_NAME = "queue.biz.user.add";
private static final String ROUTING_KEY = "rk.biz.user.add.*";
private static final Integer PREFETCH_COUNT = 10;
@Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory factory = new CachingConnectionFactory();
factory.setAddresses(ADDRESSES);
factory.setVirtualHost(VIRTUALHOST);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
return factory;
}
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
template.setMessageConverter(simpleMessageConverter());
return template;
}
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
return new RabbitAdmin(connectionFactory);
}
@Bean
public SimpleMessageConverter simpleMessageConverter() {
return new SimpleMessageConverter();
}
// 业务监听器
@Bean
public UserMessageListener messageListener() {
return new UserMessageListener();
}
@Bean
public SimpleMessageListenerContainer messageListenerContainer(
ConnectionFactory connectionFactory,
RabbitAdmin rabbitAdmin,
UserMessageListener listener) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setRabbitAdmin(rabbitAdmin);
container.setQueueNames(QUEUE_NAME);
container.setMessageListener(listener);
container.setPrefetchCount(PREFETCH_COUNT);
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
return container;
}
@Bean
public TopicExchange exchange() {
return new TopicExchange(EXCHANGE_NAME);
}
@Bean
public Queue queue() {
return new Queue(QUEUE_NAME);
}
@Bean
public Binding binding() {
return BindingBuilder.bind(queue()).to(exchange()).with(ROUTING_KEY);
}
}
配置说明:
RabbitTemplate
:用于发送和接收消息的关键类。
RabbitAdmin
:其底层实现就是从 Spring 容器中获取 exchange、bingding、queue、routingkey 的 bean,然后使用 rabbitTemplate 的 execute 方法执行对应的声明、修改、删除等一系列 rabbitMQ 基础功能操作。「这样就不需要手动在控制台添加交换器与队列了。」
多数据源配置
若有多套 RabbitMQ 地址,或者同一套地址中,区分了多个 virtualhost,配置有所不同。
一般来说,addresses 和 virtualhost 相同的,称之为同一数据源,同一数据源的消息监听器配置在一个 Config 类文件中。
配置关键点在于:
-
为每个 Bean 设置名称,通过 @Qualifier 注解指定使用的 Bean。
-
通过 @Primary 设置主要工厂、模板、管理。
如果不使用 @Qualifier 指定 Bean,会在每个数据源中都创建所有的交换器和队列。比如:
addresses 相同,virtualhost 分别为 vhost1 和 vhost2,使用单数据源的配置方式,在两个 Config 类中分别声明了 exchange1、queue1 和 exchange2、queue2,在 RabbitMQ 的控制台中,我们会发现,vhost1 和 vhost2 中会同时存在两个交换器和队列。
如果通过 @Qualifier 指定,则 vhost1 中只会有 exchange1、queue1,vhost2 中只会有 exchange2、queue2。
代码如下:
@Configuration
public class RabbitConfig {
private static final String ADDRESSES = "localhost:5672";
private static final String VIRTUALHOST = "firsthost";
private static final String USERNAME = "guest";
private static final String PASSWORD = "guest";
private static final String EXCHANGE_NAME = "exchange.biz.first";
private static final String QUEUE_NAME = "queue.biz.first";
private static final String ROUTING_KEY = "rk.biz.first.*";
private static final String DEAD_LETTER_EXCHANGE_NAME = "dle.biz.first";
private static final String DEAD_LETTER_QUEUE_NAME = "dlq.biz.first";
private static final String DEAD_LETTER_ROUTING_KEY = "dle.biz.first";
private static final Integer PREFETCH_COUNT = 10;
@Bean(name = "firstConnectionFactory")
@Primary
public ConnectionFactory firstConnectionFactory() {
CachingConnectionFactory factory = new CachingConnectionFactory();
factory.setAddresses(ADDRESSES);
factory.setVirtualHost(VIRTUALHOST);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
return factory;
}
@Bean(name = "firstRabbitTemplate")
@Primary
public RabbitTemplate firstRabbitTemplate(@Qualifier("firstConnectionFactory") ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
template.setMessageConverter(new SimpleMessageConverter());
return template;
}
@Bean(name = "firstRabbitAdmin")
@Primary
public RabbitAdmin firstRabbitAdmin(@Qualifier("firstConnectionFactory") ConnectionFactory connectionFactory) {
return new RabbitAdmin(connectionFactory);
}
@Bean(name = "firstMessageEventListener")
public FirstMessageEventListener firstMessageEventListener() {
return new FirstMessageEventListener();
}
@Bean
public SimpleMessageListenerContainer firstMessageListenerContainer(
@Qualifier("firstConnectionFactory") ConnectionFactory connectionFactory,
@Qualifier("firstRabbitAdmin") RabbitAdmin rabbitAdmin,
@Qualifier("firstMessageEventListener") FirstMessageEventListener listener) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setRabbitAdmin(rabbitAdmin);
container.setQueueNames(QUEUE_NAME, DEAD_LETTER_QUEUE_NAME);
container.setMessageListener(listener);
container.setPrefetchCount(PREFETCH_COUNT);
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
return container;
}
@Bean
public TopicExchange firstExchange(@Qualifier("firstRabbitAdmin") RabbitAdmin rabbitAdmin) {
TopicExchange topicExchange = new TopicExchange(EXCHANGE_NAME);
topicExchange.setAdminsThatShouldDeclare(rabbitAdmin);
return topicExchange;
}
@Bean
public Queue firstQueue(@Qualifier("firstRabbitAdmin") RabbitAdmin rabbitAdmin) {
Map<String, Object> arguments = Maps.newHashMap();
arguments.put("x-dead-letter-exchange", firstDeadExchange(rabbitAdmin).getName());
arguments.put("x-dead-letter-routing-key", firstDeadBinding(rabbitAdmin).getRoutingKey());
Queue queue = new Queue(QUEUE_NAME, true, false, false, arguments);
queue.setAdminsThatShouldDeclare(rabbitAdmin);
return queue;
}
@Bean
public Binding firstBinding(@Qualifier("firstRabbitAdmin") RabbitAdmin rabbitAdmin) {
Binding binding = BindingBuilder
.bind(firstQueue(rabbitAdmin))
.to(firstExchange(rabbitAdmin))
.with(ROUTING_KEY);
binding.setAdminsThatShouldDeclare(rabbitAdmin);
return binding;
}
@Bean
public TopicExchange firstDeadExchange(@Qualifier("firstRabbitAdmin") RabbitAdmin rabbitAdmin) {
TopicExchange topicExchange = new TopicExchange(DEAD_LETTER_EXCHANGE_NAME);
topicExchange.setAdminsThatShouldDeclare(rabbitAdmin);
return topicExchange;
}
@Bean
public Queue firstDeadQueue(@Qualifier("firstRabbitAdmin") RabbitAdmin rabbitAdmin) {
Queue queue = new Queue(DEAD_LETTER_QUEUE_NAME);
queue.setAdminsThatShouldDeclare(rabbitAdmin);
return queue;
}
@Bean
public Binding firstDeadBinding(@Qualifier("firstRabbitAdmin") RabbitAdmin rabbitAdmin) {
Binding binding = BindingBuilder
.bind(firstDeadQueue(rabbitAdmin))
.to(firstDeadExchange(rabbitAdmin))
.with(DEAD_LETTER_ROUTING_KEY);
binding.setAdminsThatShouldDeclare(rabbitAdmin);
return binding;
}
}
单数据源 xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!-- 消息转换器 -->
<bean id="simpleMessageConverter" class="org.springframework.amqp.support.converter.SimpleMessageConverter"/>
<!-- 连接工厂 -->
<bean id="rabbitConnectionFactory" class="org.springframework.amqp.rabbit.connection.CachingConnectionFactory">
<property name="addresses" value="${rabbitmq.addresses}" />
<property name="virtualHost" value="${rabbitmq.virtualhost}"/>
<property name="username" value="${rabbitmq.username}"/>
<property name="password" value="${rabbitmq.password}"/>
</bean>
<rabbit:template id="rabbitTemplate" connection-factory="rabbitConnectionFactory"/>
<rabbit:admin id="middlewareAdmin" connection-factory="rabbitConnectionFactory"/>
<!-- 定义exchange -->
<rabbit:topic-exchange name="exchange.biz.action">
<rabbit:bindings>
<rabbit:binding queue="myQueue" pattern="exchange.biz.action.*"/>
</rabbit:bindings>
</rabbit:topic-exchange>
<!-- 定义dead-letter-exchange -->
<rabbit:topic-exchange name="dle.biz.action">
<rabbit:bindings>
<rabbit:binding queue="deadQueue" pattern="dle.biz.action.#"/>
</rabbit:bindings>
</rabbit:topic-exchange>
<!--定义queue -->
<rabbit:queue id="myQueue" name="queue.biz.action">
<rabbit:queue-arguments>
<entry key="x-dead-letter-exchange" value="dle.biz.action"/>
<entry key="x-dead-letter-routing-key" value="dlq.biz.action"/>
</rabbit:queue-arguments>
</rabbit:queue>
<!--定义dead-letter-queue -->
<rabbit:queue id="deadQueue" name="dlq.biz.action"/>
<!-- listener 手动确认-->
<rabbit:listener-container acknowledge="manual" prefetch="10">
<rabbit:listener queues="myQueue" ref="myMessageListener"/>
<rabbit:listener queues="deadQueue" ref="myMessageListener"/>
</rabbit:listener-container>
<bean id="myMessageListener" class="com.xxx.xxx.mq.listener.MyMessageEventListener"/>
</beans>