如何应对 RocketMQ 消息堆积?

RocketMQ消息堆积应对策略

1 基础概念

消费者在消费的过程中,消费的速度跟不上服务端的发送速度,未处理的消息会越来越多,消息出现堆积进而会造成消息消费延迟。

虽然笔者经常讲:RocketMQ 、Kafka 具备堆积的能力,但是以下场景需要重点关注消息堆积和延迟的问题:

  1. 业务系统上下游能力不匹配造成的持续堆积,且无法自行恢复。

  2. 业务系统对消息的消费实时性要求较高,即使是短暂的堆积造成的消息延迟也无法接受。

2 消费原理

图片

客户端使用 Push 模式 启动后,消费消息时,分为以下两个阶段:

  • 阶段一:拉取消息

    客户端通过长轮询批量拉取的方式从 Broker 服务端获取消息,将拉取到的消息缓存到本地缓冲队列中。

    客户端批量拉取消息,常见内网环境下都会有很高的吞吐量,例如:1个单线程单分区的低规格机器(4C8GB)可以达到几万 TPS ,如果是多个分区可以达到几十万 TPS 。所以这一阶段一般不会成为消息堆积的瓶颈。

  • 阶段二:消费消息

    提交消费线程,客户端将本地缓存的消息提交到消费线程中,使用业务消费逻辑进行处理。

    此时客户端的消费能力就完全依赖于业务逻辑的复杂度(消费耗时)和消费逻辑并发度了。如果业务处理逻辑复杂,处理单条消息耗时都较长,则整体的消息吞吐量肯定不会高,此时就会导致客户端本地缓冲队列达到上限,停止从服务端拉取消息。

通过以上客户端消费原理可以看出,消息堆积的主要瓶颈在于本地客户端的消费能力,即消费耗时消费并发度

想要避免和解决消息堆积问题,必须合理的控制消费耗时和消息并发度,其中消费耗时的优先级高于消费并发度,必须先保证消费耗时的合理性,再考虑消费并发度问题。

3 消费瓶颈

3.1 消费耗时

影响消费耗时的消费逻辑主要分为 CPU 内存计算和外部 I/O 操作,通常情况下代码中如果没有复杂的递归和循环的话,内部计算耗时相对外部 I/O 操作来说几乎可以忽略。

外部 I/O 操作通常包括如下业务逻辑:

  • 读写外部数据库,例如 MySQL 数据库读写。

  • 读写外部缓存等系统,例如 Redis 读写。

  • 下游系统调用,例如 Dubbo 调用或者下游 HTTP 接口调用。

这类外部调用的逻辑和系统容量需要提前梳理,掌握每个调用操作预期的耗时,这样才能判断消费逻辑中I/O操作的耗时是否合理。

通常消费堆积都是由于这些下游系统出现了服务异常、容量限制导致的消费耗时增加。

例如:某业务消费逻辑中需要调用下游 Dubbo 接口 ,单次消费耗时为 20 ms,平时消息量小未出现异常。业务侧进行大促活动时,下游 Dubbo 服务未进行优化,消费单条消息的耗时增加到 200 ms,业务侧可以明显感受到消费速度大幅下跌。此时,通过提升消费并行度并不能解决问题,需要大幅提高下游 Dubbo 服务性能才行。

3.2 消费并发度

绝大部分消息消费行为都属于 IO 密集型,即可能是操作数据库,或者调用 RPC,这类消费行为的消费速度在于后端数据库或者外系统的吞吐量,通过增加消费并行度,可以提高总的消费吞吐量,但是并行度增加到一定程度,反而会下降。

所以,应用必须要设置合理的并行度。如下有几种修改消费并行度的方法:

  • 同一个 ConsumerGroup 下,通过增加 Consumer 实例数量来提高并行度(需要注意的是超过订阅队列数的 Consumer 实例无效)。可以通过加机器,或者在已有机器启动多个进程的方式。

  • 提高单个 Consumer 实例的消费并行线程,通过修改参数 consumeThreadMin、consumeThreadMax 实现。

4 解决策略

当面对消息堆积问题时,我们需要明确到底哪个环节出现问题了,不要慌张,也不要贸然动手。

4.1 确认消息的消费耗时是否合理

首先,我们需要查看消费耗时,确认消息的消费耗时是否合理。查看消费耗时一般来讲有两种方式:

1、打印日志

public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
     try {
        for (MessageExt messageExt : msgs) {
           long start = System.currentTimeMillis();
           // TODO 业务逻辑
          logger.info("MessageId:" + messageExt.getMsgId() + " costTime:" + (System.currentTimeMillis() - start));
        }
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
     } catch (Exception e) {
        logger.error("consumeMessage error:", e);
        return ConsumeConcurrentlyStatus.RECONSUME_LATER;
     }
}

2、查看消息轨迹

图片

当确定好消费耗时后,可以根据耗时大小,采取不同的措施。

  • 若查看到消费耗时较长,则需要查看客户端 JVM 堆栈信息排查具体业务逻辑,并优化消费逻辑。

  • 若查看到消费耗时正常,则有可能是因为消费并发度不够导致消息堆积,需要逐步调大消费线程或扩容节点来解决。

4.2 查看客户端 JVM 的堆栈

假如消费耗时非常高,需要查看 Consumer 实例 JVM 的堆栈 。

  1. 通过 jps -m 或者 ps -ef | grep java 命令获取当前正在运行的 Java 程序,通过启动主类即可获得应用的进程 pid ;

  2. 通过 jstack pid > stack.log 命令获取线程的堆栈。

  3. 执行以下命令,查看 ConsumeMessageThread 的信息 。

cat stack.log | grep ConsumeMessageThread -A 10 --color

常见的异常堆栈信息如下:

  • 示例1:空闲无堆积的堆栈 。

    消费空闲情况下消费线程都会处于 WAITING 状态等待从消费任务队里中获取消息。

图片

  • 示例2:消费逻辑有抢锁休眠等待等情况 。

    消费线程阻塞在内部的一个睡眠等待上,导致消费缓慢。

图片

  • 示例3:消费逻辑操作数据库等外部存储卡住 。

    消费线程阻塞在外部的 HTTP 调用上,导致消费缓慢。

    图片

5 总结

客户端使用 Push模式 启动后,消费消息时,分为以下两个阶段:拉取消息消费消息

客户端消费原理可以看出,消息堆积的主要瓶颈在于本地客户端的消费能力,即消费耗时消费并发度

首先分析消费耗时,然后根据耗时大小,采取不同的措施。

  • 若查看到消费耗时较长,则查看客户端堆栈信息排查具体业务逻辑,并优化消费逻辑。

  • 若查看到消费耗时正常,则有可能是因为消费并发度不够导致消息堆积,需要逐步调大消费线程或扩容节点来解决。

阅读 1064

<think>嗯,用户问的是如何解决RocketMQ消息堆积的问题,最佳实践是什么。首先,我需要回忆一下RocketMQ消息处理机制。消息堆积通常发生在消费者处理速度跟不上生产者发送速度的时候。可能的原因有很多,比如消费者性能不足、消费逻辑复杂、线程数不够,或者网络延迟等等。 根据引用里的资料,比如引用[2]提到同步刷盘模式对事务消息的重要性,但消息堆积可能更多涉及消费者端的优化。引用[3]里提到Kafka和RocketMQ的事务机制差异,但用户这里的问题是关于堆积,可能需要考虑消费者的并行度、批量消费这些点。引用[4]提到RocketMQ的高可靠性和扩展性,可能解决方案包括水平扩展消费者实例。 首先,应该分析堆积原因。需要检查消费者组的消费延迟,确认是哪个环节的问题。比如,消费者是否有足够的线程在处理消息,或者消息处理逻辑是否存在瓶颈,比如数据库操作慢,或者外部服务调用延迟。 接下来,可能的解决步骤包括:1. 提升消费者处理能力,比如增加消费者实例,或者优化消费逻辑,比如异步处理、批量处理。2. 调整RocketMQ的消费参数,比如增大批量拉取的消息数,或者调整线程池大小。3. 如果消息不重要,可以考虑跳过或者降级处理,不过这可能不符合所有场景。4. 预防措施,比如流量控制,避免突发流量导致堆积。 另外,引用[3]提到监控的重要性,需要监控消费进度和延迟,及时发现堆积情况。设置合理的告警,当堆积量超过阈值时触发处理流程。 可能还需要考虑消息的过期时间,如果消息有时效性,可以设置TTL自动丢弃过期消息,避免无效处理。同时,死信队列的处理也很重要,确保无法处理的消息不会阻塞正常流程。 用户可能需要具体的配置示例,比如如何调整pullBatchSize或者consumeThreadMax。另外,最佳实践可能包括使用集群模式而不是广播模式,合理设置重试次数,避免无限重试导致堆积加剧。 还需要注意顺序消息的问题,如果业务允许,可以改为并发消费,提升处理速度。如果必须顺序消费,可能需要优化单个队列的处理速度,或者增加队列数量。 最后,如果堆积已经发生,可能需要临时扩容消费者,或者通过重置位点来跳过部分消息,但这需要谨慎操作,避免数据丢失。</think>### RocketMQ消息堆积问题的解决方案与最佳实践 #### 一、原因分析 消息堆积通常由以下场景引发: 1. **消费端性能不足**:单线程处理、复杂业务逻辑(如数据库事务阻塞)或资源竞争 2. **生产消费速率失衡**:突发流量(如秒杀活动)或持续高吞吐场景 3. **系统异常**:网络抖动、存储故障或消息重试堆积[^2] #### 二、解决方案实施步骤 1. **消费者能力提升** ```java // 示例:调整消费者线程数配置 DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer_group"); consumer.setConsumeThreadMin(20); // 最小线程数 consumer.setConsumeThreadMax(64); // 最大线程数 ``` - 增加消费者实例(水平扩展) - 优化消费逻辑:异步处理、批量操作、缓存预加载 - 调整`pullBatchSize`参数(建议值32-256)[^4] 2. **服务端优化** ```shell # Broker配置调整示例 flushDiskType = SYNC_FLUSH # 同步刷盘保证可靠性[^2] maxTransferCountOnMessageInMemory=4096 # 内存中最大传输消息数 ``` - 增加Topic队列数(需配合消费者扩容) - 开启消息过滤功能,减少无效消息传输 3. **应急处理措施** - 临时增加消费者组(特殊处理组) - 设置消息TTL自动过期(需评估业务影响) - 使用控制台命令跳过堆积消息: ```shell sh mqadmin resetOffsetByTime -n 127.0.0.1:9876 -t TEST_TOPIC -g consumer_group -s now ``` #### 三、最佳实践组合方案 | 场景 | 配置方案 | 监控指标 | |------|----------|----------| | 日常预防 | 消费线程数=CPU核心数*2, pullBatchSize=128 | ConsumerTPS/ConsumerOffset | | 大促期间 | 自动扩缩容+限流降级+消息预加载 | PendingMessage/NetworkLatency | | 故障恢复 | 死信队列隔离+定时重试+人工介入 | DLQ_Count/RetryRate | #### 四、注意事项 1. 顺序消息场景需保持队列与消费者1:1关系 2. 事务消息建议使用同步刷盘模式[^2] 3. 监控建议: - 设置堆积量报警阈值(建议>1000条/队列) - 跟踪消费耗时百分位数(P99<200ms) 相关问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值