无侵入式引入多源rabbitMq场景(主源自动装配,副源手动配置)
- springboot接入单个rabbitmq可以通过springboot自动装配原理,简单地在配置文件中设置好必要信息即可;
- 当需要接入多个rabbitmq源时,度娘找到的多数处理方法是给每个源进行手动配置好ConnectionFactory、RabbitTemplate、SimpleRabbitListenerContainerFactory、RabbitAdmin等等并把其中的主源使用@Primary进行标志;当主源配置信息比较复杂时,原本通过springboot自动装配接入的主源rabbitmq更改为手动配置时,很大可能会影响到主源原有的配置连接信息;
- 此时我们希望做到的结果是主源rabbitMq仍然使用springboot提供的自动装配进行配置,额外引入的rabbitMq副源则使用手动配置的方式接入;如果你的rabbitMq主源仍然是使用自动装配接入,而rabbitMq副源是下面这样接入的话
@Configuration
@ConfigurationProperties("spring.rabbitmq.second")
public class SecondRabbitConfiguration extends AbstractRabbitConfiguration {
@Bean(name = "secondConnectionFactory")
public ConnectionFactory secondConnectionFactory() {
return super.connectionFactory();
}
@Bean(name = "secondRabbitTemplate")
public RabbitTemplate secondRabbitTemplate(@Qualifier("secondConnectionFactory") ConnectionFactory connectionFactory) {
return new RabbitTemplate(connectionFactory);
}
@Bean(name = "secondFactory")
public SimpleRabbitListenerContainerFactory secondFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer,
@Qualifier("secondConnectionFactory") ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
configurer.configure(factory, connectionFactory);
return factory;
}
@Bean(value = "secondRabbitAdmin")
public RabbitAdmin secondRabbitAdmin(@Qualifier("secondConnectionFactory") ConnectionFactory connectionFactory) {
return new RabbitAdmin(connectionFactory);
}
}
那么一般服务是启动不成功的,报错如下:
org.springframework.boot.autoconfigure.amqp.RabbitAnnotationDrivenConfiguration required a single bean, but 2 were found:
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
- 根据报错信息我们可以知道,此时spring中存在两个RabbitConnectionFactory,但是spring不知道哪个是主源,如果你在上面副源的配置上使用@Primary注解进行标志,那么程序可以正常运行,但是我们不可能把@Primary注解打到副源的配置上;
- 既然不可以存在两个没有区分主源的RabbitConnectionFactory,那我们可以换一个思路,配置并持有副源的Channel即可;
- Connection是物理TCP连接。Connection将应用与消息队列RabbitMQ版连接在一起。Connection会执行认证、IP解析、路由等底层网络任务。应用与消息队列RabbitMQ版完成Connection建立大约需要15个TCP报文交互,因而会消耗大量的网络资源和消息队列RabbitMQ版资源。大量的Connection会对消息队列RabbitMQ版造成巨大压力,甚至触发消息队列RabbitMQ版SYN洪水攻击防护,导致消息队列RabbitMQ版无响应,进而影响您的业务。
- Channel是物理TCP连接中的虚拟连接。当应用通过Connection与消息队列RabbitMQ版建立连接后,所有的AMQP协议操作(例如创建队列、发送消息、接收消息等)都会通过Connection中的Channel完成。Channel可以复用Connection,即一个Connection下可以建立多个Channel。Channel不能脱离Connection独立存在,而必须存活在Connection中。当某个Connection断开时,该Connection下的所有Channel都会断开。
配置副源的Channel
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.rabbitmq.client.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.Connection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Objects;
/**
* @author pengzi
* @date 2021/05/10 15:17:25
* @description rabbitmq副源
*/
@Slf4j
@Configuration
public class RabbitConfigSecond {
@Value("${second.rabbitmq.host:}")
private String host;
@Value("${second.rabbitmq.port:}")
private Integer port;
@Value("${second.rabbitmq.username:}")
private String username;
@Value("${second.rabbitmq.password:}")
private String password;
@Value("${second.rabbitmq.virtual-host:}")
private String virtualHost;
public Channel channel;
/**
* 接收数据的队列
*/
public final static String RECEIVE_DATA_QUEUE = "RECV";
/**
* 推送数据的队列
*/
public final static String SEND_DATA_QUEUE = "SEND";
@Bean
public String secondRabbitConnectionFactory(SecondShutdownListener shutdownListener) {
Channel channel = processChannel(shutdownListener);
this.channel = channel;
return "secondRabbitConnectionFactory";
}
public Channel processChannel(SecondShutdownListener shutdownListener) {
com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory = new com.rabbitmq.client.ConnectionFactory();
rabbitConnectionFactory.setHost(host);
rabbitConnectionFactory.setPort(port);
rabbitConnectionFactory.setUsername(username);
rabbitConnectionFactory.setPassword(password);
rabbitConnectionFactory.setAutomaticRecoveryEnabled(true);
rabbitConnectionFactory.setNetworkRecoveryInterval(5000);
if (StringUtils.isNotBlank(virtualHost)) {
rabbitConnectionFactory.setVirtualHost(virtualHost);
}
CachingConnectionFactory connectionFactory = new CachingConnectionFactory(rabbitConnectionFactory);
Connection connection = connectionFactory.createConnection();
Channel channel = connection.createChannel(false);
Consumer consumer = new SecondRabbitMqConsumer(channel);
try {
//声明队列
channel.queueDeclare(RECEIVE_DATA_QUEUE, false, false, false, null);
channel.queueDeclare(SEND_DATA_QUEUE, false, false, false, null);
//绑定消息队列消费者
channel.basicConsume(RECEIVE_DATA_QUEUE, true, consumer);
//Channel关闭监听器,监听到Channel关闭需要重新发起Connection并创建持有新的Channel
channel.addShutdownListener(shutdownListener);
} catch (IOException e) {
e.printStackTrace();
}
return channel;
}
}
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import lombok.extern.slf4j.Slf4j;
/**
* @author pengzi
* @date 2021/05/11 17:11:36
* @description 副源消息队列消费者
*/
@Slf4j
public class SecondRabbitMqConsumer extends DefaultConsumer {
/**
* Constructs a new instance and records its association to the passed-in channel.
*
* @param channel the channel to which this consumer is attached
*/
public SecondRabbitMqConsumer(Channel channel) {
super(channel);
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) {
String bodyString = new String(body);
log.info("secondReceiveData properties:{}", properties);
log.info("secondReceiveData:{}", bodyString);
}
}
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ShutdownListener;
import com.rabbitmq.client.ShutdownSignalException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
/**
* @author pengzi
* @date 2021/05/11 17:11:36
* @description 副源Channel关闭监听器,监听到Channel关闭需要重新发起Connection并创建持有新的Channel
*/
@Configuration
@Slf4j
public class SecondShutdownListener implements ShutdownListener, ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void shutdownCompleted(ShutdownSignalException cause) {
log.info("ShutdownListener cause:" + cause);
RabbitConfigSecond rabbitConfigSecond = (RabbitConfigSecond) applicationContext.getBean("rabbitConfigSecond");
Channel channel = rabbitConfigSecond.processChannel(this);
rabbitConfigSecond.channel = channel;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
import com.alibaba.fastjson.JSONObject;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.MessageProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.UUID;
/**
* @author pengzi
* @date 2021/05/11 17:19:57
* @description 副源消息队列消息生产者
*/
@Component
@Slf4j
public class SecondRabbitMqProvider {
@Autowired
private RabbitConfigSecond secondRabbitConfig;
/**
* 副源消息队列推送数据
*
* @param sendData 推送消息
*/
public void secondSendData(TransferBodyDTO sendData) {
log.info("secondSendData:{}", sendData);
AMQP.BasicProperties props = new AMQP.BasicProperties.Builder()
.deliveryMode(MessageProperties.PERSISTENT_TEXT_PLAIN.getDeliveryMode())
.messageId(UUID.randomUUID().toString())
.build();
try {
secondRabbitConfig.channel.basicPublish("", RabbitConfigSecond.SEND_DATA_QUEUE, props, JSONObject.toJSONString(sendData).getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}