记录问题的解决过程。解决的结果代码在最后的总结部分,拉到底就完事。
问题:
RabbitMQ发送消息时,会不断创建新的信道(Channel),直到channel数量达到rabbitmq设置的上限,之后就无法继续将消息写入队列
SpringBoot版本: 2.3.5.RELEASE
问题代码
RabbitMQ配置类
@Configuration
public class RabbitConfig {
String user = "guest";
String password = "guest";
String host = "127.0.0.1";
String port = "5672";
int cachesize = 5;
String exchange = "abc";
String routingkey = "abc.testQueue";
//rabbitmq连接配置
@Bean
public ConnectionFactory connectionFactory(){
CachingConnectionFactory factory = new CachingConnectionFactory();
factory.setUri("amqp://"+user+":"+password+"@"+host+":"+port+"");
factory.setCacheMode(CachingConnectionFactory.CacheMode.CHANNEL);
factory.setChannelCacheSize(cachesize);
factory.setChannelCheckoutTimeout(10000);
return factory;
}
//创建一个topic交换机
@Bean
public TopicExchange topicExchange4Disease(){
return new TopicExchange(exchange);
}
//创建一个队列
@Bean(name = "testQueue")
public Queue testQueue(){
return new Queue(routingkey);
}
//将队列绑定至topic交换机上
@Bean
public Binding testQueueBindToTopic(Queue testQueue, Exchange topicExchange){
return BindingBuilder.bind(testQueue).to(topicExchange).with(routingkey).noargs();
}
}
发送消息实现类
@Service
public class SendMsgService{
@Autowired
private RabbitTemplate rabbitTemplate;
String exchange = "abc";
String queue = "abc.testQueue";
public void send4Test(){
//自己的业务逻辑...
StudentInfoDTO dto = new StudentInfoDTO();
dto.setId(123);
dto.setName("Jack");
rabbitTemplate.convertAndSend(exchange,queue,dto);
}
}
运行结果就是,每发一次消息,都会创建一个新的channel直到撑爆…其他倒是都挺正常
排查错误
检查rabbitMQ的配置类中,关于连接的配置。
@Bean
public ConnectionFactory connectionFactory(){
CachingConnectionFactory factory = new CachingConnectionFactory();
factory.setUri("amqp://"+user+":"+password+"@"+host+":"+port+"");
factory.setCacheMode(CachingConnectionFactory.CacheMode.CHANNEL);
factory.setChannelCacheSize(cachesize);
factory.setChannelCheckoutTimeout(10000);
return factory;
}
这样配置显然没毛病,指定缓存channel的模式,不想占用太多连接。
发送的步骤有点小问题,没有手动关闭channel
//问题代码
rabbitTemplate.convertAndSend(exchange,queue,dto);
但是明明使用的是RabbitTemplate来简化rabbit使用流程,直接发送就完了,怎么还要手动关channel呢?
原因在于配置与使用混乱。
配置的CachingConnectionFactory
, 相当于所有连接rabbitmq和发送接收的操作,由我们自己来管理。于是所有用过的信道都被缓存下来,然而又使用rabbitTemplate
发送,rabbitTemplate
又自动创建新的信道来发送消息。于是channel越来越多…
由此想到手动关闭信道
将之前的发送代码用下面这些来代替
Channel channel = rabbitTemplate.getConnectionFactory().createConnection().createChannel(false);
channel.basicPublish(exchange,routingkey,true,MessageProperties.BASIC,dto.toString().getBytes());
channel.close();
效果是不错的,信道不会太多了。
但是!这样一来,信道的缓存模式就没有了任何作用,因为每次发送消息都创建了新的信道,随后又关闭,并没有被缓存!浪费性能和效率啊。
那么要想实现从信道的缓存池中获取信道,可能得自己实现一个缓存池了。
可以参考这篇博客 《完蛋,手写RabbitMQ客户连接池》
或者,有着我不知道的配置,可以直接获取缓存的channel来使用…
解决方法
总之,初心不改,路不能走歪。本着能省就省的原则,投入RabbitTemplate的怀抱…
前面的问题在于,虽然看似用到了RabbitTemplate,但没有配置它,实际使用的依然是手动操作的方式。
所以,在rabbit配置类中加上:
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setExchange(exchange);
rabbitTemplate.setDefaultReceiveQueue(routingkey);
rabbitTemplate.setMandatory(true);
return rabbitTemplate;
}
将连接交给RabbitTemplate管理,就没问题了
发送消息的代码依然使用:
rabbitTemplate.convertAndSend(exchange,queue,dto);
总结
最后,完整的配置代码,和简单的发送消息实现贴上
Rabbit配置
@Configuration
public class RabbitConfig {
String user = "guest";
String password = "guest";
String host = "127.0.0.1";
String port = "5672";
int cachesize = 5;
String exchange = "abc";
String routingkey = "abc.testQueue";
//rabbitmq连接配置
@Bean
public ConnectionFactory connectionFactory(){
CachingConnectionFactory factory = new CachingConnectionFactory();
factory.setUri("amqp://"+user+":"+password+"@"+host+":"+port+"");
//指定缓存模式为 Channel,那么就会缓存connect中的多个channel,重复使用
factory.setCacheMode(CachingConnectionFactory.CacheMode.CHANNEL);
//设置channel最大缓存数量(实际创建的channel数量可超出这个设定值)
factory.setChannelCacheSize(cachesize);
//设置channel检查超时时长(若设置了这一条,则创建的channel数量无法超出上一行设置的CacheSize)
factory.setChannelCheckoutTimeout(10000);
return factory;
}
//创建一个topic交换机
@Bean
public TopicExchange topicExchange4Disease(){
return new TopicExchange(exchange);
}
//创建一个队列
@Bean(name = "testQueue")
public Queue testQueue(){
return new Queue(routingkey);
}
//将队列绑定至topic交换机上
@Bean
public Binding testQueueBindToTopic(Queue testQueue, Exchange topicExchange){
return BindingBuilder.bind(testQueue).to(topicExchange).with(routingkey).noargs();
}
//配置RabbitTemplate,将rabbit连接由RabbitTemplate进行管理
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setExchange(exchange);
rabbitTemplate.setDefaultReceiveQueue(routingkey);
rabbitTemplate.setMandatory(true);
return rabbitTemplate;
}
//这一项可不加。我这里是因为发送的消息在rabbitmq的管理界面上查看是乱码(其实就是数据序列化,不能直观的看出内容,并不影响接收)
@Bean
Jackson2JsonMessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
}
发送消息
@Service
public class SendMsgService{
@Autowired
private RabbitTemplate rabbitTemplate;
String exchange = "abc";
String queue = "abc.testQueue";
public void send4Test(){
//写自己的业务逻辑...
StudentInfoDTO dto = new StudentInfoDTO();
dto.setId(123);
dto.setName("Jack");
//发送(直接发送,不转换类型)
rabbitTemplate.convertAndSend(exchange,queue,dto);
//发送(将dto转为json格式的字符串, 我这里用的是com.alibaba.fastjson工具, 有需要的自行添加依赖)
//rabbitTemplate.convertAndSend(exchange,queue,JSONObject.parseObject(JSONObject.toJSONString(dto)).toString());
}
}