线上系统/tmp 目录不断增加分析与总结

 

一、问题描述

系统配置为单核4G, web 工程配置堆2G,  /tmp目录 二进制文件不断增长,平均一天增长20G, 手动清理/tmp目录,重启系统,问题依旧。java

二、分析

/tmp 目录存放系统运行时产生的临时文件。在Redhat-like系统上,会按期清理/tmp目录下10天未访问的文件。这个机制保证了,linux不会像windows那样在较长时间运行后变得臃肿不堪。
 
清理脚本位于/etc/cron.daily/tmpwatch,内容以下,
 
#! /bin/sh
flags=-umc
/usr/sbin/tmpwatch "$flags" -x /tmp/.X11-unix -x /tmp/.XIM-unix \
     -x /tmp/.font-unix -x /tmp/.ICE-unix -x /tmp/.Test-unix \
     -X '/tmp/hsperfdata_*' 10d /tmp
/usr/sbin/tmpwatch "$flags" 30d /var/tmp
for d in /var/{cache/man,catman}/{cat?,X11R6/cat?,local/cat?}; do
    if [ -d "$d" ]; then
     /usr/sbin/tmpwatch "$flags" -f 30d "$d"
    fi
done
本质是调用了系统命令/usr/sbin/tmpwatch 来执行对/tmp和/var/tmp目录的清理。tmpwatch 通常被用来清扫那些用来临时驻留文件的目录。
 
注意到,tmpwatch删除时有个排除项,/tmp/hsperfdata_*,java程序在启动时,默认会生成/tmp/hsperfdata_{USERNAME}/{PID}的文件,
这个文件会存储jvm运行的相关信息。jps\jconsole等工具的数据源就是来自于这个文件。若这个文件不存在,jps命令执行时找不到这个进程。关于这个问题,曾经有个bug相关( https://bugzilla.redhat.com/show_bug.cgi?id=527425),这个bug就是因为tmpwatch 没有排除/tmp/hsperfdata_*这个目录,致使该目录被删除,jps没法找到对应的进程。
 
那么/tmp一般会有哪些临时数据在这里呢?
例如,jvm启动数据、mysql的sock文件、apache默认的上传文件目录、nginx的缓存文件以及一些其余进程的临时文件。
 
1.查看/tmp, 经过 ls /tmp | wc -l观察, /tmp文件大约以4个/s 的速度增长,并且都是二进制文件。

2.lsof -p pid 肯定tmp文件都被进程id为10791的同一个Java进程打开。mysql


根据上面的分析,这些文件应该是该进程的临时文件,并且不断在增长,有多是文件句柄泄露。linux

查看该进程的句柄图 nginx


12.15号,系统打开的句柄数量在逐步的增长,并且没有出现相对平稳的迹象,肯定是句柄泄露了,这印证了咱们的猜测。web

下来须要进一步分析到底是什么缘由形成的句柄泄露。sql

查看文件内容,vi -b /tmp/filename, 包含 <</Length 2541/Filter/FlateDecode>>stream 之类的内容。猜想应该是和解码有关。

 谷歌搜索关键字,肯定FlateDecode是解码 PDF stream 的一个工具。查看程序中引用相关pdf的代码,以下图所示:apache

public  static  byte [] transfer( byte [] bytes,  int  pageNum)  throws  IOException {
     LOG.info( "PDF合同转IMAGE开始...pageNum={}" , pageNum);
     PdfDecoder decode_pdf =  new  PdfDecoder( true );
     decode_pdf.scaling =  1 .5F;
     FontMappings.setFontReplacements();
     byte [] outbytes =  new  byte [ 0 ];
     ByteArrayOutputStream out =  new  ByteArrayOutputStream();
     try  {
         decode_pdf.openPdfArray(bytes);  //bytes is byte[] array with PDF
         BufferedImage img = decode_pdf.getPageAsImage(pageNum);
         ImageIO.write(img,  "jpg" , out);
         outbytes = out.toByteArray();
         LOG.info( "PDF合同转IMAGE成功...pageNum={}" , pageNum);
     catch  (Exception e) {
         LOG.error( "PDF合同转IMAGE异常...pageNum={},e={}" , pageNum, e);
     finally  {
         out.close();
     }
     return  outbytes;
}

这段代码用来将pdf转化成一个jpg的图片,使用了jpedal第三方库。 windows

jpedal是一个开源的纯Java的PDF文档解析库,能够用来方便的查看和编辑文字和图片。 api

回到代码, 按照以往编码的经验,有多是PdfDecoder没有释放资源,致使生成的临时文件一直没有释放掉。查看jpedal文档,发现的确提供了closePdfFile 关闭pdf文件的方法。 缓存

finally 块里添加 

decode_pdf.flushObjectValues(true);
decode_pdf.closePdfFile();

从新发版,发现以后句柄图达到了相对平稳的状态,tmp目录也再也不继续增长临时文件。

 

虽然问题解决了,可是还有一些困惑。临时文件怎么生成的?page fault为啥这么多?

一、临时文件究竟是怎么生成的?

decode_pdf.openPdfArray(bytes)  根据传进来的字节流 打开pdf文件,

 

 

jpedal在这里作了一个优化,当pdf文件小于16k时或者alwaysCacheInMemory = -1时,直接内存缓存该pdf。

当pdf文件的大小大于16k时,会在临时目录下生成一个前缀为page,后缀为bin的二进制文件,该临时目录由系统参数 java.io.tmpdir 指定,默认在/tmp目录下。

这样,因为线上环境的pdf基本都大于16k,因此/tmp目录下就会看到不断的临时文件生成。这个临时文件命名规则为page***.bin。

 

二、添加closePdf文件以后,为啥问题就解决了呢?

closePdf会调用PdfReader的closePdfFile()方法,该方法根据缓存的临时文件名称删除该临时文件。

 

 

三、未关闭pdf文件,为啥会引发较多的page fault呢?

page fault 分为 minor page fault 和major page fault。

major page fault也称为hard page fault, 指须要访问的内存不在虚拟地址空间,也不在物理内存中,须要从慢速设备载入。从swap回到物理内存也是hard page fault。

minor page fault也称为soft page fault, 指须要访问的内存不在虚拟地址空间,可是在物理内存中,只须要MMU创建物理内存和虚拟地址空间的映射关系便可。 
(一般是多个进程访问同一个共享内存中的数据,可能某些进程尚未创建起映射关系,因此访问时会出现soft page fault)

正常状况下,系统也会有一些pagefault,以下图所示:

,因此pagefault和该问题没有直接关系。minflt表示从内存加载数据时每秒出现的小的错误数目,能够忽略。若是majflt较大,表示从磁盘载入内存页面,发生了swap,此时须要关注。

 

三、总结

咱们详细的回顾了这次线上发生的问题,以及如何去定位,而后去解决问题的整个过程。

(1)问题发现,收到系统磁盘空间不足的报警。

(2)问题定位,先根据du确认是tmp目录增加过快的问题,而后根据lsof和进程句柄图肯定是文件句柄泄露,再根据临时文件的文件内容,定位相关的源代码,查看源代码,确认是文件句柄资源没有正确释放。

(3)解决问题,查看api,确认是资源泄露的问题,修复代码上线。

 
另外,第一次写这类关于线上问题故障的文章,但愿你们多多反馈。
相关文章
相关标签/搜索