- 线上高峰期会出现消息堆积吗
- 压测环境下出现消费堆积怎么解决
2.1 消费者数量少
2.2 消费者数量不少,压力在机器CPU或者内存
2.3 消费者数量不少,压力在数据库
2.4 消费者数量不少,压力在接口 - 线上出现消息堆积的事故
3.1 案例1—消息发送unack
3.2 案例二—消息出现error异常 - 怎么处理线上的消息堆积事故
- 代码中如何防止消息堆积
5.1 消息unack导致消息堆积
5.2 消息出现ERROR异常
作为一个程序员,也是经历了几次MQ消息堆积的事故的。
理论上说,消息堆积的两个原因:
- 生产者生产消息太快;
- 消费者消费消息太慢;
但是,真正会出现消息堆积的,大多数都是代码出现了bug,导致消费者消息速度过慢。
1. 线上高峰期会出现消息堆积吗
若是MQ在线上经常出现消息堆积,那么MQ早被淘汰了。
MQ三大作用:异步、削峰、解耦。
作为一个中大型项目来说,功能测试是最基本的。对项目来说,每周都会有压力测试,上一个重要的功能前,也都会在压力测试达标的情况下才能正式上线。
而压力测试保证了系统在高峰期的水平下依旧可以正常稳定的工作。
并且线上环境会配置限流策略,例如压测环境下系统核心链路为200并发2000TPS。那么在系统上线之后,会配置相应的限流熔断策略,保证系统不会出现崩溃。
也就保证了:在线上高峰期情况下,不会存在生产者生产过快、或者消费者消费过慢导致消息堆积的情况。
2. 压测环境下出现消费堆积怎么解决
压测环境出现,就要解决这个问题,首先肯定不是生产者的问题,因为未达到预期的压测数值。
那么是消费者出现的问题:
- 消费者数量太少?
- 消费者数量不少,是调用接口或者数据库的压力?
2.1 消费者数量少
此时应该具体问题具体分析了。消费者数量太少,针对RabbitMQ来说,他提供了【RabbitMQ-2】RabbitMQ的并发参数(concurrency和prefetch)配置,可以设置核心的消费者数量,和最大的消费者数量,保证了在高峰期消费者可以动态扩容。
2.2 消费者数量不少,压力在机器CPU或者内存
水平扩展消费者集群即可,毕竟机器成瓶颈了,增加几个机器来消息不过分。
2.3 消费者数量不少,压力在数据库
此时,水平扩展消费者集群没用了,需要和dba一起排查数据库性能问题的具体原因,是消费者中存在慢sql;还是数据库本身性能到极限,需要水平扩展数据库。
2.4 消费者数量不少,压力在接口
判断接口数据能否被缓存,调用微服务接口的话,对压力大的接口进行优化。
3. 线上出现消息堆积的事故
事故之所以是事故,那是因为代码有bug了。
3.1 案例1—消息发送unack
首先说下RabbitMQ消费者的三种ACK机制:(1)无ACK(2)手动ACK(3)自动ACK
当然线上项目,无ACK这种情况基本没有。
- 自动ACK:消息出现异常,并未捕获交给Spring,那么会发送unack;
- 手动ACK:消息在出现异常时,发送unack;
无论哪种unack,消息都会重新回到队列头,然后这个消息会立即被推给消费者。
你品?这个消息在消费者A出现异常,到达消费者B大概率还是异常,消息被重复的消费-失败-消费...就会导致正常的消息不会被消费,造成消息堆积。
3.2 案例二—消息出现error异常
【RabbitMQ-13】惊!线上的RabbitMQ消费者自己kill自己,导致消息大量堆积
我们有一个队列,由于某个消息过于巨大,导致了系统出现OOM,消费者出现了ERROR异常,然后消费者挂了...关键这个消息又回到MQ中,分发给下个消费者,然后下个消息者也挂了...最后所有消费者都挂了,消息疯狂堆积。
4. 怎么处理线上的消息堆积事故
- 完善的监控报警系统:不能得到真正出现事故的时候,开发人员后知后觉。
- 快速定位代码bug,一般线上出现消息堆积,大概率是代码问题,快速修复代码,恢复消费;
- 根据实际的消息队列,判断是让其自然消费,还是征用机器水平扩展快速消费;
5. 代码中如何防止消息堆积
既然是事故,那么我们必须要防止事故。
针对3种两个案例:我说下自己的解决方案:
5.1 消息unack导致消息堆积
消费者不可避免出现异常,那么出现异常应该如何处理呢?
【RabbitMQ-6】MQ中间件-rabbitmq-消费者消息获取及异常处理的实现(SpringBoot2.0环境下)
- 区分异常种类,判断是否重回队列;
- 设置消费重回队列的次数,达到某个次数后,不重回队列;
- 具体unack的消息是进入死信队列,还是记录log表后丢弃,需要看具体的业务;
5.2 消息出现ERROR异常
【RabbitMQ-13】惊!线上的RabbitMQ消费者自己kill自己,导致消息大量堆积
里面记录了两种方法。推荐提供一个自定义Advice对消费者进行AOP处理。捕获ERROR异常登记log表,不对Spring框架抛出ERROR异常。