線上系統/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,確認是資源泄露的問題,修復代碼上線。

 
另外,第一次寫這類關於線上問題故障的文章,但願你們多多反饋。
相關文章
相關標籤/搜索