眾所周知, Java 在處理數(shù)據(jù)量比較大的時(shí)候,加載到內(nèi)存必然會導(dǎo)致內(nèi)存溢出,而在一些數(shù)據(jù)處理中我們不得不去處理海量數(shù)據(jù),在做數(shù)據(jù)處理中,我們常見的手段是分解,壓縮,并行,臨時(shí)文件等方法;
成都創(chuàng)新互聯(lián)公司主要從事網(wǎng)站設(shè)計(jì)、成都網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)賀蘭,10年網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):18980820575例如,我們要將 數(shù)據(jù)庫 (不論是什么數(shù)據(jù)庫)的數(shù)據(jù)導(dǎo)出到一個(gè)文件,一般是Excel或文本格式的CSV;對于Excel來講,對于POI和JXL的接口,你很多時(shí)候沒有辦法去控制內(nèi)存什么時(shí)候向磁盤寫入,很惡心,而且這些API在內(nèi)存構(gòu)造的對象大小將比數(shù)據(jù)原有的大小要大很多倍數(shù),所以你不得不去拆分Excel,還好,POI開始意識到這個(gè)問題,在3.8.4的版本后,開始提供cache的行數(shù),提供了SXSSFWorkbook的接口,可以設(shè)置在內(nèi)存中的行數(shù),不過可惜的是,他當(dāng)你超過這個(gè)行數(shù),每添加一行,它就將相對行數(shù)前面的一行寫入磁盤(如你設(shè)置2000行的話,當(dāng)你寫第20001行的時(shí)候,他會將第一行寫入磁盤),其實(shí)這個(gè)時(shí)候他些的臨時(shí)文件,以至于不消耗內(nèi)存,不過這樣你會發(fā)現(xiàn),刷磁盤的頻率會非常高,我們的確不想這樣,因?yàn)槲覀兿胱屗_(dá)到一個(gè)范圍一次性將數(shù)據(jù)刷如磁盤,比如一次刷1M之類的做法,可惜現(xiàn)在還沒有這種API,很痛苦,我自己做過測試,通過寫小的Excel比使用目前提供刷磁盤的API來寫大文件,效率要高一些,而且這樣如果訪問的人稍微多一些磁盤IO可能會扛不住,因?yàn)镮O資源是非常有限的,所以還是拆文件才是上策;而當(dāng)我們寫CSV,也就是文本類型的文件,我們很多時(shí)候是可以自己控制的,不過你不要用CSV自己提供的API,也是不太可控的,CSV本身就是文本文件,你按照文本格式寫入即可被CSV識別出來;如何寫入呢?下面來說說。。。
在處理數(shù)據(jù)層面,如從數(shù)據(jù)庫中讀取數(shù)據(jù),生成本地文件,寫代碼為了方便,我們未必要1M怎么來處理,這個(gè)交給底層的驅(qū)動(dòng)程序去拆分,對于我們的程序來講我們認(rèn)為它是連續(xù)寫即可;我們比如想將一個(gè)1000W數(shù)據(jù)的數(shù)據(jù)庫表,導(dǎo)出到文件;此時(shí),你要么進(jìn)行分頁,oracle當(dāng)然用三層包裝即可, MySQL 用limit,不過分頁每次都會新的查詢,而且隨著翻頁,會越來越慢,其實(shí)我們想拿到一個(gè)句柄,然后向下游動(dòng),編譯一部分?jǐn)?shù)據(jù)(如10000行)將寫文件一次(寫文件細(xì)節(jié)不多說了,這個(gè)是最基本的),需要注意的時(shí)候每次buffer的數(shù)據(jù),在用outputstream寫入的時(shí)候,最好flush一下,將緩沖區(qū)清空下;接下來, 執(zhí)行一個(gè)沒有where條件的SQL,會不會將內(nèi)存撐爆 ?是的,這個(gè)問題我們值得去思考下,通過API發(fā)現(xiàn)可以對SQL進(jìn)行一些操作,例如,通過:PreparedStatement statement = connection.prepareStatement(sql),這是默認(rèn)得到的預(yù)編譯,還可以通過設(shè)置:PreparedStatement statement = connection.prepareStatement(sql , ResultSet.TYPE_FORWARD_ONLY , ResultSet.CONCUR_READ_ONLY);
來設(shè)置游標(biāo)的方式,以至于游標(biāo)不是將數(shù)據(jù)直接cache到本地內(nèi)存,然后通過設(shè)置statement.setFetchSize(200);設(shè)置游標(biāo)每次遍歷的大小;OK,這個(gè)其實(shí)我用過,oracle用了和沒用沒區(qū)別,因?yàn)閛racle的jdbc API默認(rèn)就是不會將數(shù)據(jù)cache到j(luò)ava的內(nèi)存中的,而mysql里頭設(shè)置根本無效, 我上面說了一堆廢話,呵呵 ,我只是想說,java提供的標(biāo)準(zhǔn)API也未必有效,很多時(shí)候要看廠商的實(shí)現(xiàn)機(jī)制,還有這個(gè)設(shè)置是很多網(wǎng)上說有效的,但是這純屬抄襲;對于oracle上面說了不用關(guān)心,他本身就不是cache到內(nèi)存,所以java內(nèi)存不會導(dǎo)致什么問題,如果是mysql,首先必須使用5以上的版本,然后在連接參數(shù)上加上useCursorFetch=true這個(gè)參數(shù),至于游標(biāo)大小可以通過連接參數(shù)上加上:defaultFetchSize=1000來設(shè)置,例如:
jdbc:mysql://xxx.xxx.xxx.xxx:3306/abc?zeroDateTimeBehavior=convertToNull&useCursorFetch=true&defaultFetchSize=1000
上次被這個(gè)問題糾結(jié)了很久(mysql的數(shù)據(jù)老導(dǎo)致程序內(nèi)存膨脹,并行2個(gè)直接系統(tǒng)就宕了),還去看了很多源碼才發(fā)現(xiàn)奇跡竟然在這里,最后經(jīng)過mysql文檔的確認(rèn),然后進(jìn)行測試,并行多個(gè),而且數(shù)據(jù)量都是500W以上的,都不會導(dǎo)致內(nèi)存膨脹,GC一切正常,這個(gè)問題終于完結(jié)了。
看完以上內(nèi)容大家應(yīng)該累了吧,這里小編把自己的大數(shù)據(jù)學(xué)習(xí)qun531629188推薦給大家無論是大牛還是想轉(zhuǎn)行想學(xué)習(xí)的大學(xué)生
小編我都挺歡迎,晚上20:10都有一節(jié)【免費(fèi)的】大數(shù)據(jù)直播課程,專注大數(shù)據(jù)分析方法,大數(shù)據(jù)編程,大數(shù)據(jù)倉庫,大數(shù)據(jù)案例,人工智能,數(shù)據(jù)挖掘都是純干貨分享,
我們再聊聊其他的,數(shù)據(jù)拆分和合并,當(dāng)數(shù)據(jù)文件多的時(shí)候我們想合并,當(dāng)文件太大想要拆分,合并和拆分的過程也會遇到類似的問題,還好,這個(gè)在我們可控制的范圍內(nèi),如果文件中的數(shù)據(jù)最終是可以組織的,那么在拆分和合并的時(shí)候,此時(shí)就不要按照數(shù)據(jù)邏輯行數(shù)來做了,因?yàn)樾袛?shù)最終你需要解釋數(shù)據(jù)本身來判定,但是只是做拆分是沒有必要的,你需要的是做二進(jìn)制處理,在這個(gè)二進(jìn)制處理過程,你要注意了,和平時(shí)read文件不要使用一樣的方式,平時(shí)大多對一個(gè)文件讀取只是用一次read操作,如果對于大文件內(nèi)存肯定直接掛掉了,不用多說,你此時(shí)因該每次讀取一個(gè)可控范圍的數(shù)據(jù),read方法提供了重載的offset和length的范圍,這個(gè)在循環(huán)過程中自己可以計(jì)算出來,寫入大文件和上面一樣,不要讀取到一定程序就要通過寫入流flush到磁盤;其實(shí)對于小數(shù)據(jù)量的處理在現(xiàn)代的NIO技術(shù)的中也有用到,例如多個(gè)終端同時(shí)請求一個(gè)大文件下載,例如視頻下載吧,在常規(guī)的情況下,如果用java的容器來處理,一般會發(fā)生兩種情況:
其一為內(nèi)存溢出,因?yàn)槊總€(gè)請求都要加載一個(gè)文件大小的內(nèi)存甚至于更多,因?yàn)閖ava包裝的時(shí)候會產(chǎn)生很多其他的內(nèi)存開銷,如果使用二進(jìn)制會產(chǎn)生得少一些,而且在經(jīng)過輸入輸出流的過程中還會經(jīng)歷幾次內(nèi)存拷貝,當(dāng)然如果有你類似nginx之類的中間件,那么你可以通過send_file模式發(fā)送出去,但是如果你要用程序來處理的時(shí)候,內(nèi)存除非你足夠大,但是java內(nèi)存再大也會有GC的時(shí)候,如果你內(nèi)存真的很大,GC的時(shí)候死定了,當(dāng)然這個(gè)地方也可以考慮自己通過直接內(nèi)存的調(diào)用和釋放來實(shí)現(xiàn),不過要求剩余的物理內(nèi)存也足夠大才行,那么足夠大是多大呢?這個(gè)不好說,要看文件本身的大小和訪問的頻率;
其二為假如內(nèi)存足夠大,無限制大,那么此時(shí)的限制就是線程,傳統(tǒng)的IO模型是線程是一個(gè)請求一個(gè)線程,這個(gè)線程從主線程從線程池中分配后,就開始工作,經(jīng)過你的Context包裝、Filter、攔截器、業(yè)務(wù)代碼各個(gè)層次和業(yè)務(wù)邏輯、訪問數(shù)據(jù)庫、訪問文件、渲染結(jié)果等等,其實(shí)整個(gè)過程線程都是被掛住的,所以這部分資源非常有限,而且如果是大文件操作是屬于IO密集型的操作,大量的CPU時(shí)間是空余的,方法最直接當(dāng)然是增加線程數(shù)來控制,當(dāng)然內(nèi)存足夠大也有足夠的空間來申請線程池,不過一般來講一個(gè)進(jìn)程的線程池一般會受到限制也不建議太多的,而在有限的系統(tǒng)資源下,要提高性能,我們開始有了new IO技術(shù),也就是NIO技術(shù),新版的里面又有了AIO技術(shù),NIO只能算是異步IO,但是在中間讀寫過程仍然是阻塞的(也就是在真正的讀寫過程,但是不會去關(guān)心中途的響應(yīng)),還未做到真正的異步IO,在監(jiān)聽connect的時(shí)候他是不需要很多線程參與的,有單獨(dú)的線程去處理,連接也又傳統(tǒng)的socket變成了selector,對于不需要進(jìn)行數(shù)據(jù)處理的是無需分配線程處理的;而AIO通過了一種所謂的回調(diào)注冊來完成,當(dāng)然還需要OS的支持,當(dāng)會掉的時(shí)候會去分配線程,目前還不是很成熟,性能最多和NIO吃平,不過隨著技術(shù)發(fā)展,AIO必然會超越NIO,目前谷歌V8虛擬機(jī)引擎所驅(qū)動(dòng)的node.js就是類似的模式,有關(guān)這種技術(shù)不是本文的說明重點(diǎn);
將上面兩者結(jié)合起來就是要解決大文件,還要并行度,最土的方法是將文件每次請求的大小降低到一定程度,如8K(這個(gè)大小是經(jīng)過測試后網(wǎng)絡(luò)傳輸較為適宜的大小,本地讀取文件并不需要這么小),如果再做深入一些,可以做一定程度的cache,將多個(gè)請求的一樣的文件,cache在內(nèi)存或分布式緩存中,你不用將整個(gè)文件cache在內(nèi)存中,將近期使用的cache幾秒左右即可,或你可以采用一些熱點(diǎn)的算法來配合;類似迅雷下載的斷點(diǎn)傳送中(不過迅雷的網(wǎng)絡(luò)協(xié)議不太一樣),它在處理下載數(shù)據(jù)的時(shí)候未必是連續(xù)的,只要最終能合并即可,在服務(wù)器端可以反過來,誰正好需要這塊的數(shù)據(jù),就給它就可以;才用NIO后,可以支持很大的連接和并發(fā),本地通過NIO做socket連接測試,100個(gè)終端同時(shí)請求一個(gè)線程的服務(wù)器,正常的WEB應(yīng)用是第一個(gè)文件沒有發(fā)送完成,第二個(gè)請求要么等待,要么超時(shí),要么直接拒絕得不到連接,改成NIO后此時(shí)100個(gè)請求都能連接上服務(wù)器端,服務(wù)端只需要1個(gè)線程來處理數(shù)據(jù)就可以,將很多數(shù)據(jù)傳遞給這些連接請求資源,每次讀取一部分?jǐn)?shù)據(jù)傳遞出去,不過可以計(jì)算的是,在總體長連接傳輸過程中總體效率并不會提升,只是相對相應(yīng)和所開銷的內(nèi)存得到量化控制,這就是技術(shù)的魅力,也許不要太多的算法,不過你得懂他。
類似的數(shù)據(jù)處理還有很多,有些時(shí)候還會將就效率問題,比如在 HBase 的文件拆分和合并過程中,要不影響線上業(yè)務(wù)是比較難的事情,很多問題值得我們?nèi)パ芯繄鼍?,因?yàn)椴煌膱鼍坝胁煌姆椒ㄈソ鉀Q,但是大同小異,明白思想和方法,明白內(nèi)存和體系 架構(gòu) ,明白你所面臨的是沈陽的場景,只是細(xì)節(jié)上改變可以帶來驚人的效果。
作者:風(fēng)火數(shù)據(jù)
鏈接:https://juejin.im/post/5b556c846fb9a04f9963a8b5
來源:掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。
本文名稱:高級Java研發(fā)人員在解決大數(shù)據(jù)問題上的技巧-創(chuàng)新互聯(lián)
網(wǎng)頁鏈接:http://www.rwnh.cn/article36/dhhssg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供App開發(fā)、App設(shè)計(jì)、全網(wǎng)營銷推廣、微信小程序、關(guān)鍵詞優(yōu)化、域名注冊
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容