MQ消息积压,把公司系统整挂了!

👉 这是一个或许对你有用的社群

🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料: 

71d629141429042b539cadf1c6fb2517.gif

👉这是一个或许对你有用的开源项目

国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。

功能涵盖 RBAC 权限、SaaS 多租户、数据权限、商城、支付、工作流、大屏报表、微信公众号、CRM 等等功能:

  • Boot 仓库:https://gitee.com/zhijiantianya/ruoyi-vue-pro

  • Cloud 仓库:https://gitee.com/zhijiantianya/yudao-cloud

  • 视频教程:https://doc.iocoder.cn

【国内首批】支持 JDK 21 + SpringBoot 3.2.2、JDK 8 + Spring Boot 2.7.18 双版本 

来源:苏三说技术


前言

我之前在一家餐饮公司待过两年,每天中午和晚上用餐高峰期,系统的并发量不容小觑。为了保险起见,公司规定各部门都要在吃饭的时间轮流值班,防止出现线上问题时能够及时处理。

我当时在后厨显示系统团队,该系统属于订单的下游业务。

用户点完菜下单后,订单系统会通过发kafka消息给我们系统,系统读取消息后,做业务逻辑处理,持久化订单和菜品数据,然后展示到划菜客户端。

这样厨师就知道哪个订单要做哪些菜,有些菜做好了,就可以通过该系统出菜。系统自动通知服务员上菜,如果服务员上完菜,修改菜品上菜状态,用户就知道哪些菜已经上了,哪些还没有上。这个系统可以大大提高后厨到用户的效率。

b7b0d39e3ca55f0acb00b1e441e8d6bc.png

这一切的关键是消息中间件:kafka,如果它出现问题,将会直接影响到后厨显示系统的用户功能使用。

这篇文章跟大家一起聊聊,我们当时出现过的消息积压问题,希望对你会有所帮助。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro

  • 视频教程:https://doc.iocoder.cn/video/

1 第一次消息积压

刚开始我们的用户量比较少,上线一段时间,mq的消息通信都没啥问题。

随着用户量逐步增多,每个商家每天都会产生大量的订单数据,每个订单都有多个菜品,这样导致我们划菜系统的划菜表的数据越来越多。

在某一天中午,收到商家投诉说用户下单之后,在平板上出现的菜品列表有延迟。

厨房几分钟之后才能看到菜品。

我们马上开始查原因。

出现这种菜品延迟的问题,必定跟kafka有关,因此,我们先查看kafka。

果然出现了消息积压

通常情况下,出现消息积压的原因有:

  1. mq消费者挂了。

  2. mq生产者生产消息的速度,大于mq消费者消费消息的速度。

我查了一下监控,发现我们的mq消费者,服务在正常运行,没有异常。

剩下的原因可能是:mq消费者消费消息的速度变慢了。

接下来,我查了一下划菜表,目前不太多只有几十万的数据。

看来需要优化mq消费者的处理逻辑了。

我在代码中增加了一些日志,把mq消息者中各个关键节点的耗时都打印出来了。

发现有两个地方耗时比较长:

  1. 有个代码是一个for循环中,一个个查询数据库处理数据的。

  2. 有个多条件查询数据的代码。

于是,我做了有针对性的优化。

将在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消费者处理消息的速度提升了很多,消息积压问题被解决了。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud

  • 视频教程:https://doc.iocoder.cn/video/

2 第二次消息积压

没想到,过了几个月之后,又开始出现消息积压的问题了。

但这次是偶尔会积压,大部分情况不会。

这几天消息的积压时间不长,对用户影响比较小,没有引起商家的投诉。

我查了一下划菜表的数据只有几百万。

但通过一些监控,和DBA每天发的慢查询邮件,自己发现了异常。

我发现有些sql语句,执行的where条件是一模一样的,只有条件后面的参数值不一样,导致该sql语句走的索引不一样。

比如:order_id=123走了索引a,而order_id=124走了索引b。

有张表查询的场景有很多,当时为了满足不同业务场景,加了多个联合索引。

MySQL会根据下面几个因素选择索引:

  1. 通过采样数据来估算需要扫描的行数,如果扫描的行数多那可能io次数会更多,对cpu的消耗也更大。

  2. 是否会使用临时表,如果使用临时表也会影响查询速度;

  3. 是否需要排序,如果需要排序则也会影响查询速度。

综合1、2、3以及其它的一些因素,MySql优化器会选出它自己认为最合适的索引。

MySQL优化器是通过采样来预估要扫描的行数的,所谓采样就是选择一些数据页来进行统计预估,这个会有一定的误差。

由于MVCC会有多个版本的数据页,比如删除一些数据,但是这些数据由于还在其它的事务中可能会被看到,索引不是真正的删除,这种情况也会导致统计不准确,从而影响优化器的判断。

上面这两个原因导致MySQL在执行SQL语句时,会选错索引

明明使用索引a的时候,执行效率更高,但实际情况却使用了索引b。

为了解决MySQL选错索引的问题,我们使用了关键字force index,来强制查询sql走索引a。

这样优化之后,这次小范围的消息积压问题被解决了。

3 第三次消息积压

过了半年之后,在某个晚上6点多钟。

有几个商家投诉过来,说划菜系统有延迟,下单之后,几分钟才能看到菜品。

我查看了一下监控,发现kafka消息又出现了积压的情况。

查了一下MySQL的索引,该走的索引都走了,但数据查询还是有些慢。

此时,我再次查了一下划菜表,惊奇的发现,短短半年表中有3千万的数据了。

通常情况下,单表的数据太多,无论是查询,还是写入的性能,都会下降。

这次出现查询慢的原因是数据太多了。

为了解决这个问题,我们必须:

  1. 做分库分表

  2. 将历史数据备份

由于现阶段做分库分表的代价太大了,我们的商户数量还没有走到这一步。

因此,我们当时果断选择了将历史数据做备份的方案。

当时我跟产品和DBA讨论了一下,划菜表只保留最近30天的数据,超过几天的数据写入到历史表中。

这样优化之后,划菜表30天只会产生几百万的数据,对性能影响不大。

消息积压的问题被解决了。

4 第四次消息积压

通过上面这几次优化之后,很长一段时间,系统都没有出现消息积压的问题。

但在一年之后的某一天下午,又有一些商家投诉过来了。

此时,我查看公司邮箱,发现kafka消息积压的监控报警邮件一大堆。

但由于刚刚一直在开会,没有看到。

这次的时间点就有些特殊。

一般情况下,并发量大的时候,是中午或者晚上的用餐高峰期,而这次出现消息积压问题的时间是下午

这就有点奇怪了。

刚开始查询这个问题一点头绪都没有。

我问了一下订单组的同事,下午有没有发版,或者执行什么功能?

因为我们的划菜系统,是他们的下游系统,跟他们有直接的关系。

某位同事说,他们半小时之前,执行了一个批量修改订单状态的job,一次性修改了几万个订单的状态。

而修改了订单状态,会自动发送mq消息。

这样导致,他们的程序在极短的时间内,产生了大量的mq消息。

而我们的mq消费者根本无法处理这些消息,所以才会产生消息积压的问题。

我们当时一起查了kafka消息的积压情况,发现当时积压了几十万条消息。

要想快速提升mq消费者的处理速度,我们当时想到了两个方案:

  1. 增加partion数量。

  2. 使用线程池处理消息。

但考虑到,当时消息已经积压到几个已有的partion中了,再新增partion意义不大。

于是,我们只能改造代码,使用线程池处理消息了。

为了开始消费积压的消息,我们将线程池的核心线程最大线程数量调大到了50。

这两个参数是可以动态配置的。

这样调整之后,积压了几十万的mq消息,在20分钟左右被消费完了。

这次突然产生的消息积压问题被解决了。

解决完这次的问题之后,我们还是保留的线程池消费消息的逻辑,将核心线程数调到8,最大线程数调到10

当后面出现消息积压问题,可以及时通过调整线程数量,先临时解决问题,而不会对用户造成太大的影响。

注意:使用线程池消费mq消息不是万能的。该方案也有一些弊端,它有消息顺序的问题,也可能会导致服务器的CPU使用率飙升。此外,如果在多线程中调用了第三方接口,可能会导致该第三方接口的压力太大,而直接挂掉。

总之,MQ的消息积压问题,不是一个简单的问题。

虽说产生的根本原因是:MQ生产者生产消息的速度,大于MQ消费者消费消息的速度,但产生的具体原因有多种。

我们在实际工作中,需要针对不同的业务场景,做不同的优化。

我们需要对MQ队列中的消息积压情况,进行监控和预警,至少能够及时发现问题。

没有最好的方案,只有最合适当前业务场景的方案。


欢迎加入我的知识星球,全面提升技术能力。

👉 加入方式,长按”或“扫描”下方二维码噢

18326f398f870be92af3ca2cec8df3de.png

星球的内容包括:项目实战、面试招聘、源码解析、学习路线。

2d54075305e8afc5d18f6ae35bf05d27.png

1365b06e21982be458232a9820767ad3.png838fc6ad437c540f58565a2817514865.png2ac47a4edadfd28649728cf671c8fd18.pngc60b6c51b3a2439b020e6fe5386b28bb.png

文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)
【SCI复现】含可再生能源与储能的区域微电网最优运行:应对不确定性的解鲁棒性与非预见性研究(Matlab代码实现)内容概要:本文围绕含可再生能源与储能的区域微电网最优运行展开研究,重点探讨应对不确定性的解鲁棒性与非预见性策略,通过Matlab代码实现SCI论文复现。研究涵盖多阶段鲁棒调度模型、机会约束规划、需求响应机制及储能系统优化配置,结合风电、光伏等可再生能源出力的不确定性建模,提出兼顾系统经济性与鲁棒性的优化运行方案。文中详细展示了模型构建、算法设计(如C&CG算法、大M法)及仿真验证全过程,适用于微电网能量管理、电力系统优化调度等领域的科研与工程实践。; 适合人群:具备一定电力系统、优化理论和Matlab编程基础的研究生、科研人员及从事微电网、能源管理相关工作的工程技术人员。; 使用场景及目标:①复现SCI级微电网鲁棒优化研究成果,掌握应对风光负荷不确定性的建模与求解方法;②深入理解两阶段鲁棒优化、分布鲁棒优化、机会约束规划等先进优化方法在能源系统中的实际应用;③为撰写高水平学术论文或开展相关课题研究提供代码参考和技术支持。; 阅读建议:建议读者结合文档提供的Matlab代码逐模块学习,重点关注不确定性建模、鲁棒优化模型构建与求解流程,并尝试在不同场景下调试与扩展代码,以深化对微电网优化运行机制的理解。
<think>嗯,用户问的是如何处理MQ消息积压的问题,我需要仔细想想该怎么回答。首先,我得回忆一下常见的消息积压原因和处理方法。消息积压通常出现在生产速度超过消费速度的时候,可能有很多原因,比如消费者处理能力不足、消费者故障、消息处理逻辑复杂,或者资源不足等等。 用户可能是一个开发者或者系统管理员,遇到了线上消息积压的问题,需要快速找到解决方法。他们可能想了解如何快速应对当前的积压,同时如何预防未来的积压。所以我的回答需要结构清晰,分步骤给出解决方案,同时要涵盖应急处理、根本原因分析和预防措施。 首先,应急处理方面,扩容消费者实例是个直接的办法。增加消费者数量可以提升消费能力,尤其是对于分区或队列较多的系统,比如Kafka的分区扩容。另外,提高消费者并行度,比如调线程池参数,或者优化批处理。不过要注意,扩容可能需要系统支持水平扩展,否则可能无效。另外,跳过非关键消息或降级处理也是一种应急手段,但需要确保业务允许。 接下来是根本原因分析。需要检查是否有消费者故障,比如日志是否有异常,消费者是否正常心跳。网络问题也可能导致消费延迟,还有消费逻辑是否存在性能问题,比如慢查询、复杂计算或者外部依赖延迟。消息生产是否突增,比如促销活动导致流量激增,这时候可能需要限流或者提前准备资源。 然后是优化消费逻辑。比如将同步操作改为异步,减少数据库操作的耗时,或者引入缓存、批量处理。如果顺序不重要,可以增加消费者并行度,调预处理步骤,比如过滤无效消息。优化代码中的低效部分,比如避免重复计算,减少锁竞争,使用更高效的数据结构。 长期预防方面,监控和预警是关键,设置积压阈值报警,实时监控消费延迟。自动伸缩可以根据负载自动调消费者数量,比如K8s的HPA。还有限流和降级策略,生产端限流防止突发流量,消费端降级非核心功能。设计上,提高系统的容错和重试机制,比如死信队列处理失败消息,合理设置重试次数和间隔。 另外,一些特定场景的处理方法,比如调消息有效期,避免积压消息堆积导致存储问题。或者数据迁移时临时调消费速率。需要根据具体业务情况选择合适的方法。 我需要确保回答覆盖这些方面,结构清晰,分点说明,让用户能够一步步排查和处理。同时要注意使用正确的中文术语,避免技术术语错误。可能还需要提醒用户,处理积压时要评估业务影响,不能盲目操作,比如跳过消息可能导致数据不一致,需要业务方确认。 最后,总结一下处理流程:先应急,再分析原因,优化逻辑,最后长期预防。这样用户能有一个清晰的解决思路。可能还需要强调监控的重要性,以及自动伸缩和限流这些自动化手段的必要性。确保回答全面且实用,帮助用户真正解决问题。</think>处理MQ消息积压问题需分步骤解决,以下是系统化处理方案: #### 一、应急处理(快速止血) 1. **扩容消费者实例** - 增加消费者组节点数量(如Kafka需同步增加分区数) - 调消费者线程池参数: ```properties spring.kafka.listener.concurrency=8 # 根据CPU核数调 ``` - 示例:若原有2个消费者节点,可扩容至8节点,理论消费能力提升4倍 2. **临时跳过非关键消息** ```java // RocketMQ示例:重置消费位点到最新位置 consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); ``` *适用场景:允许丢失部分数据的监控/日志类消息* 3. **降级处理** - 关闭次要业务逻辑 - 简化消息处理流程(如跳过数据校验) #### 二、根因分析(必须同步进行) $$ \text{积压时间} = \frac{\text{堆积消息量}}{\text{生产速率} - \text{消费速率}} $$ 通过监控指标定位瓶颈: 1. **消费端检查** - 消费延迟监控:`kafka.consumer.lag` - 资源利用率:CPU >80% 或 GC时间 >1s需优化 - 下游依赖延迟:数据库响应时间、第三方API成功率 2. **生产端检查** - 突发流量是否超出设计容量 - 消息体大小异常(如超过配置的`max.message.bytes`) #### 三、消费逻辑优化(核心手段) 1. **批量消费优化** ```java // RocketMQ批量消费配置 consumer.setConsumeMessageBatchMaxSize(32); ``` *配合数据库批量写入可提升5-10倍吞吐量* 2. **异步化处理** ```python # RabbitMQ示例:将IO操作移交线程池 async def callback(ch, method, properties, body): await loop.run_in_executor(None, sync_processing, body) ``` 3. **热点数据缓存** ```java // 使用Caffeine缓存减少数据库查询 Cache<String, ProductInfo> cache = Caffeine.newBuilder() .maximumSize(10_000) .build(); ``` #### 四、架构级解决方案 1. **自动扩缩容** ```yaml # K8s HPA配置示例 metrics: - type: External external: metric: name: kafka_consumer_lag target: type: AverageValue averageValue: 1000 ``` 2. **分级消息设计** | 等级 | 延迟要求 | 处理方式 | |------|----------|-------------------| | L1 | <1s | 内存队列直连Worker| | L2 | <1min | RabbitMQ/Kafka | | L3 | 小时级 | 离线任务处理 | 3. **重试队列优化** ```mermaid graph LR A[主队列] --> B{处理成功?} B -->|是| C[完成] B -->|否| D[重试队列:10s后] D --> E{重试3次?} E -->|否| A E -->|是| F[死信队列人工处理] ``` #### 五、预防措施 1. **容量压测** $$ \text{需保障容量} = \text{日常峰值} \times 3 $$ 定期验证系统极限处理能力 2. **监控体系** - 关键指标报警阈值: - 消费延迟 > 5分钟 - 错误率 > 1% - CPU使用率 > 70% 3. **灰度机制** 新消费者版本先引流10%流量验证 #### 典型场景处理 1. **数据库关联型积压** - 使用`EXPLAIN`分析慢查询 - 添加复合索引: ```sql ALTER TABLE orders ADD INDEX idx_status_created(status, created_at); ``` 2. **计算密集型积压** - 启用GPU加速(如TensorFlow推理) - 调用公式优化: $$ \text{原计算} = \sum_{i=1}^{n} \frac{x_i^2}{y_i} \Rightarrow \text{优化为} \frac{1}{n}\sum x_i^2 \cdot \sum \frac{1}{y_i} $$ 处理完成后需验证: 1. 消费延迟降为秒级 2. 系统资源留30%余量 3. 消息轨迹完可追溯
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值