標籤:jdb 分頁 對象 datetime 視頻下載 HERE stream nec amp
眾所周知, Java 在處理資料量比較大的時候,載入到記憶體必然會導致記憶體溢出,而在一些資料處理中我們不得不去處理海量資料,在做資料處理中,我們常見的手段是分解,壓縮,並行,臨時檔案等方法;
例如,我們要將 資料庫 (不論是什麼資料庫)的資料匯出到一個檔案,一般是Excel或文字格式設定的CSV;對於Excel來講,對於POI和JXL的介面,你很多時候沒有辦法去控制記憶體什麼時候向磁碟寫入,很噁心,而且這些API在記憶體構造的對象大小將比資料原有的大小要大很多倍數,所以你不得不去拆分Excel,還好,POI開始意識到這個問題,在3.8.4的版本後,開始提供cache的行數,提供了SXSSFWorkbook的介面,可以設定在記憶體中的行數,不過可惜的是,他當你超過這個行數,每添加一行,它就將相對行數前面的一行寫入磁碟(如你設定2000行的話,當你寫第20001行的時候,他會將第一行寫入磁碟),其實這個時候他些的臨時檔案,以至於不消耗記憶體,不過這樣你會發現,刷磁碟的頻率會非常高,我們的確不想這樣,因為我們想讓他達到一個範圍一次性將資料刷如磁碟,比如一次刷1M之類的做法,可惜現在還沒有這種API,很痛苦,我自己做過測試,通過寫小的Excel比使用目前提供刷磁碟的API來寫大檔案,效率要高一些,而且這樣如果訪問的人稍微多一些磁碟IO可能會扛不住,因為IO資源是非常有限的,所以還是拆檔案才是上策;而當我們寫CSV,也就是文本類型的檔案,我們很多時候是可以自己控制的,不過你不要用CSV自己提供的API,也是不太可控的,CSV本身就是文字檔,你按照文字格式設定寫入即可被CSV識別出來;如何寫入呢?下面來說說。。。
在處理資料層面,如從資料庫中讀取資料,產生本地檔案,寫代碼為了方便,我們未必要1M怎麼來處理,這個交給底層的驅動程式去拆分,對於我們的程式來講我們認為它是連續寫即可;我們比如想將一個1000W資料的資料庫表,匯出到檔案;此時,你要麼進行分頁,oracle當然用三層封裝即可, MySQL 用limit,不過分頁每次都會新的查詢,而且隨著翻頁,會越來越慢,其實我們想拿到一個控制代碼,然後向下遊動,編譯一部分資料(如10000行)將寫檔案一次(寫檔案細節不多說了,這個是最基本的),需要注意的時候每次buffer的資料,在用outputstream寫入的時候,最好flush一下,將緩衝區清空下;接下來, 執行一個沒有where條件的SQL,會不會將記憶體撐爆 ?是的,這個問題我們值得去思考下,通過API發現可以對SQL進行一些操作,例如,通過:PreparedStatement statement = connection.prepareStatement(sql),這是預設得到的先行編譯,還可以通過設定:PreparedStatement statement = connection.prepareStatement(sql , ResultSet.TYPE_FORWARD_ONLY , ResultSet.CONCUR_READ_ONLY);
來設定遊標的方式,以至於遊標不是將資料直接cache到本地記憶體,然後通過設定statement.setFetchSize(200);設定遊標每次遍曆的大小;OK,這個其實我用過,oracle用了和沒用沒區別,因為oracle的jdbc API預設就是不會將資料cache到java的記憶體中的,而mysql裡頭設定根本無效, 我上面說了一堆廢話,呵呵 ,我只是想說,java提供的標準API也未必有效,很多時候要看廠商的實現機制,還有這個設定是很多網上說有效,但是這純屬抄襲;對於oracle上面說了不用關心,他本身就不是cache到記憶體,所以java記憶體不會導致什麼問題,如果是mysql,首先必須使用5以上的版本,然後在串連參數上加上useCursorFetch=true這個參數,至於遊標大小可以通過串連參數上加上:defaultFetchSize=1000來設定,例如:
jdbc:mysql://xxx.xxx.xxx.xxx:3306/abc?zeroDateTimeBehavior=convertToNull&useCursorFetch=true&defaultFetchSize=1000
上次被這個問題糾結了很久(mysql的資料老導致程式記憶體膨脹,並行2個直接系統就宕了),還去看了很多源碼才發現奇蹟竟然在這裡,最後經過mysql文檔的確認,然後進行測試,並行多個,而且資料量都是500W以上的,都不會導致記憶體膨脹,GC一切正常,這個問題終於完結了。
看完以上內容大家應該累了吧,這裡小編把自己的大資料學習qun531629188推薦給大家無論是大牛還是想轉行想學習的大學生
小編我都挺歡迎,晚上20:10都有一節【免費的】大資料直播課程,專註大資料分析方法,大資料編程,大資料倉儲,大資料案例,人工智慧,資料採礦都是純乾貨分享,
我們再聊聊其他的,資料拆分和合并,當資料檔案多的時候我們想合并,當檔案太大想要拆分,合并和拆分的過程也會遇到類似的問題,還好,這個在我們可控制的範圍內,如果檔案中的資料最終是可以組織的,那麼在拆分和合并的時候,此時就不要按照資料邏輯行數來做了,因為行數最終你需要解釋資料本身來判定,但是只是做拆分是沒有必要的,你需要的是做二進位處理,在這個二進位處理過程,你要注意了,和平時read檔案不要使用一樣的方式,平時大多對一個檔案讀取只是用一次read操作,如果對於大檔案記憶體肯定直接掛掉了,不用多說,你此時因該每次讀取一個可控範圍的資料,read方法提供了重載的offset和length的範圍,這個在迴圈過程中自己可以計算出來,寫入大檔案和上面一樣,不要讀取到一定程式就要通過寫入流flush到磁碟;其實對於小資料量的處理在現代的NIO技術的中也有用到,例如多個終端同時請求一個大檔案下載,例如視頻下載吧,在常規的情況下,如果用java的容器來處理,一般會發生兩種情況:
其一為記憶體溢出,因為每個請求都要載入一個檔案大小的記憶體甚至於更多,因為java封裝的時候會產生很多其他的記憶體開銷,如果使用二進位會產生得少一些,而且在經過輸入輸出資料流的過程中還會經曆幾次記憶體拷貝,當然如果有你類似nginx之類的中介軟體,那麼你可以通過send_file模式發送出去,但是如果你要用程式來處理的時候,記憶體除非你足夠大,但是java記憶體再大也會有GC的時候,如果你記憶體真的很大,GC的時候死定了,當然這個地方也可以考慮自己通過直接記憶體的調用和釋放來實現,不過要求剩餘的實體記憶體也足夠大才行,那麼足夠大是多大呢?這個不好說,要看檔案本身的大小和訪問的頻率;
其二為假如記憶體足夠大,無限制大,那麼此時的限制就是線程,傳統的IO模型是線程是一個請求一個線程,這個線程從主線程從線程池中分配後,就開始工作,經過你的Context封裝、Filter、攔截器、業務代碼各個層次和商務邏輯、訪問資料庫、訪問檔案、渲染結果等等,其實整個過程線程都是被掛住的,所以這部分資源非常有限,而且如果是大檔案操作是屬於IO密集型的操作,大量的CPU時間是空餘的,方法最直接當然是增加線程數來控制,當然記憶體足夠大也有足夠的空間來申請線程池,不過一般來講一個進程的線程池一般會受到限制也不建議太多的,而在有限的系統資源下,要提高效能,我們開始有了new IO技術,也就是NIO技術,新版的裡面又有了AIO技術,NIO只能算是非同步IO,但是在中間讀寫過程仍然是阻塞的(也就是在真正的讀寫過程,但是不會去關心中途的響應),還未做到真正的非同步IO,在監聽connect的時候他是不需要很多線程參與的,有單獨的線程去處理,串連也又傳統的socket變成了selector,對於不需要進行資料處理的是無需分配線程處理的;而AIO通過了一種所謂的回調註冊來完成,當然還需要OS的支援,當會掉的時候會去分配線程,目前還不是很成熟,效能最多和NIO吃平,不過隨著技術發展,AIO必然會超越NIO,目前GoogleV8虛擬機器引擎所驅動的node.js就是類似的模式,有關這種技術不是本文的說明重點;
將上面兩者結合起來就是要解決大檔案,還要並行度,最土的方法是將檔案每次請求的大小降低到一定程度,如8K(這個大小是經過測試後網路傳輸較為適宜的大小,本地讀取檔案並不需要這麼小),如果再做深入一些,可以做一定程度的cache,將多個請求的一樣的檔案,cache在記憶體或分布式緩衝中,你不用將整個檔案cache在記憶體中,將近期使用的cache幾秒左右即可,或你可以採用一些熱點的演算法來配合;類似迅雷下載的斷點傳送中(不過迅雷的網路通訊協定不太一樣),它在處理下載資料的時候未必是連續的,只要最終能合并即可,在伺服器端可以反過來,誰正好需要這塊的資料,就給它就可以;才用NIO後,可以支援很大的串連和並發,本地通過NIO做socket串連測試,100個終端同時請求一個線程的伺服器,正常的WEB應用是第一個檔案沒有發送完成,第二個請求要麼等待,要麼逾時,要麼直接拒絕得不到串連,改成NIO後此時100個請求都能串連上伺服器端,服務端只需要1個線程來處理資料就可以,將很多資料傳遞給這些串連請求資源,每次讀取一部分資料傳遞出去,不過可以計算的是,在總體長串連傳輸過程中總體效率並不會提升,只是相對相應和所開銷的記憶體得到量化控制,這就是技術的魅力,也許不要太多的演算法,不過你得懂他。
類似的資料處理還有很多,有些時候還會將就效率問題,比如在 HBase 的檔案拆分和合并過程中,要不影響線上業務是比較難的事情,很多問題值得我們去研究情境,因為不同的情境有不同的方法去解決,但是大同小異,明白思想和方法,明白記憶體和體系 架構 ,明白你所面臨的是瀋陽的情境,只是細節上改變可以帶來驚人的效果。
風火資料
連結:https://juejin.im/post/5b556c846fb9a04f9963a8b5
來源:掘金
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。
進階Java研發人員在解決大資料問題上的技巧