线上服务OOMkill根因竟是PageCache的问题


问题背景&现象

ExitCode:137 OOMkill

问题现象137.png

线上环境的后台服务频繁重启,重启的原因日志是ExitCode:137 OOMkill 。OOMkill是服务器的一种保护行为,服务器会定时的扫描服务器中的各个进程,每个进程根据一种机制得到一个分数,如果这个分数最高则会被服务器主动kill掉。OOMkill的具体机制在此不做过多赘述。而进程得到高分的原因一般情况是内存资源占用过高导致的。被OOMkill后总是难于保留当时具体情况,每次都是服务被重启了才发现问题。还好运维平台可以支持OOMkill保护,发现资源超限情况不让直接OOMkill而是先保护现场并发出告警。

Memory hit original limit

Memory hit original limit告警.png

果然设置了保护之后,告警就接踵而至了。而且确实是内存超限导致的,如果没有保护又事一次烦人的OOMkill 。知道是内存的问题接下来问题定位的方向就更明了了。


问题定位&分析

先dump堆内存再说

第一个首先怀疑的是应用服务的堆内存。但是dump堆内存下来分析后,发现占用的并不高。总内存只有200M左右。


3.堆内存分析.png

看来堆内存并不是罪魁祸首。
总结:dump命令,jmap -dump:format=b,file= /文件路径/dump文件名称.hprof 进程ID ,如:jmap -dump:format=b,file=/home/heap.hprof 1

是堆外内存的原因吗?

不是堆内存,那应该就是堆外内存的原因了。项目中有些开源框架使用到Netty,Netty是有使用堆外内存的。而且JDK8的元空间也是属于堆外。

JDK8元空间观测

通过查看JVM的配置,发现元空间的大小并没有限制。


4元空间大小没做限制.png

原因是项目以前使用的是JDK8以前的版本,升级到了JDK8后对应的JVM配置没有对应更新。运维平台观测到的非堆内存占用确实也是不小。


image.png

给JVM参数加元空间的限制,-XX:MetaspaceSize=xxxm -XX:MaxMetaspaceSize=xxxm 。
本以为万事大吉,但过了几天烦人的内存超限告警还是来了。

netty堆外内存占用观测

不是元空间占用的大量内存,那会不会是项目其他地方使用到了堆内存呢? 查看了整个项目的业务代码和相关的依赖,只有Netty使用到堆外内存。那么就想办法观测一下Netty使用堆外内存的情况。关于Netty的堆外内存如何使用,因篇幅有限此处不做扩展;只是通过以下工具可以监控到堆外内存的大小:

public static List startReport() {
        Field field = ReflectionUtils.findField(PlatformDependent.class, "DIRECT_MEMORY_COUNTER");
        field.setAccessible(true);
        try {
            AtomicLong directMemory = (AtomicLong) field.get(PlatformDependent.class);
            int memoryInKb = (int) (directMemory.get() / 1024);
            log.info("堆外内存大小:{}",memoryInKb);
            if (list.size() <=100) {
                list.add("时间:" + LocalDateTime.now()
                                              .toString() + "  堆外内存:" + memoryInKb + "k ");
            }else {
                list.clear();
            }
            return list;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return list;
    }

并通过一个上帝接口持续观测:


image.png

但是经过一段时间的观测,占用的内存并没有异常增长。

page cache占用总内存情况

无奈之下,只能进一步的通过运维监控平台继续找一些观测数据。经过长时间的观测对比。发现page cache的变化对总内存变化影响很明显,而且发出内存超限提醒的服务节点page cache都没法正常回收。

5pc对总内存的影响很明显.png

page cache是linux内核为了提高磁盘文件操作而设计的一种机制。它会将一些磁盘的文件数据写入到内存中,并设计一套淘汰机制回收内存。根据上图观测到的情况,page cache占用的内存并没法正常回收。

vmtouch观测文件缓存情况

梳理了整个业务项目的代码,并没有发现有大量读写文件的地方。怀疑是日志文件的读写,导致一直在产生page cache。为了验证个问题需要使用到vmtouch命令,关于此命令的介绍可以参见官方文档:https://hoytech.com/vmtouch
此命令可以查看磁盘文件被加载到内存中的情况。
查看日志文件被加载到内存的情况:

image.png

整个日志文件900M左右,全部被加载到内存中。
将被加载到内存中的缓冲驱逐掉:
image.png

观测到的page cache和总内存对应马上降低下来:
image.png

终于问题原因变得明朗了,接下来就要看看page cache为什么不能正常回收了。

page cache数据观测

为什么page cache这么多?

page cache为什么这么多,是因为日志文件太大了吗?为了验证这个问题,通过修改log4j的配置,将每个日志文件分割成500M以内,但是问题依旧。而且日志文件被切割后,不再写入的日志文件在依然被加载到内存中,说明是读日志文件的原因。


企业微信截图_16235175941830.png
企业微信截图_16235176181799.png

日志文件的读场景,是运维平台做日志采集的情况才会触发的。于是找了运维平台的同事反馈。

为什么page cache没正常回收?

反馈问题后自己也持续观察,发现有问题的节点。发现page cache的扫描速度很慢,而且脏页阈值配置的也不一样。和运维同事沟通后,得知服务器节点是被随机分配的,可能节点属于不同的物理机,而脏页阈值的配置使用的应该是物理机的配置。


![企业微信截图_16235191045039.png](https://upload-images.jianshu.io/upload_images/3424555-9f22f5cf599b0d7e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

问题解决&防范

曲线救国

既然问题原因是读日志文件导致的,那就不让监控程序主动来读取日志,而是主动将数据通过消息队列推送给监控系统。公司基于log4j自研封装了日志工具,可以根据配置将日志直接通过kafka推送给监控系统,这样就能避免读日志文件,page cache的问题也可以避免了。

问题根治

主动推送日志的方法只能是避免问题的发生,要根治问题还是得从page cache的相关配置入手。page cache难回收的原因一般有三个(具体可以参见:Page Cache难以回收问题):
1.直接内存回收导致
2.系统脏页内存积压过多导致
3.系统NUMA策略配置不当导致
其中情况1 通过sar -B命令观测可以排插,因为pgscand并没有发生

企业微信截图_16235198212630.png

情况3通过命令numactl --hardware确认也可以排除了。
情况2需要不断配置系统参数并调试,因是线上环境,而且初始环境没法模拟验证,只能反馈给运维团队,让他们持续优化。

你可能感兴趣的:(线上服务OOMkill根因竟是PageCache的问题)