一:首先我们要明白什么是消息堆积问题?
当生产者发送消息的速度超过了消费者消费的速度,就会导致队列中的消息堆积,直到队列存储消息达到上限,最早接收到的消息,可能就会成为死信,会被丢弃,这就是消息堆积问题
什么是死信?
当一个队列中的消息满足下列情况之一时,可以成为死信(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为lazy 。bulid(); } - 基于注解的方式
-
@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