scala系統導出大文件問題解決方案

     這周遇到了一個技術難題,我們系統中的導出文件功能雖然早已完成,但是當導出超過8MB的文件時就會提示GC overhead,一開始只把問題定位到緩衝區不夠,所以試圖找到一條分批查詢的方案,經過一定時間的調研就找到了一個看似挺好的方案:

protected void fillHeader(String[] columns, int contentLen, int headNums) {

    tmpwb = new XSSFWorkbook();

    tmpwb = updateDimensionRef(tmpwb, columns.length - 1, contentLen + headNums);

    wb = new SXSSFWorkbook(tmpwb, DEFAULT_ROW_ACCESS_WINDOW_SIZE);

    sh = wb.getSheet(DEFAULT_SHEET_NAME);

    sh.setRandomAccessWindowSize(DEFAULT_ROW_ACCESS_WINDOW_SIZE);

}

    這一方案中,EFAULT_ROW_ACCESS_WINDOW_SIZE指的是緩衝區中存放的行數,超過這個行數就會把文件溢寫到磁盤裏,這樣就起到了動態釋放緩衝區的作用,由於原先用的是

XSSFWorkbook,而這一方案需要使用SXSSFWorkbook,而SXSSFWorkbook中沒有更新dimension的方法,所以先在XSSFWorkbook中定義好dimension,然後再轉換成SXSSFWorkbook.經過這次改動後試驗發現,性能確實有所提升,導出15MB左右不成問題.

    本來以爲大功告成,可是第二天測試MM再次給我報bug,說導出70MB的大文件仍然報錯,而客戶需求的最大文件是80MB,我突然意識到問題沒有那麼簡單,革命尚未成功,同志仍需努力.

    我開始研究報的錯,Error java.lang.OutOfMemoryError: GC overhead limit exceeded,在StackOverflow上搜了一些帖子,建議調整jvm參數,於是我把-Xmx和-Xms都調成了4g,結果發現這個錯誤消除了,但又開始報新的錯誤,504 gateway-timeout,根據上面的提示,結合Google的帖子,我認爲問題可能是出在導出數據量太大,導致需要時間長,而我們的超時時間可能設置得太短,覺得有必要調整一下nginx的參數,於是修改了一下nginx.conf中的配置,把proxy_send_timeout和proxy_read_timeout的值都調成了600,延長了nginx後端服務器數據回傳時間和等候後端服務器響應時間,由於和系統連接時間無關,因此proxy_connect_timeout 這個參數就不更改了.經過這次更改之後,發現依然會報504 gateway-timeout,這時候意識到可能問題並不是單方面的,開始思考解決方案.

    現在的重點就是要找出導致導出失敗的根源,於是在調用這個接口的一系列相關代碼,從routes開始,幾個關鍵點都打上log,最後發現在在一個Handler的最後一步還能打印出日誌,說明那個Handler已經執行完畢,而從Handler發送消息的那個接收方Actor收到結果後做的第一件事情就是打印一條日誌,但是那一條一直沒有打印出來,那麼問題就定位到了Actor傳送消息的過程,一開始只想到可能是Akka消息傳遞過程的超時,於是延長了系統中每一個接口的超時時間,之後沒有再報接口的Ask TimeOut,但是仍然有504 gateway-timeout,由於涉及到akka通信機制,所以我很自然地聯想到akka的參數是否可以設置一下,由於我們傳輸的是全表查詢結果,量非常大,因此想到是否把容量類參數的值設置得大一點,maximum-frame-size這個參數表示最大允許傳輸的數據量,另外send-buffer-size和receive-buffer-size這兩個參數也是限制消息傳遞大小的,於是把這三個參數都調大,基本上問題定位到了消息傳遞的大小限制,因爲我認爲超時時間已經設置得很長了,按照傳遞消息的速度來看,不至於這麼久還沒傳完,因此唯一的可能性是本身就不允許傳遞大數據量,而這些一般都可以在參數中設置,這麼一想,我滿以爲問題終於快得到解決了.

    令人遺憾的是,問題並沒有因爲調參而解決了,後來我一度還嘗試了把maximum-payload-bytes這個參數的值也調大,發現依然無濟於事,似乎又陷入了迷茫當中.但是我依然沒有放棄尋找答案,這個時候發現一篇帖子:http://stackoverflow.com/questions/31038115/akka-net-sending-huge-messages-maximum-frame-size ,上面那個程序員也遇到了類似的問題,根據他的描述,我推斷出可能akka消息傳輸是存在一個上限的,大於這個上限的話設置的參數也會是無效的,但是這一點我還沒有去證明,如果要證明這些,需要研究一下akka的源碼,願各位大神們有空做個分享,好,就是你啦~~.回到那個問題,那個帖子的樓主這麼描述:Also, if the message is being dropped, is there any way to get notified about it? Sometimes it sends a Dissassociated error and sometimes it just sits doing nothing. log-frame-size-exceeding = on and log-buffer-size-exceeding = 50000 settings do not seem to have an effect.

    這個時候,下面有一個無名神僧道出了天機,讓我恍然大悟,他說:In general it's a bad idea to push big portions of data over the wire at once. It's a lot better to split them into smaller parts and send one by one (this also makes a retry policy less expensive, if it's necessary). If you want to keep your actor logic unaware of transport details, you may abstract it by defining a specialized pair of actors, whose only job will be to split/join big messages.

    我突然意識到,akka設計者的初衷可能只是想通過actor來傳送消息,並沒有讓你們通過這種傳送消息的機制直接傳送大批量的數據,這種全表查詢結果寫入到大文件並且傳送的問題還是更適合使用scp來傳送,或者用rz,sz也可以實現文件的收發,或者用一個python的插件也可以實現在網頁上的秒傳,其中scp和那個我記不清名字的python插件(工作文檔有記錄)是我用過的體驗比較好的兩個文件傳輸工具,可以輕鬆傳輸hive和HBase等導出的大批量數據,而Akka或許目前的設計只是用來傳遞短信息而已,就像一條微博只能輸入140個字,你要表達一大段內容,只能使用博客和日誌等功能,本身面向的用途和使用場景就是不一樣的.

    數據切分,用多線程來解決問題,也是一個思路,同事建議我這麼做,但我發現多線程存在同步的問題,讀取的順序必須完全保持一致,我們的產品下週就要交付了,同時測試MM如果要再抽出額外時間測大量多線程問題,大家壓力都會比較大,而且留給我們的時間也不多了.當然最主要的還是我考慮到目前文件也不算特別大,能傳輸100MB已經可以滿足客戶需求,用多線程會消耗更多系統字段,而且創建每個線程,以及對線程的管理也消耗時間,綜合考慮,覺得暫時沒有必要使用多線程.既然問題卡在Actor傳送消息這塊,那麼我是否可以把處理邏輯都放在一個Handler上,包括全表查詢,查詢結果寫入Excel以及Excel的導出,這一系列的流程都在一個Handler裏搞定,最後只給之前的那個Actor發送是否導出成功的消息,同時回傳下載的路徑,在那個Actor裏,只需要做下載這一步就可以了.說幹就幹,昨天下午把這塊代碼改成了我想要的樣子,改完之後在本地做了測試,目前導出100MB的文件已經不成問題,時間也就3分鐘,最多4分鐘,如果有一個進度條,用戶應該是很容易接受這樣的速度的,畢竟平時我們用其他系統導出文件也都需要時間,至此,這個問題算是告一段落了.

    這次解決問題的過程,從找到一個方案,由於導出更大文件還是不行而否定,再到後來定位問題,定位問題後發現這個方案行不通,最後換方案,試驗成功,我發現自己還有許多時間可以優化,主要是要縮短試錯的成本,要在較短的時間內能夠發現一條路走不通,然後換一條路.路漫漫其修遠兮,吾將上下而求索.


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章