第一次消息积压
通常情况下,出现消息积压的原因有:
-
mq消费者挂了。
-
mq生产者生产消息的速度,大于mq消费者消费消息的速度。
消费者挂了重启消费者
第二点主要mq消费者消费消息的速度变慢了
发现有两个地方耗时比较长:
-
有个代码是一个for循环中,一个个查询数据库处理数据的。
-
有个多条件查询数据的代码。
将在for循环中一个个查询数据库的代码,改成通过参数集合,批量查询
数据。
多条件查询数据的地方,增加了一个联合索引。
第二次消息积压
当时数据只有几百万,发现偶尔会存在消息积压的情况,通过监控发现了慢sql,发现
有些sql语句,执行的where条件是一模一样的,只有条件后面的参数值不一样,导致该sql语句走的索引不一样。
比如:vin=走了索引a,而vin=124走了索引b
有张表查询的场景有很多,当时为了满足不同业务场景,加了多个联合索引。
MySQL会根据下面几个因素选择索引:
-
通过采样数据来估算需要扫描的行数,如果扫描的行数多那可能io次数会更多,对cpu的消耗也更大。
-
是否会使用临时表,如果使用临时表也会影响查询速度;
-
是否需要排序,如果需要排序则也会影响查询速度。
综合1、2、3以及其它的一些因素,MySql优化器会选出它自己认为最合适的索引。
MySQL优化器是通过采样来预估要扫描的行数的,所谓采样
就是选择一些数据页来进行统计预估,这个会有一定的误差。
由于MVCC
会有多个版本的数据页,比如删除一些数据,但是这些数据由于还在其它的事务中可能会被看到,索引不是真正的删除,这种情况也会导致统计不准确,从而影响优化器的判断。
上面这两个原因导致MySQL在执行SQL语句时,会选错索引
。
明明使用索引a的时候,执行效率更高,但实际情况却使用了索引b。
为了解决MySQL选错索引的问题,我们使用了关键字force index
,来强制查询sql走索引a。
这样优化之后,这次小范围的消息积压问题被解决了。
第三次消息积压
由于数据库表数据激增,单表的数据太多,无论是查询,还是写入的性能,都会下降。
-
做分库分表
-
将历史数据备份
只保留最近30天的数据,超过几天的数据写入到历史表
中。
这样优化之后,30天只会产生几百万的数据,对性能影响不大。
消息积压的问题被解决了。
第四次消息积压
有时程序在极短的时间内,产生了大量的mq消息。
mq消费者根本无法处理这些消息,所以才会产生消息积压的问题。
要想快速提升mq消费者的处理速度,我们当时想到了两个方案:
-
增加partion数量。
-
使用线程池处理消息。
但考虑到,当时消息已经积压到几个已有的partion中了,再新增partion意义不大。
使用线程池处
理消息
为了开始消费积压的消息,我们将线程池的核心线程
和最大线程
数量调大到了50。
这两个参数是可以动态配置的。
这样调整之后,积压了几十万的mq消息,在20分钟左右被消费完了。
这次突然产生的消息积压问题被解决了。
解决完这次的问题之后,我们还是保留的线程池消费消息的逻辑,将核心线程数调到8
,最大线程数调到10
。
当后面出现消息积压问题,可以及时通过调整线程数量,先临时解决问题,而不会对用户造成太大的影响。
注意:使用线程池消费mq消息不是万能的。该方案也有一些弊端,它有消息顺序的问题,也可能会导致服务器的CPU使用率飙升。此外,如果在多线程中调用了第三方接口,可能会导致该第三方接口的压力太大,而直接挂掉。
总之,MQ的消息积压问题,不是一个简单的问题。
虽说产生的根本原因是:MQ生产者生产消息的速度,大于MQ消费者消费消息的速度,但产生的具体原因有多种。
我们在实际工作中,需要针对不同的业务场景,做不同的优化。
我们需要对MQ队列中的消息积压情况,进行监控和预警,至少能够及时发现问题。
没有最好的方案,只有最合适当前业务场景的方案。