熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

Java在處理大數據的時候一些小技巧

2022-06-13   來源: Java核心技術 

  眾所周知java在處理數據量比較大的時候加載到內存必然會導致內存溢出而在一些數據處理中我們不得不去處理海量數據在做數據處理中我們常見的手段是分解壓縮並行臨時文件等方法

  例如我們要將數據庫(不論是什麼數據庫)的數據導出到一個文件一般是Excel或文本格式的CSV對於Excel來講對於POI和JXL的接口你很多時候沒有辦法去控制內存什麼時候向磁盤寫入很惡心而且這些API在內存構造的對象大小將比數據原有的大小要大很多倍數所以你不得不去拆分Excel還好POI開始意識到這個問題的版本後開始提供cache的行數提供了SXSSFWorkbook的接口可以設置在內存中的行數不過可惜的是他當你超過這個行數每添加一行它就將相對行數前面的一行寫入磁盤(如你設置行的話當你寫第行的時候他會將第一行寫入磁盤)其實這個時候他些的臨時文件以至於不消耗內存不過這樣你會發現刷磁盤的頻率會非常高我們的確不想這樣因為我們想讓他達到一個范圍一次性將數據刷如磁盤比如一次刷M之類的做法可惜現在還沒有這種API很痛苦我自己做過測試通過寫小的Excel比使用目前提供刷磁盤的API來寫大文件效率要高一些而且這樣如果訪問的人稍微多一些磁盤IO可能會扛不住因為IO資源是非常有限的所以還是拆文件才是上策而當我們寫CSV也就是文本類型的文件我們很多時候是可以自己控制的不過你不要用CSV自己提供的API也是不太可控的CSV本身就是文本文件你按照文本格式寫入即可被CSV識別出來如何寫入呢?下面來說說

  在處理數據層面如從數據庫中讀取數據生成本地文件寫代碼為了方便我們未必要M怎麼來處理這個交給底層的驅動程序去拆分對於我們的程序來講我們認為它是連續寫即可我們比如想將一個W數據的數據庫表導出到文件此時你要麼進行分頁oracle當然用三層包裝即可mysql用limit不過分頁每次都會新的查詢而且隨著翻頁會越來越慢其實我們想拿到一個句柄然後向下游動編譯一部分數據(如行)將寫文件一次(寫文件細節不多說了這個是最基本的)需要注意的時候每次buffer的數據在用outputstream寫入的時候最好flush一下將緩沖區清空下接下來執行一個沒有where條件的SQL會不會將內存撐爆?是的這個問題我們值得去思考下通過API發現可以對SQL進行一些操作例如通過PreparedStatement statement = connectionprepareStatement(sql)這是默認得到的預編譯還可以通過設置

  PreparedStatement statement = connectionprepareStatement(sqlResultSetTYPE_FORWARD_ONLYResultSetCONCUR_READ_ONLY)

  來設置游標的方式以至於游標不是將數據直接cache到本地內存然後通過設置statementsetFetchSize()設置游標每次遍歷的大小OK這個其實我用過oracle用了和沒用沒區別因為oracle的jdbc API默認就是不會將數據cache到java的內存中的而mysql裡頭設置根本無效我上面說了一堆廢話呵呵我只是想說java提供的標准API也未必有效很多時候要看廠商的實現機制還有這個設置是很多網上說有效的但是這純屬抄襲對於oracle上面說了不用關心他本身就不是cache到內存所以java內存不會導致什麼問題如果是mysql首先必須使用以上的版本然後在連接參數上加上useCursorFetch=true這個參數至於游標大小可以通過連接參數上加上defaultFetchSize=來設置例如

  jdbcmysql//xxxxxxxxxxxx/abc?zeroDateTimeconvertToNull&useCursorFetch=true&defaultFetchSize=< /span>

  上次被這個問題糾結了很久(mysql的數據老導致程序內存膨脹並行個直接系統就宕了)還去看了很多源碼才發現奇跡竟然在這裡最後經過mysql文檔的確認然後進行測試並行多個而且數據量都是W以上的都不會導致內存膨脹GC一切正常這個問題終於完結了

  我們再聊聊其他的數據拆分和合並當數據文件多的時候我們想合並當文件太大想要拆分合並和拆分的過程也會遇到類似的問題還好這個在我們可控制的范圍內如果文件中的數據最終是可以組織的那麼在拆分和合並的時候此時就不要按照數據邏輯行數來做了因為行數最終你需要解釋數據本身來判定但是只是做拆分是沒有必要的你需要的是做二進制處理在這個二進制處理過程你要注意了和平時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目前谷歌V虛擬機引擎所驅動的nodejs就是類似的模式有關這種技術不是本文的說明重點

  將上面兩者結合起來就是要解決大文件還要並行度最土的方法是將文件每次請求的大小降低到一定程度K(這個大小是經過測試後網絡傳輸較為適宜的大小本地讀取文件並不需要這麼小)如果再做深入一些可以做一定程度的cache將多個請求的一樣的文件cache在內存或分布式緩存中你不用將整個文件cache在內存中將近期使用的cache幾秒左右即可或你可以采用一些熱點的算法來配合類似迅雷下載的斷點傳送中(不過迅雷的網絡協議不太一樣)它在處理下載數據的時候未必是連續的只要最終能合並即可在服務器端可以反過來誰正好需要這塊的數據就給它就可以才用NIO後可以支持很大的連接和並發本地通過NIO做socket連接測試個終端同時請求一個線程的服務器正常的WEB應用是第一個文件沒有發送完成第二個請求要麼等待要麼超時要麼直接拒絕得不到連接改成NIO後此時個請求都能連接上服務器端服務端只需要個線程來處理數據就可以將很多數據傳遞給這些連接請求資源每次讀取一部分數據傳遞出去不過可以計算的是在總體長連接傳輸過程中總體效率並不會提升只是相對相應和所開銷的內存得到量化控制這就是技術的魅力也許不要太多的算法不過你得懂他

  類似的數據處理還有很多有些時候還會將就效率問題比如在HBase的文件拆分和合並過程中要不影響線上業務是比較難的事情很多問題值得我們去研究場景因為不同的場景有不同的方法去解決但是大同小異明白思想和方法明白內存和體系架構明白你所面臨的是沈陽的場景只是細節上改變可以帶來驚人的效果


From:http://tw.wingwit.com/Article/program/Java/hx/201311/26253.html
  • 上一篇文章:

  • 下一篇文章:
  • 推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.