数据堆积发生的时机:
一般来说,当我们的MQ消费者出现了故障,导致无法消费MQ服务的消息的时候,而生产者在不知道的情况下继续生产数据,那么就会有大量的数据堆积到MQ服务中,从而导致MQ服务无法使用,最后影响到生产者也不能正常工作。
那些年挨打的瞬间
话说在很久以前,我们的某个业务系统,紧急上线,没有接入到平台的监控和熔断机制里面,导致MQ堆积引发对外服务不可用。当时造成这个原因是我们在消费者加入了保证数据消费顺序的逻辑,即前一条数据没有消费成功,则后面的数据不再签收消费,但是我们针对消费出错的容错处理是重试机制,即将错误数据重新投递到队列尾部,然后保证其它数据能正常消费导致了这两个逻辑冲突。
如有如下队列:
我们的容错机制:因为在处理数据的时候我们允许几次数据消费失败,所以引入了重新投递机制,这样可以保证一个数据消费失败能够正常重试,并且不影响后面正确的数据。
保证消费顺序:我们在保证消费顺序的时候,因为是集群多消费者,所以对某些数据有顺序要求的,就加了这种强一直顺序保证。
场景重现:在那个月黑风高的夜晚,突然一个电话打来,让我赶紧爬起来看下为什么系统不能提交任务了,骂骂咧咧的登上VPN,排查发现MQ服务堆积了上千万数据,并且已经不能再提交新任务。于是排查,发现在消费端有一个强顺序任务报错,第一个消费失败重新投递,然后开始消费第二个的时候发现第一个任务没有处理,一直拒绝签收,导致卡在了消费端,从而造成了MQ数据堆积。
解决思路:
- 暂停所有消费者
- 修改数据导致bug
- 使用运维平台取出有问题这一个序列的任务,也就是出现问题几个任务,修改了数据后重新投递到队列中
- 启动消费者,让其正常处理任务
上面初步解决了问题,但是消费太慢,当时处理三十万数据差不多要半个小时,一个小时也才处理六十多万数据,处理上千万数据得十多二十个小时,而且还在不停的生产,这个速度很难让实施那边接受,于是我们想到的第一个办法就是增加消费者进行部署,我们临时抽调了其它服务资源,增加了消费者数量。
当时按照预估会将时间缩减到三分之一,正当我们快快乐乐的喝茶聊天的时候,不出意外的话要出意外了,最后果不其然出意外了。由于我们分表策略是由时间分片的,当时由于数据量的问题,直接将主表给整卡了,插入非常缓慢,刚开始想到调整表,比如去掉索引,或者先引接到临时表后面再处理,但是还有其它业务使用这个表因此不能随便动,但是实施又在催先让服务可用再说。最后心一横,直接将数据引接到下游,先把上游通道资源给释放出来,方式就是不做任何业务处理,部署消费者把主MQ的数据直接投递到备用MQ中,实际处理业务的消费者处理备用MQ中的数据,然后再插入到临时表中,最后过度过去。
最后很快就把主MQ的数据引接到了临时MQ中,让主MQ能够正常接收任务,打通了业务,随后处理数据库层面的东西,最后最后再还原到原来架构。
防止堆积建议
一般来说,MQ我们要给他作为一个管道而不是一个池子来使用,因此我们应该尽量的让这个管道处于通畅的状态。我们后来除了上面的事故外,还发生过因为Redis集群重启导致缓存数据状态丢失,从而堆积等问题。后来我们就优化了使用MQ的流程。
1、生产者记录投递的数据,相当于备份,启动周期线程扫描任务,进行删除或者警告。
2、设置数据过期时间,无论数据是否消费成功都给删除掉,即使造成了数据丢失,因为有第一步,所以可以将丢失的数据给找回来。
3、消费者记录次数,将原来消费后记录,改到消费前记录,一旦消费次数达到了阈值,直接丢弃该任务。
要实现上面的功能,必须要有一点就是监控,警报一定要做好,因为如果没有一个好的监控系统,那么很容易造成数据丢失,不能及时发现。幸运的是我们有成熟的监控,前面造成数据堆积的重要原因就是内网部署没有接入监控,导致数据堆积了很久没有被发现,从而造成了服务不可用。