1、背景介绍
这是一个临时文件的清洗服务,主要使用消费者生产者模型。
清洗方式为分段清洗,因为清洗数据量过大,所以需要对数据进行分段以方便容错处理。
清洗服务模型如图:
每个消费者实例中一个消费者进程执行一段的清洗任务需要的时间大约为15分钟。
生产者生产消息的速度远大于消费速度。
2、问题出现
清洗程序在凌晨启动,但是启动没几分钟后便收到疯狂告警:
看日志信息来说,就是疯狂GC但是回收不到垃圾,堆内存不够了。
那接下来就看看JVM的监控情况:
确实堆内存不够用了,那为什么不够用呢?
3、问题定位与解决
1)堆内存dump
在设置了 -XX:HeapDumpPath=/app/logs -XX:+HeapDumpOnOutOfMemoryError 后,JVM会在发生OOM的时候将当时的内存状况dump下来。
那接下来我们就分析一波内存情况
使用工具:jvisualvm
2)出现问题的线程
看样子就是在拷贝byte数组时候出现了内存不够用。
那接下来看看堆中是哪些实例占用了过多的内存
3)堆分析
先按照大小排序:
这个byte数组大概占用了93%的内存,那问题差不多就出在这儿了。
点进去看看这个实例的引用是什么:
按照从大到小排序,看到这个
MessageClientExt
就基本上可以猜到,是不是消息体过多导致的?
下面我们去源码分析一波,这个消息体到底是如何存在与消失的。
4)RocketMQ消费者源码分析(rocketmq-client:4.3.2)
在这之前,我们先看看RocketMQ的一些关键模型图:
Topic与Queue的关系:
每个Topic在不同的Broker内可以有不同数量的Queue,这全部的Queue都需要被消费者连接。
生产者投递消息会根据策略选择Queue进行投递,具体什么策略大家可以自行Google一下,这里不过多介绍。
目前我们线上设置的是10个broker,每个broker有16个queue。
Queue与消费者实例的关系:
这里为了简单起见,将所有的Broker合并为一个。
根据RebalanceService
的策略,会把Broker的Queue与消费者实例中本地的ProcessQueue一一对应。
消费逻辑
这里重点关注消息何时被拉到本地,忽略offset怎么更新、顺序消费、消费失败等情况。
消息体流转源码分析
1、消息拉取到后存放在哪儿?
org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl#pullMessage
// 首先进行一些限制性的校验,重点关注defaultMQPushConsumer.