MQ消息积压,把我整吐血了
前言
我之前在一家餐饮公司待过两年,每天中午和晚上用餐高峰期,系统的并发量不容小觑。
为了保险起见,公司规定各部门都要在吃饭的时间轮流值班,防止出现线上问题时能够及时处理。
我当时在后厨显示系统团队,该系统属于订单的下游业务。
用户点完菜下单后,订单系统会通过发kafka
消息给我们系统,系统读取消息后,做业务逻辑处理,持久化订单和菜品数据,然后展示到划菜客户端。
这样厨师就知道哪个订单要做哪些菜,有些菜做好了,就可以通过该系统出菜。系统自动通知服务员上菜,如果服务员上完菜,修改菜品上菜状态,用户就知道哪些菜已经上了,哪些还没有上。这个系统可以大大提高后厨到用户的效率。
这一切的关键是消息中间件:kafka,如果它出现问题,将会直接影响到后厨显示系统的用户功能使用。
这篇文章跟大家一起聊聊,我们当时出现过的消息积压问题
,希望对你会有所帮助。
1 第一次消息积压
刚开始我们的用户量比较少,上线一段时间,mq的消息通信都没啥问题。
随着用户量逐步增多,每个商家每天都会产生大量的订单数据,每个订单都有多个菜品,这样导致我们划菜系统的划菜表的数据越来越多。
在某一天中午,收到商家投诉说用户下单之后,在平板上出现的菜品列表有延迟。
厨房几分钟之后才能看到菜品。
我们马上开始查原因。
出现这种菜品延迟的问题,必定跟kafka有关,因此,我们先查看kafka。
果然出现了消息积压
。
通常情况下,出现消息积压的原因有:
- mq消费者挂了。
- mq生产者生产消息的速度,大于mq消费者消费消息的速度。
我查了一下监控,发现我们的mq消费者,服务在正常运行,没有异常。
剩下的原因可能是:mq消费者消费消息的速度变慢了。
接下来,我查了一下划菜表,目前不太多只有几十万的数据。
看来需要优化mq消费者的处理逻辑了。
我在代码中增加了一些日志,把mq消息者中各个关键节点的耗时都打印出来了。
发现有两个地方耗时比较长:
- 有个代码是一个for循环中,一个个查询数据库处理数据的。
- 有个多条件查询数据的代码。
于是,我做了有针对性的优化。
将在for循环中一个个查询数据库的代码,改成通过参数集合,批量查询
数据。
有时候,我们需要从指定的用户集合中,查询出有哪些是在数据库中已经存在的。
实现代码可以这样写:
public List<User> queryUser(List<User> searchList) {
if (CollectionUtils.isEmpty(searchList)) {
return Collections.emptyList();
}
List<User> result = Lists.newArrayList();
searchList.forEach(user -> result.add(userMapper.getUserById(user.getId())));
return result;
}
这里如果有50个用户,则需要循环50次,去查询数据库。我们都知道,每查询一次数据库,就是一次远程调用。
如果查询50次数据库,就有50次远程调用,这是非常耗时的操作。
那么,我们如何优化呢?
具体代码如下:
public List<User> queryUser(List<User> searchList) {
if (CollectionUtils.isEmpty(searchList)) {
return Collections.emptyList();
}
List<Long> ids = searchList.stream().map(User::getId).collect(Collectors.toList());
return userMapper.getUserByIds(ids);
}
提供一个根据用户id集合批量查询用户的接口,只远程调用一次,就能查询出所有的数据。
多条件查询数据的地方,增加了一个联合索引
,解决了问题。
这样优化之后, mq消费者处理消息的速度提升了很多,消息积压问题被解决了。
2 第二次消息积压
没想到,过了几个月之后,又开始出现消息积压的问题了。
但这次是偶尔会积压,大部分情况不会。
这几天消息的积压时间不长,对用户影响比较小,没有引起商家的投诉。
我查了一下划菜表的数据只有几百万。
但通过一些监控,和DBA每天发的慢查询邮件,自己发现了异常。
我发现有些sql语句,执行的where条件是一模一样的,只有条件后面的参数值不一样,导致该sql语句走的索引不一样。
比如:order_id=123走了索引a,而order_id=124走了索引b。
有张表查询的场景有很多,当时为了满足不同业务场景,加了多个联合索引。
MySQL会根据下面几个因素选择索引:
- 通过采样数据来估算需要扫描的行数,如果扫描的行数多那可能io次数会更多,对cpu的消耗也更大。
- 是否会使用临时表,如果使用临时表也会影响查询速度;
- 是否需要排序,如果需要排序则也会影响查询速度。
综合1、2、3以及其它的一些因素,MySql优化器会选出它自己认为最合适的索引。
MySQL优化器是通过采样来预估要扫描的行数的,所谓采样
就是选择一些数据页来进行统计预估,这个会有一定的误差。
由于MVCC
会有多个版本的数据页,比如删除一些数据,但是这些数据由于还在其它的事务中可能会被看到,索引不是真正的删除,这种情况也会导致统计不准确,从而影响优化器的判断。
上面这两个原因导致MySQL在执行SQL语句时,会选错索引
。
明明使用索引a的时候,执行效率更高,但实际情况却使用了索引b。
为了解决MySQL选错索引的问题,我们使用了关键字force index
,来强制查询sql走索引a。
这样优化之后,这次小范围的消息积压问题被解决了。
3 第三次消息积压
过了半年之后,在某个晚上6点多钟。
有几个商家投诉过来,说划菜系统有延迟,下单之后,几分钟才能看到菜品。
我查看了一下监控,发现kafka消息又出现了积压的情况。
查了一下MySQL的索引,该走的索引都走了,但数据查询还是有些慢。
此时,我再次查了一下划菜表,惊奇的发现,短短半年表中有3千万的数据了。
通常情况下,单表的数据太多,无论是查询,还是写入的性能,都会下降。
这次出现查询慢的原因是数据太多了。
为了解决这个问题,我们必须:
- 做分库分表
- 将历史数据备份
由于现阶段做分库分表的代价太大了,我们的商户数量还没有走到这一步。
因此,我们当时果断选择了将历史数据做备份的方案。
当时我跟产品和DBA讨论了一下,划菜表只保留最近30天的数据,超过几天的数据写入到历史表
中。
这样优化之后,划菜表30天只会产生几百万的数据,对性能影响不大。
消息积压的问题被解决了。
最近就业形式比较困难,为了感谢各位小伙伴对苏三一直以来的支持,我特地创建了一些工作内推群, 看看能不能帮助到大家。
你可以在群里发布招聘信息,也可以内推工作,也可以在群里投递简历找工作,也可以在群里交流面试或者工作的话题。
进群方式,添加苏三的私人微信:su_san_java,备注:博客园+所在城市,即可加入。
4 第四次消息积压
通过上面这几次优化之后,很长一段时间,系统都没有出现消息积压的问题。
但在一年之后的某一天下午,又有一些商家投诉过来了。
此时,我查看公司邮箱,发现kafka消息积压的监控报警邮件一大堆。
但由于刚刚一直在开会,没有看到。
这次的时间点就有些特殊。
一般情况下,并发量大的时候,是中午或者晚上的用餐高峰期,而这次出现消息积压问题的时间是下午
。
这就有点奇怪了。
刚开始查询这个问题一点头绪都没有。
我问了一下订单组的同事,下午有没有发版,或者执行什么功能?
因为我们的划菜系统,是他们的下游系统,跟他们有直接的关系。
某位同事说,他们半小时之前,执行了一个批量修改订单状态的job,一次性修改了几万个订单的状态。
而修改了订单状态,会自动发送mq消息。
这样导致,他们的程序在极短的时间内,产生了大量的mq消息。
而我们的mq消费者根本无法处理这些消息,所以才会产生消息积压的问题。
我们当时一起查了kafka消息的积压情况,发现当时积压了几十万条消息。
要想快速提升mq消费者的处理速度,我们当时想到了两个方案:
- 增加partion数量。
- 使用线程池处理消息。
但考虑到,当时消息已经积压到几个已有的partion中了,再新增partion意义不大。
于是,我们只能改造代码,使用线程池处
理消息了。
为了开始消费积压的消息,我们将线程池的核心线程
和最大线程
数量调大到了50。
这两个参数是可以动态配置的。
这样调整之后,积压了几十万的mq消息,在20分钟左右被消费完了。
这次突然产生的消息积压问题被解决了。
解决完这次的问题之后,我们还是保留的线程池消费消息的逻辑,将核心线程数调到8
,最大线程数调到10
。
当后面出现消息积压问题,可以及时通过调整线程数量,先临时解决问题,而不会对用户造成太大的影响。
注意:使用线程池消费mq消息不是万能的。
该方案也有一些弊端,它有消息顺序的问题,也可能会导致服务器的CPU使用率飙升。此外,如果在多线程中调用了第三方接口,可能会导致该第三方接口的压力太大,而直接挂掉。
总之,MQ的消息积压问题,不是一个简单的问题。
虽说产生的根本原因是:MQ生产者生产消息的速度,大于MQ消费者消费消息的速度,但产生的具体原因有多种。
我们在实际工作中,需要针对不同的业务场景,做不同的优化。
我们需要对MQ队列中的消息积压情况,进行监控和预警,至少能够及时发现问题。
没有最好的方案,只有最合适当前业务场景的方案。
最后说一句(求关注,别白嫖我)
如果这篇文章对您有所帮助,或者有所启发的话,帮忙扫描下发二维码关注一下,您的支持是我坚持写作最大的动力。
求一键三连:点赞、转发、在看。
关注苏三的公众号:【苏三说技术】,在公众号中回复:面试、代码神器、开发手册、时间管理有超赞的粉丝福利,另外回复:加群,可以跟很多BAT大厂的前辈交流和学习。
MQ消息积压,把我整吐血了的更多相关文章
- 关于MQ的几件小事(六)消息积压在消息队列里怎么办
1.大量消息在mq里积压了几个小时了还没解决 场景:几千万条数据在MQ里积压了七八个小时,从下午4点多,积压到了晚上很晚,10点多,11点多.线上故障了,这个时候要不然就是修复consumer的问题, ...
- 不恰当使用线程池处理 MQ 消息引起的故障
现状 业务部门反应网站访问特别慢,负责运维监控的同事说MQ消息队列积压了,中间件的说应用服务器内存占用很高,GC 一直回收不了内存,GC 线程占了近 100% 的 CPU,其他的基本上都在等待,数据库 ...
- RabbitMQ消息积压的几种解决思路
在日常工作中使用RabbitMQ偶尔会遇不可预料的情况导致的消息积压,一般出现消息积压基本上分为几种情况: 消费者消费消息的速度赶不上生产速度,这总问题主要是业务逻辑没设计好消费者和生产者之间的平衡, ...
- mq消息堆积处理
1.大量消息在mq里积压 场景:几千万条数据在MQ里积压了七八个小时,从下午4点多,积压到了晚上很晚,10点多,11点多.线上故障了,这个时候要不然就是修复consumer的问题,让他恢复消费速度,然 ...
- 动态线程池框架 DynamicTp v1.0.6版本发布。还在为Dubbo线程池耗尽烦恼吗?还在为Mq消费积压烦恼吗?
DynamicTp 简介 DynamicTp 是一个基于配置中心实现的轻量级动态线程池管理工具,主要功能可以总结为 动态调参.通知报警.运行监控.三方包线程池管理等几大类. 经过几个版本迭代,目前最新 ...
- RabbitMQ:消息丢失 | 消息重复 | 消息积压的原因+解决方案+网上学不到的使用心得
前言 首先说一点,企业中最常用的实际上既不是RocketMQ,也不是Kafka,而是RabbitMQ. RocketMQ很强大,但主要是阿里推广自己的云产品而开源出来的一款消息队列,其实中小企业用Ro ...
- 阿里云ACE共创空间——MQ消息队列产品测试
一.产品背景消息队列是阿里巴巴集团自主研发的专业消息中间件. 产品基于高可用分布式集群技术,提供消息订阅和发布.消息轨迹查询.定时(延时)消息.资源统计.监控报警等一系列消息云服务,是企业级互联网架构 ...
- IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列
1.引言 消息是互联网信息的一种表现形式,是人利用计算机进行信息传递的有效载体,比如即时通讯网坛友最熟悉的即时通讯消息就是其具体的表现形式之一. 消息从发送者到接收者的典型传递方式有两种: 1)一种我 ...
- 避免MQ消息重发的简单实现思路
一.MQ消息发送 一.MQ消息发送 1.发送端MQ-client(消息生产者:Producer)将消息发送给MQ-server: 2.MQ-server将消息落地: 3.MQ-server回ACK给M ...
- Java语言快速实现简单MQ消息队列服务
目录 MQ基础回顾 主要角色 自定义协议 流程顺序 项目构建流程 具体使用流程 代码演示 消息处理中心 Broker 消息处理中心服务 BrokerServer 客户端 MqClient 测试MQ 小 ...
随机推荐
- docker运行javaWeb服务,操作文件异常
一.问题由来 部署一个测试服务在自己的服务器上面,然后运行其中的一个功能.然后报错,报错信息如下 二.问题分析 自己一开始也很疑惑,怎么会出现这个问题呢,自己明明把对应的文件放在对应的目录下面,并且已 ...
- Jetty的threadpool模块
Jetty提供的线程池相关的模块,如下: threadpool threadpool-virtual,使用JDK 21提供的virtual threads. threadpool-virtual-pr ...
- 本周四晚19:00知识赋能第3期直播丨OpenHarmony智能家居项目之控制面板功能实现
OpenAtom OpenHarmony(以下简称"OpenHarmony")开源开发者成长计划项目自 2021 年 10 月 24 日上线以来,在开发者中引发高度关注. 成长计划 ...
- 深入理解MD5算法:原理、应用与安全
第一章:引言 导言 在当今数字化时代,数据安全和完整性变得至关重要.消息摘要算法是一种用于验证数据完整性和安全性的重要工具.在众多消息摘要算法中,MD5(Message Digest Algorith ...
- UML 哲学之道——类图[三]
前言 简单整理一些uml中的类图. 正文 类的基本表示法: 名称.属性(类型.可见性).方法(参数.返回值.可见性) 想上面这样,第一行是名称,第二行是属性,第三行是方法 可见性: 表示public ...
- sql 语句系列(闰年)[八百章之第十九章]
前言 判断闰年还是挺有用的. mysql select DAY(LAST_DAY(DATE_ADD(CURRENT_DATE,INTERVAL -DAYOFYEAR(CURRENT_DATE)+1+3 ...
- docker 应用篇————docker 网络[十七]
前言 简单介绍一下docker 网络. 正文 使用ip addr. 可以看到网络. 有一个虚拟网卡: 那么基本上容器就处于这样的模式了. 那么也就是所有容器都在同一网关下面了. 那么问题来了,理论上容 ...
- websocket fleck demo
前言 fleck 比较简洁,想看下他的源码的,先感受一下demo吧. 正文 先上代码. static IDictionary<string, IWebSocketConnection> d ...
- c# countDownEvent类
前言 把异步先总结完吧. countDownEvent 这东西是干什么的呢? 比如说我们比赛跑步,我们需要得出的是第一二三名得出后就可以先统计出来,因为比较重要,后面没有获得获奖名次的可以后续统计出来 ...
- 美团二面:如何保证Redis与Mysql双写一致性?连续两个面试问到了!
引言 Redis作为一款高效的内存数据存储系统,凭借其优异的读写性能和丰富的数据结构支持,被广泛应用于缓存层以提升整个系统的响应速度和吞吐量.尤其是在与关系型数据库(如MySQL.PostgreSQL ...