如何解决消息堆积问题

171 阅读2分钟

一:首先我们要明白什么是消息堆积问题?

当生产者发送消息的速度超过了消费者消费的速度,就会导致队列中的消息堆积,直到队列存储消息达到上限,最早接收到的消息,可能就会成为死信,会被丢弃,这就是消息堆积问题

什么是死信?

当一个队列中的消息满足下列情况之一时,可以成为死信(dead letter):

消费者使用basic.reject或 basic.nack声明消费失败,并且消息的requeue参数设置为false

消息是一个过期消息,超时无人消费

要投递的队列消息堆积满了,最早的消息可能成为死信

二:解决思路

  • 增加更多的消费者,提高消费速度
  • 在消费者内开启线程池加快消息处理速度(不推荐,如果消息堆积的很多,开启很多线程,是对CPU的一种浪费,因为CPU要在线程池中做上下文的切换)
  • 扩大队列容积,提高堆积上限

RabbitMQ默认内存存储,但如果在高并发的情况下,把消息都放在内存中是不合适的,因此我们需要通过惰性队列来解决这个问题

惰性队列是从RabbitMQ的3.6.0版本开始,就增加了Lazy Queues的概念
  • 特征:
  • 接收到消息后直接存入磁盘而非内存
  • 消费者要消费消息时才会从磁盘中读取并加载到内存
  • 支持数百万条的消息存储

而要设置一个队列为惰性队列,只需要在声明队列时,指定x-queue-mode属性为lazy即可。可以通过命令行将一个运行中的队列修改为惰性队列

rabbitmqctl set_policy Lazy "^lazy-queue$" '{"queue-mode":"lazy"}' --apply-to queues  

用SpringAMQP声明惰性队列分两种方式

  • 基于@Bean的方式
  • @Bean
    public Queue lazyQueue() {
        return QueueBuilder
            .durable("lazy.queue")
            .lazy() // 开启x-queue-mode为lazybulid();
    }
    
  • 基于注解的方式
  •    @RabbitListener(queuesToDeclare = @Queue(
                name = "lazy.queue",
                durable = "true",
                arguments = @Argument(name = "x-queue-mode",value = "lazy")
        ))
        public void listenLazyQueue(String msg) {
            log.info("接收到 lazy.queue的消息:{}",msg);
        }
    

三、总结

消息队列的解决方案

  • 队列上绑定多个消费者,提高消费速度
  • 给消费者开启线程池,提高消费速度
  • 使用惰性队列,可以在MQ中保存更多信息

惰性队列的优点

  • 基于磁盘存储,消息上限高
  • 没有间歇性的page-out,性能比较稳定

惰性队列的缺点

  • 基于磁盘存储,消息时效性会降低
  • 性能受限于磁盘的IO