RabbitMQ发送消息不断创建新Channel, 信道缓存“失效“

4 篇文章 0 订阅
2 篇文章 0 订阅

记录问题的解决过程。解决的结果代码在最后的总结部分,拉到底就完事。

问题:

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());
        

    }
}
  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
RabbitMQ中,发送消息时,可以使用`channel.basicConsume`方法来订阅队列并消费消息。下面是对`channel.basicConsume`方法的参数进行讲解: 1. `queue`:指定要订阅的队列名称。 2. `auto_ack`:设置是否自动确认消息的接收。如果设置为True,则表示一旦消息被消费者接收,就自动确认;如果设置为False,则需要手动确认消息的接收。 3. `on_message_callback`:指定一个回调函数,用于处理接收到的消息。当有消息到达时,会调用该回调函数进行处理。 4. `exclusive`:设置是否为独占模式。如果设置为True,则只允许当前连接的消费者订阅该队列;如果设置为False,则允许多个消费者订阅该队列。 5. `consumer_tag`:指定消费者的标签。可以用于取消订阅时的标识。 6. `arguments`:可选参数,用于设置额外的参数。 下面是一个示例代码,演示了如何使用`channel.basicConsume`方法发送消息: ```python import pika def callback(ch, method, properties, body): # 处理接收到的消息 print("Received message:", body.decode()) # 手动确认消息的接收 ch.basic_ack(delivery_tag=method.delivery_tag) # 建立与RabbitMQ服务器的连接 connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) channel = connection.channel() # 声明一个队列 channel.queue_declare(queue='my_queue') # 订阅队列并消费消息 channel.basicConsume(queue='my_queue', on_message_callback=callback, auto_ack=False) # 开始接收消息 channel.start_consuming() ``` 在上面的示例中,我们首先建立与RabbitMQ服务器的连接,然后声明一个队列。接下来,使用`channel.basicConsume`方法订阅队列,并指定一个回调函数`callback`来处理接收到的消息。最后,调用`channel.start_consuming`方法开始接收消息

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值