一次RocketMQ ons SDK Bug导致消息不断堆积到重试队列的案例分析

本文涉及的产品
云原生网关 MSE Higress,422元/月
容器镜像服务 ACR,镜像仓库100个 不限时长
函数计算FC,每月15万CU 3个月
简介: 一次RocketMQ ons SDK Bug导致消息不断堆积到重试队列的案例分析

背景介绍

系统运行在专有云,应用运行时环境是EDAS Container( EDAS Container是EDAS 平台 HSF 应用运行的基础容器,EDAS Container 包含 Ali-Tomcat 和 Pandora),消息处理使用的是【ons SDK】,消息消费者使用【PUSH】方式【批量】消费【普通消息】,MessageModel是【CLUSTERING】。

为了解决RocketMQ Producer某个性能问题,对Pandora进行了升级(主要是升级RocketMQ版本)。

下面从技术角度对升级中遇到的问题及分析过程进行总结,积累经验以避免类似问题的发生。

问题描述

Pandora升级完成后,我们在RocketMQ控制台看到【消费者状态】->【实时消息堆积量】有8亿条,而每个Consumer实例堆积量是几十条,如图1:

在【消费者状态】->【连接详情】有消息消费失败的情况,如图2:

在应用服务器ons.log也可以实时查看消息消费的指标信息,如图3:

这部分的统计指标的实现可以查看:org.apache.rocketmq.client.stat.ConsumerStatsManager

分析过程

根据我们前面几篇关于MQ消息堆积的文章,可以知道:

  1. 消息堆积总量与Consumer实例消息堆积量相符的情况下,通常是Consumer消费能力弱导致堆积
  2. 消息堆积总量大,而Consumer实例消息堆积量很小的情况下,通常是消息堆积在了重试队列中

从【问题描述】看,更像是发生了第二种情况导致的消息堆积。

总体思路

消息在重试队列中堆积,说明Consumer实例消费消息的时候出现了某些异常,导致Consumer实例将消息发送到了Broker重试队列中,所以我们分析【哪些地方】调用了【发送消息到Broker重试队列的接口】就基本抓住了这个问题的关键。

通过分析RocketMQ源码,发现主要有两个地方调用了【发送消息到Broker重试队列的接口】:

  1. 一个是org.apache.rocketmq.client.impl.consumer.ConsumeMessageConcurrentlyService的内部类ConsumeRequest
  2. 另一个是org.apache.rocketmq.client.impl.consumer.ConsumeMessageConcurrentlyService中清理过期Message的定时任务(最终是交给每个ProcessQueue来清理各自的Message)

ConsumeRequest

下面贴一下主要的代码,与该问题不相关的代码省略掉了;如果不想看代码,可以跳过此处,查看后面的流程图。

为了便于理解,我们使用流程图来表达下图4中代码主要逻辑,见图5:

分析上面流程及代码,发现ConsumeConcurrentlyContext类的ackIndex变量是分析消息成功与失败的核心变量。

是否业务处理异常?

RocketMQ框架在业务处理类出现下面情况的时候,认为消息消费失败:

  1. 业务处理类返回ConsumeConcurrentlyStatus.RECONSUME_LATER
  2. 业务处理类返回null
  3. 业务处理类抛出异常

通过业务处理类日志可以确定业务没有返回ConsumeConcurrentlyStatus.RECONSUME_LATER的情况;

从代码可以看出,当出现2、3情况的时候,框架会将warn日志打印到ons.log中,通过过滤ons.log中“consumeMessage exception”和“consumeMessage return null”关键词,没有相应的日志记录,所以也不是这两种情况造成的。

备注:

当出现2、3情况的时候,ons.log日志中并没有打印出线程栈信息,如果想具体定位异常产生的位置,可以通过arthas stack命令进行分析。

arthas watch processConsumeResult

既然发送失败消息到Broker重试队列是在processConsumeResult方法调用的,那么我们可以分析下该方法的入参及返回值情况。

watch com.alibaba.rocketmq.client.impl.consumer.ConsumeMessageConcurrentlyService 
processConsumeResult "{params,returnObj}" "target.consumeGroup=='GID_CID_XXX'" -x 3 -n3

watch正常机器

watch异常机器

通过上面的watch,我们找到了问题最关键的地方,我们用下面的场景来分析下ackIndex不同值的影响。

场景一

  1. 业务处理类批量消费了【8】条数据,消费成功返回:CONSUME_SUCCESS
  2. ackIndex=Integer.MAX_VALUE
  3. RocketMQ框架分析消费成功了【8】条,失败【0】条
  4. 因为都消费成功了,不会将消息发送到Broker重试队列中

场景二

  1. 业务处理类批量消费了【8】条数据,消费成功返回:CONSUME_SUCCESS
  2. ackIndex=0
  3. RocketMQ框架分析消费成功了【1】条,失败【7】条
  4. 因为有【7】条消费失败,所以会将【7】条消费失败的消息发送到Broker重试队列中

arthas watch setAckIndex

既然有地方在修改ackIndex,先验证下我们的判断是否正确。

watch com.alibaba.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext setAckIndex "{params,returnObj}" "params[0]==0"

通过观察,确实有地方在不断将ackIndex的值修改为0。

arthas stack setAckIndex

我们继续定位是什么地方将ackIndex修改为0的。

stack com.alibaba.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext setAckIndex "params[0]==0" -n 2

通过线程栈可知BatchConsumerImpl类调用了ConsumeConcurrentlyContext.setAckIndex方法。

arthas jad BatchConsumerImpl

没有源码的情况下,我们可以使用arthas jad对类进行反编译。

jad com.aliyun.openservices.ons.api.impl.rocketmq.BatchConsumerImpl

ConsumeContext类实例字段acknowledgeIndex默认值是多少呢?如果是0,问题的原因就找到了。

athas jad ConsumeContext

没有源码的情况下,我们可以使用arthas jad对类进行反编译。

jad com.aliyun.openservices.ons.api.ConsumeContext

通过上面代码可以看出,ConsumeContext类实例字段acknowledgeIndex的默认值是0。

ProcessQueue

通过上面的分析,我们已经定位到了问题,ProcessQueue做下简单描述,不做具体分析了。

解决办法

由上面的分析,这个问题属于RocketMQ ons SDK的一个Bug,修复就交给相应的产研团队来fix吧。

经验总结

1-5-10,1分钟发现,5分钟定位,10分钟恢复。

当故障发生的时候,需要【1】最短时间内发现(监控报警是否做好),需要【10】最快的速度恢复(变更管理和预案是否做好),【5】似乎不是最主要的。

参考资料

EDAS基本概念

相关实践学习
消息队列RocketMQ版:基础消息收发功能体验
本实验场景介绍消息队列RocketMQ版的基础消息收发功能,涵盖实例创建、Topic、Group资源创建以及消息收发体验等基础功能模块。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
禹迹
+关注
目录
打赏
0
0
1
0
39
分享
相关文章
RocketMQ 流存储解析:面向流场景的关键特性与典型案例
RocketMQ 流存储解析:面向流场景的关键特性与典型案例
88746 28
MQTT常见问题之MQTT云端sdk消费者 出现重复消费如何解决
MQTT(Message Queuing Telemetry Transport)是一个轻量级的、基于发布/订阅模式的消息协议,广泛用于物联网(IoT)中设备间的通信。以下是MQTT使用过程中可能遇到的一些常见问题及其答案的汇总:
MQTT常见问题之调用.net sdk 报“不知道这样的主机”如何解决
MQTT(Message Queuing Telemetry Transport)是一个轻量级的、基于发布/订阅模式的消息协议,广泛用于物联网(IoT)中设备间的通信。以下是MQTT使用过程中可能遇到的一些常见问题及其答案的汇总:
DataWorks操作报错合集之在使用MaxCompute的Java SDK创建函数时,出现找不到文件资源的情况,是BUG吗
DataWorks是阿里云提供的一站式大数据开发与治理平台,支持数据集成、数据开发、数据服务、数据质量管理、数据安全管理等全流程数据处理。在使用DataWorks过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。
127 0
关于 Spring Integration 你知道多少,包含集成MQTT案例讲述及源码3
关于 Spring Integration 你知道多少,包含集成MQTT案例讲述及源码
1423 0
关于 Spring Integration 你知道多少,包含集成MQTT案例讲述及源码3
鸿蒙HarmonyOS埋点SDK,ClkLog适配鸿蒙埋点分析
ClkLog鸿蒙埋点SDK通过手动埋点的方式实现HarmonyOS 原生应用的前端数据采集。快速接入即可获取埋点数据,同时支持分析功能(基础统计分析、自定义分析、用户画像等)。
|
13天前
aliyun-upload-sdk访问空值Bug
这段内容描述了一个代码问题:方法调用时未正确处理参数。第一个图显示方法需要传入参数,但未进行空值判断;第二个图显示调用时未传参,导致报错并使上传失败。这是典型的参数处理不当引发的运行时错误,需完善判空逻辑和参数传递以解决问题。
aliyun-upload-sdk访问空值Bug
如何实现一个简单易用的 RocketMQ SDK
2018 年,做为架构负责人,接到一个架构需求:实现一个简单易用的 RocketMQ SDK 。 因为各个团队 RocketMQ 原生客户端配置起来千奇百怪,有的配置存在风险,各团队负责人都需要一个简洁易用的 RocketMQ SDK 。 我立马调研相关开源的方案,当时 RocketMQ-Spring 项目并没有开源,而阿里云的 ONS SDK 是开源的,我只能讲目标转向 阿里云 ONS 。 通过学习 ONS 的设计方式,我对于 RocketMQ 的客户端原理有了进一步了解,同时参考 ONS 的设计,也实现了公司内部使用的 RocketMQ SDK 。
如何实现一个简单易用的 RocketMQ SDK
Rabbitmq 搭建使用案例 [附源码]
Rabbitmq 搭建使用案例 [附源码]
77 0
RocketMQ—一次连接namesvr失败的案例分析
项目组在使用RocketMQ时遇到Consumer连接Name Server失败的问题,异常显示连接特定地址失败。通过Arthas工具逐步分析代码执行路径,定位到创建Channel返回空值导致异常。进一步跟踪发现,问题源于Netty组件在初始化`ByteBufAllocator`时出现错误。分析依赖后确认存在Netty版本冲突。解决方法为排除冲突的Netty包,仅保留兼容版本。
612 0
RocketMQ—一次连接namesvr失败的案例分析

云原生

+关注

相关产品

  • 云消息队列 MQ
  • AI助理

    你好,我是AI助理

    可以解答问题、推荐解决方案等