消息队列面试题之一

消息积压

消息积压概述

消息的积压来自于两方面:要么发送快了,要么消费变慢了

  • 监控发现,生产和消费消息的速度没什么变化,出现消息积压的情况,检查是有消费失败反复消费的情况。

  • 监控发现,消费消息的速度变慢,检查消费实例,日志中是否有大量消费错误、消费线程是否死锁、是否卡在某些资源上。

  • 单位时间内发送的消息增多,比如赶上大促或者抢购,短时间内不太可能优化消费端的代码来提升消费性能,但可以通过扩容消费端的实例数来提升总体的消费能力。

  • 如果短时间内没有服务器资源扩容,可以将系统降级,通过关闭某些不重要的业务,减少消息发送的数据量,最低限度让系统还能正常运转,保证核心业务的可用性。

  • 严重影响 QM 甚至整个系统时,可以考虑临时启用多个消费者,并发接受消息,持久化之后回头让生产者重新生产消息,或者极端情况下直接丢弃消息。

消息积压扩容方案

利用临时消费者,消费原来积压队列中的消息。该消费者不做任何耗时的操作,将消息均匀写入新创建的队列里,最后将更多 Consumer 部署到更多的机器上消费新创建队列上的消息。等待积压的消息被消费,恢复到正常状态,撤掉扩容服务器。具体步骤和思路如下:

  • 先修复 Consumer 的问题,确保其恢复正常的消费速度,然后将现有 Consumer 都停止
  • 临时建立好原先 10 倍或者 20 倍的 Queue 数量
  • 写一个临时的分发数据的 Consumer 程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的 10 倍数量的 Queue
  • 接着临时征用 10 倍机器来部署 Consumer,每一批 Consumer 消费一个临时 Queue 的数据

这种做法相当于临时将 Queue 资源和 Consumer 资源扩大了 10 倍,即以正常的 10 倍速度消费积压的消息,扩容前后如下图所示:

mq-expansion

消息积压真实场景

场景一:大量消息积压,并且设置了过期时间

假设用的是 RabbitMQ,由于 RabbitMQ 是可以设置过期时间的(TTL),如果消息在 Queue 中积压超过一定的时间,就会被 RabbitMQ 清理掉。这个时候就不是消息被大量积压的问题,而是大量的消息被直接搞丢了。这种情况下,就不是说要增加 Consumer 消费积压的消息,因为实际上消息是没有积压的,而是丢了大量的消息,此时可以采取的一个方案就是批量重导。当大量的消息积压的时候,由于设置了过期时间,RabbitMQ 会直接丢弃数据,然后等业务高峰期过了之后,例如在晚上 12 点以后,写个临时程序将丢失的那批数据查询出来,然后重新将消息写入 RabbitMQ 里,即把白天丢的消息全部补回来。假设 10000 个订单积压在 RabbitMQ 里面,没有来得及处理掉,其中 2000 个订单都丢了,那么只能手动写个临时程序把那 2000 个订单查询出来,然后手动发送消息到 RabbitMQ 中重新进行消费。

场景二:大量消息积压,导致 MQ 磁盘满了

消息积压在 MQ 里,那么如果很长时间都没有处理掉,此时导致 MQ 都快写满了,那应该怎么办?这个时候可以写一个临时程序,启用多个消费者,并发接受消息,同时持久化消息,即快速消费掉 MQ 中积压的消息。到凌晨的时候,将持久化的消息重新写回 MQ 中进行消费;如果希望加快已持久化消息的消费速度,可以引入上述的消息积压扩容方案。