記一次內部測試平臺的性能故障分析排查過程--excel文件太大導致讀取文件失敗

第一次報障

1、執行用例總是提示獲取到dataprovider獲取到的Object[][]為空

Object[][]為空.png

2、在本地調試并不能每次復現,重啟一下服務器就能好。沒有明顯的報錯日志。

第一次問題定位

1、運行調試,發現總是讀取表格文件的這一步

讀取表格文件.png

發現這里的錯誤只捕獲了IOException。于是在拋出錯誤的時候,將IOException調整為Exception

修改為捕獲Exception.png

一開始捕捉到的是一個NullPointException

于是第一次的解決方案是:加多一個NullPointException。

加多一個NullPointException.png

由于本地運行正常,所以就上線了并告訴大家修復了問題。

第二次報障

上線后,測試同學小美仍然反饋出執行不了的問題,然后重啟之后,原本的文件就能夠正常執行了,但是再上傳一個新文件執行,同樣會報這個錯。

第二次問題定位

1、此時發現測試同學小美所使用的表格文件特別大。正常情況下只有200k左右的用例文件,執行有問題的文件居然高達4M。

文件很大.png

考慮可能是4M的文件解析帶來的問題,

于是在拋出錯誤的時候,再次調整為Exception

修改為捕獲Exception.png

【重現步驟】

1、先執行一次4M的文件的用例

2、執行完成后再上傳一個4M的用例(上傳用例過程在線上執行,直連線上的數據庫,然后復制一份用例文件到本地執行)

3、使用新的文件執行用例

此時明確發現,是由于OOM內存溢出導致的問題

OOM.png

于是打開jprofiler進行分析

1、第一次運行4m文件

第一次運行4M.png

再上傳一個新的4m文件,然后執行用例(上傳用例過程在線上執行,直連線上的數據庫,然后復制一份用例文件到本地執行)。

直接內存占用高達1.62G,GC100%運行,CPU未出現過載,所以性能瓶頸點主要在內存。判斷可能存在內存泄漏。

第二次運行4M.png
GC活動.png
cpu占用.png

另外還發現:超時還可能導致無法連接上zk,導致后續的測試無法繼續執行

[圖片上傳失敗...(image-5f70f9-1548245913902)]

但從上面只能定位到可能是內存泄漏,但未能定位到是什么原因導致,于是我們轉向Live Memory

打開jprofiler的Live Memory,執行如下步驟:

1、先執行兩個普通大小用例文件的用例,觀察內存中那類占用比較多--此時沒發現什么異常(所以沒有截圖)

2、再執行一個4M大小的用例。

發現下圖中AttrXobj、ElementXobj暴漲。幾乎占據了圖中80%以上的量。

(后續還執行了第二個4M文件,但由于直接跑崩了所以沒有收集到第二次4M文件執行的內存數據)

Live Memory.png

然后搜索AttrXobj(搜索AttrXobj),得知該類與XSSFWorkbook有關。

搜索結果.png

并且

1、從第一條搜索結果可知(SXSSFWorkbook & XSSFWorkbook 效率比拼)XSSFWorkbook本身在大數據量下存在性能問題。

image.png

2、在第四條記錄上看到了非常類似情況:記一次FullGC的排查

image.png

所以基本上定位到問題是處在我們ExcelUtils中用到的XSSFWorkbook對象身上。

最終解決思路

第一個:像記一次FullGC的排查這樣限制用戶上傳文件大小

思考:雖然該方法成本最低。但由于用例文件可能會存放多個sheet,即使單個sheet數據量不超過100k,整個excel文件的總size無法預估,且無法解決根本問題,所以先不采取這個方法。

第二個:檢查內存泄漏點,解決內存泄漏

從下圖中可發現,黃框部分是執行完GC之后的內存占用情況,隨著用例執行而增多。

內存占用趨勢.png
讀取表格方法的源碼.png

結合兩圖可以定位到是XSSFWorkbook的問題。那我們關注到調用這個類的方法上。

從下圖中可以看到,在方法中,有一個FileInputStream對象,但是讀取了之后沒有對應的關閉方法。

excelWBook、excelWsheet這兩個對象是一個靜態對象,在執行完操作之后,卻沒有進行對象的清空。可能會一直存活著。

從代碼里也可以看到一個點:excelWBook這個變量是一個靜態變量,第一次讀取4M文件后,未被釋放。然后到了第二次讀取的時候,

excelWBook = new XSSFWorkbook(ExcelFile)

先執行new XSSFWorkbook(ExcelFile) 再將new出來的對象賦值給excelWBook。所以 此時 excelWBook占用一波內存,new XSSFWorkbook(ExcelFile)又要占用一波內存,導致直接內存爆掉了。

修復前內存情況.png

【優化點】

1、將excelWSheet、excelWBook、cell從靜態變量改為實例變量,釋放內存。

2、加一個try-finally/try-with-resource關閉FileInputStream文件流。

3、使用SXSSFWorkbook替換XSSFWorkbook,降低內存開銷。

參考文檔

IntelliJ IDEA集成JProfiler,入門教程

SXSSFWorkbook & XSSFWorkbook 效率比拼

記一次FullGC的排查

修復結果

多次執行4M大小的文件響應正常且內存穩定。

修復后內存情況.png

修復后的關鍵源碼

public static Object[][] getTableArray(String FilePath, String SheetName, int totalCols, Boolean isWithTitleRow) throws IOException {
    String[][] tabArray = (String[][])null;
    FileInputStream excelFile = null;

    try {
        excelFile = new FileInputStream(FilePath);
        XSSFWorkbook excelWBook = new XSSFWorkbook(excelFile);
        XSSFSheet excelWSheet = excelWBook.getSheet(SheetName);
        int startRow = 0;
        int startCol = 0;
        int lastRowNum = excelWSheet.getLastRowNum();
        int lastCol = totalCols - 1;
        if (isWithTitleRow) {
            startRow = 1;
        }

        int totalRow = getRealRowNum(excelWSheet, startRow, lastRowNum, startCol, lastCol);
        if (isWithTitleRow) {
            tabArray = new String[totalRow][totalCols];
        } else {
            tabArray = new String[totalRow + 1][totalCols];
        }

        for(int currentRow = startRow; currentRow <= totalRow; ++currentRow) {
            for(int currentCol = startCol; currentCol <= lastCol; ++currentCol) {
                tabArray[currentRow - startRow][currentCol] = getCellData(excelWSheet, currentRow, currentCol);
            }
        }
    } finally {
        if (excelFile != null) {
            excelFile.close();
        }

    }

    return tabArray;
}
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容