您好,登錄后才能下訂單哦!
這周遇到了一個技術難題,我們系統中的導出文件功能雖然早已完成,但是當導出超過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分鐘,如果有一個進度條,用戶應該是很容易接受這樣的速度的,畢竟平時我們用其他系統導出文件也都需要時間,至此,這個問題算是告一段落了.
這次解決問題的過程,從找到一個方案,由于導出更大文件還是不行而否定,再到后來定位問題,定位問題后發現這個方案行不通,最后換方案,試驗成功,我發現自己還有許多時間可以優化,主要是要縮短試錯的成本,要在較短的時間內能夠發現一條路走不通,然后換一條路.路漫漫其修遠兮,吾將上下而求索.
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。