RocketMQ-ONS 内存占用过大问题处理
- 1、问题环境描述
- 2、问题现像描述
- 3、问题分析
- 3.1、问题定位阶段1(确认内存占用原因)
- 3.2、问题定位阶段2(缓存参数配置无效问题)
- 3.3、问题定位阶段2(分析占用原因)
- 4、解决方案
- 5、总结
1、问题环境描述
- 运行在聚石塔环境下(K8S)
- RocketMQ使用阿里云4.0系列版本的ONS
- 客户端版本号为
<dependency> <groupId>com.aliyun.openservices</groupId> <artifactId>ons-client</artifactId> <version>1.8.4.Final</version> </dependency>
- 需要订阅8个topic,其中4个普通主题,4个顺序主题
- 启动时8个主题剩余消费消息有50w+
2、问题现像描述
- 服务启动一段时间后接口请求超时
- 链路追踪服务报消息消费超时
3、问题分析
3.1、问题定位阶段1(确认内存占用原因)
- 查看服务监控发现内存占用过大(这个是服务升级后的,达到了7个G)
- 通过 arthas-boot 的 dashboard 命令看到内存集中在老年代无法回收
- 通过rocketmq控制台的仪表盘查看到处理中的消息达到了10w的数量
- 处理中的消息数量过大意味着消息已经被消费端接收但是还未处理,即未回执ACK消息,所以基本肯定是服务从RocketMQ拉取过多消息到本地引起的内存占用过大导致没有资源响应接口请求
3.2、问题定位阶段2(缓存参数配置无效问题)
- ONS有两个参数用于控制缓存数据
- maxCachedMessageAmount
- Consumer允许在客户端中缓存的最大消息数量,默认值为5000,设置过大可能会引起客户端OOM,取值范围为[100, 50000]
考虑到批量拉取,实际最大缓存量会少量超过限定值
该限制在客户端级别生效,限定额会平均分配到订阅的Topic上,比如限制为1000条,订阅2个Topic,每个Topic将限制缓存500条
- Consumer允许在客户端中缓存的最大消息数量,默认值为5000,设置过大可能会引起客户端OOM,取值范围为[100, 50000]
- maxCachedMessageSizeInMiB
- Consumer允许在客户端中缓存的最大消息容量,默认值为512 MiB,设置过大可能会引起客户端OOM,取值范围为[16, 2048]
考虑到批量拉取,实际最大缓存量会少量超过限定值
该限制在客户端级别生效,限定额会平均分配到订阅的Topic上,比如限制为1000MiB,订阅2个Topic,每个Topic将限制缓存500MiB
- Consumer允许在客户端中缓存的最大消息容量,默认值为512 MiB,设置过大可能会引起客户端OOM,取值范围为[16, 2048]
- maxCachedMessageAmount
- 但是配置之后并没有效果,查看源码发现参数值会写入到 DefaultMQPushConsumer 的 pullThresholdForTopic 和 pullThresholdSizeForTopic 属性上,同时这里也检查并限定了参数的最大值和最小值
- 而这两个参数会在 com.aliyun.openservices.shade.com.alibaba.rocketmq.client.impl.consumer.RebalancePushImpl 类的 messageQueueChanged 方法内触发写入到对应的 pullThresholdForQueue 和 pullThresholdSizeForQueue 属性上才能生效
- 但是实际上调用这个方法之前都会经过 updateProcessQueueTableInRebalance 方法判断才会调用,这在相对稳定的线上环境该方法会始终返回false,所以导致配置的参数没有生效
3.3、问题定位阶段2(分析占用原因)
-
按理来说就算参数未生效,但是默认值最大也才100Mb,为什么会膨胀到几个G呢
-
其实是参数配置的维度是整个消费端,而默认值是 topic 下的 queue 级别,如果 queue 数量很大,是不是有可能撑爆整个JVM
-
我们知道开源rocketmq 在创建 topic 时默认的 queue 是 16 个,但是ONS却是有几百个(下面截图是4个topic的queue数量),所以最差情况下需要占用几百个G
-
到这里也就能解释为什么内存会占用这么大的原因了
4、解决方案
- 这个问题在 1.8.8 版本中修复过了,所以我们直接升级客户端到最新版本就行了,点击查看ONS客户端具体版本变更
<dependency>
<groupId>com.aliyun.openservices</groupId>
<artifactId>ons-client</artifactId>
<!--2023-02-06 发布-->
<version>1.8.8.8.Final</version>
</dependency>
5、总结
- 已知ONS一个topic下可能会有300+的queue,订阅8个topic加上重试队列最终总的queue会达到3000+的一个数量,而 pullThresholdSizeForQueue 最小需要1Mb,所以最终还是需要3个G的内存,不过我们可以通过部署3台服务来分摊一下
- 其实如果业务数据量不是很多且不是很活跃的情况下,部分queue是会被回收的,所以还是需要结合实际情况配置 pullThresholdForQueue 和 pullThresholdSizeForQueue
- 还需要注意的是 ConsumeMessageBatchMaxSize 参数,批量拉取消息数量,即假设当前 pullThresholdForQueue 限制为10,而已经使用了 5,如果下一次批量拉去的数量大于5,那最终存储的数量就会突破10,内存也是