溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java怎么將千萬(wàn)級(jí)別數(shù)據(jù)生成文件并優(yōu)化

發(fā)布時(shí)間:2021-09-10 11:57:52 來(lái)源:億速云 閱讀:210 作者:chen 欄目:編程語(yǔ)言

這篇文章主要講解了“Java怎么將千萬(wàn)級(jí)別數(shù)據(jù)生成文件并優(yōu)化”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“Java怎么將千萬(wàn)級(jí)別數(shù)據(jù)生成文件并優(yōu)化”吧!

現(xiàn)場(chǎng)提的問(wèn)題概況

  • 數(shù)據(jù)量:生成xml,每個(gè)文件100W+ 條的數(shù)據(jù)

  • 內(nèi)存控制:最好不要超過(guò)512M

  • 問(wèn)題詳情:在處理70W左右的時(shí)候內(nèi)存溢出

一、先來(lái)看一下程序要生成的xml文件的結(jié)構(gòu)

<File> 
<FileType>1</FileType> 
<RType>12</RType> 
<Version>03</Version> 
<BNo>004</BNo> 
<FileQ>5</FileQ> 
<FNo>0006</FNo> 
<RecordNum>1000000</RecordNum> 
<!-- 上面是文件頭 下面是百萬(wàn)個(gè)<RecordList> --> 
<RecordList> 
  <Msisdn>10350719507</Msisdn> 
  <State>1</State> 
  <StartDate>20110303</StartDate> 
  <Date>20110419</Date> 
  <Balance>45000</Balance> 
</RecordList> 
  ... <!-- 可能百萬(wàn)個(gè) <RecordList> 塊--> 
</File>

二、給大家說(shuō)一下如何把大數(shù)據(jù)生成xml文件

1、小數(shù)據(jù)量的情況下 < 1W條數(shù)據(jù)

比較好用的方法是使用開(kāi)源框架,比如XStream 直接把javabean 生成 xml

  • 優(yōu)點(diǎn):api操作簡(jiǎn)單,方便維護(hù)

  • 缺點(diǎn):數(shù)據(jù)量大的情況下太消耗內(nèi)存

2、大數(shù)據(jù)量生成一個(gè)xml文件(本程序采用的方法)

自己做的一個(gè)可以使用極少的內(nèi)存生成無(wú)限制大的xml文件框架由3部分生成xml文件

第一部分:生成文件頭

例如:xxx.toXML(Object obj, String fileName)

第二部分:通過(guò)每次向文件里面追加3000(可配置)條數(shù)據(jù)的形式生成文件塊

例如:xxx.appendXML(Object object); //object 可以是ArrayList 或者一個(gè)單獨(dú)的javaBean

第三部分:生成xml文件尾巴

例如:xxx.finishXML();

  • 程序中的調(diào)用:調(diào)用xxx.toXML(Object obj, String fileName) 生成文件頭之后,可以循環(huán)從數(shù)據(jù)庫(kù)中讀取數(shù)據(jù)生成ArrayList,通過(guò)xxx.appendXML(Object object) 方法追加到xml文件里面,xxx.finishXML() 對(duì)文件進(jìn)行收尾

  • 對(duì)框架說(shuō)明:我上面提供的例子有文件頭 + 文件塊 + 文件尾巴. 如果和你們的實(shí)際使用文件不太一致的話,可以參考上面提供的思路修改一下即可,主要的方法是把相同的文件塊部分分離出來(lái)通過(guò)追加的形式寫(xiě)入xml文件.

有了思路之后,大家可以嘗試著自己寫(xiě)一個(gè)類(lèi)似的大數(shù)據(jù)處理框架(千萬(wàn)級(jí)別以上),如何有什么需要幫助的可以直接聯(lián)系我,因?yàn)槭枪镜某绦?,不太敢放出?lái),怕......

三、我是如何測(cè)試性能和優(yōu)化的

1、手動(dòng)排除

根據(jù)文件崩潰時(shí)候的日志發(fā)現(xiàn)是在生成xml的框架里面報(bào)的錯(cuò)誤,第一想到的是框架有些資源沒(méi)有釋放.于是把自己做的文件生成框架整體的排查了一遍,并且自己寫(xiě)個(gè)簡(jiǎn)單程序生成200萬(wàn)條數(shù)據(jù),使用xml框架生成一個(gè)xml文件,整個(gè)生成過(guò)程中任務(wù)管理器(xp)查看程序?qū)?yīng)的java進(jìn)程使用的內(nèi)存基本在20M左右,因此排除框架的問(wèn)題.懷疑是數(shù)據(jù)庫(kù)查詢(xún)和調(diào)用框架的部門(mén)出現(xiàn)問(wèn)題.

檢測(cè)了一遍主程序的關(guān)鍵部分代碼,優(yōu)化了一下字符串處理.手動(dòng)的釋放一些對(duì)象的內(nèi)存(例如:調(diào)用ArrayList.clear(),或者把對(duì)象置空等),分配512內(nèi)存后運(yùn)行程序,60萬(wàn)數(shù)據(jù)的時(shí)候內(nèi)存溢出,因?yàn)槟苤鲃?dòng)釋放的對(duì)象都已經(jīng)釋放掉了,還是沒(méi)有解決,果斷放棄看代碼,準(zhǔn)備使用JProfile進(jìn)行內(nèi)存檢測(cè).

2、手動(dòng)排除沒(méi)有解決,借助內(nèi)存分析工具JProfile進(jìn)行排除

通過(guò)在數(shù)據(jù)庫(kù)中生成300W條數(shù)據(jù),在JProfile上面多跑程序,一邊運(yùn)行,一邊調(diào)用JProfile 提供的執(zhí)行GC按鈕主動(dòng)運(yùn)行垃圾回收,運(yùn)行50W數(shù)據(jù)后,通過(guò)檢測(cè)中發(fā)現(xiàn) java.long.String[] 和 oracle.jdbc.driver.Binder[] 兩個(gè)對(duì)象的數(shù)目一直保持在自增狀態(tài),而且數(shù)目基本上差不多,對(duì)象數(shù)目 都在200W以上,由于java.long.String[]對(duì)象是需要依賴(lài)對(duì)象而存在的,因此斷定問(wèn)題就出在oracle.jdbc.driver.Binder[]上面,由于改對(duì)象存在引用導(dǎo)致String[]不能正?;厥?

3、通過(guò)在JProfile對(duì)象查看對(duì)象的管理

檢測(cè)到oracle.jdbc.driver.Binder 被 oracle.jdbc.driver.T4CPreparedStatement 引起,而T4CPreparedStatement正好是Oracle對(duì)jdbc OraclePreparedStatement的具體實(shí)現(xiàn),因此斷定是在數(shù)據(jù)庫(kù)處理方面出現(xiàn)的問(wèn)題導(dǎo)致oracle.jdbc.driver.Binder對(duì)象不能正常釋放,通過(guò)再一次有目的的檢測(cè)代碼,排查jdbc數(shù)據(jù)查詢(xún)的問(wèn)題,把問(wèn)題的矛頭直至數(shù)據(jù)庫(kù)的批處理和事務(wù)處理.因此程序是每生成一個(gè)文件成功后,會(huì)把已經(jīng)處理的數(shù)據(jù)轉(zhuǎn)移到對(duì)應(yīng)的歷史表中進(jìn)行備份,而再個(gè)表操作的過(guò)程中使用了批處理和事務(wù),使用批處理主要是保證執(zhí)行速度,使用事務(wù)主要是保證同時(shí)成功和失敗。

4、又因此程序每次從數(shù)據(jù)庫(kù)中查詢(xún)3000條數(shù)據(jù)處理

所以準(zhǔn)備監(jiān)控oracle.jdbc.driver.Binder的對(duì)象數(shù)目是否和查詢(xún)次數(shù)對(duì)應(yīng).,通過(guò)在程序中Sysout輸出查詢(xún)次數(shù) + JProfile運(yùn)行GC測(cè)試 Binder,數(shù)據(jù)匹配,證實(shí)是java在數(shù)據(jù)庫(kù)批處理的過(guò)程中有些問(wèn)題.

5、專(zhuān)門(mén)把批處理代碼提取出來(lái)通過(guò)JProfile內(nèi)存分析.最終問(wèn)題定位完畢.

原因如下:100W數(shù)據(jù)生成一個(gè)文件的過(guò)程中,等文件生成完畢之后才能把數(shù)據(jù)庫(kù)中的數(shù)據(jù)備份到歷史表中,這個(gè)時(shí)候才能進(jìn)行事務(wù)的提交,也就是執(zhí)行commit(), 并且刪除原表數(shù)據(jù),100W數(shù)據(jù)按照3000一批寫(xiě)入文件,每批次只是通過(guò) PreparedStatement.addBatch();加入到批次里面去,并沒(méi)有執(zhí)行PreparedStatement.executeBatch(),而是在commit()之前統(tǒng)一調(diào)用的PreparedStatement.executeBatch(),這樣的話PreparedStatement就會(huì)緩存100W條數(shù)據(jù)信息,造成了內(nèi)存溢出.

錯(cuò)誤的方法如下

try{
    conn.setAutoCommit(false);
    pst = conn.prepareStatement(insertSql);
    pstDel = conn.prepareStatement(delSql);
    pstUpdate = conn.prepareStatement(sql);
    ... 
      //totalSize = 100W數(shù)據(jù) / 3000一批次 
    for (int i = 1; i <= totalSize; i++) {
        client.appendXML(list);
    }
    // 錯(cuò)誤的使用方法 
    client.finishXML();
    pst.executeBatch();
    pstDel.executeBatch();
}
... 
  finally {
    try {
        if (isError) {
            conn.rollback();
        } else 
              conn.commit();
        ...
    }
    ...
}

正確的方法如下

try{
    conn.setAutoCommit(false);
    pst = conn.prepareStatement(insertSql);
    pstDel = conn.prepareStatement(delSql);
    pstUpdate = conn.prepareStatement(sql);
    ... 
        //totalSize = 100W數(shù)據(jù) / 3000一批次 
    for (int i = 1; i <= totalSize; i++) {
        list = 從數(shù)據(jù)庫(kù)中查詢(xún)3000條數(shù)據(jù) 
                client.appendXML(list);
        pst.executeBatch();
        pstDel.executeBatch();
    }
    client.finishXML();
}
... 
finally {
    try {
        if (isError) {
            conn.rollback();
        } else 
               conn.commit();
        ...
    }
    ...
}

如果碰到和我一樣的需要給大家一個(gè)提醒。

oracle在每次執(zhí)行executeBatch();進(jìn)行批處理的時(shí)候,當(dāng)前connection對(duì)應(yīng)的rownum會(huì)根據(jù)操作的結(jié)果發(fā)生變化。

在執(zhí)行pst.executeBatch(); 之后,當(dāng)前連接的 rownum 數(shù)就會(huì)發(fā)生變化. 因此凡是通過(guò)rownum查詢(xún)數(shù)據(jù)的程序都要小心這一點(diǎn)。

感謝各位的閱讀,以上就是“Java怎么將千萬(wàn)級(jí)別數(shù)據(jù)生成文件并優(yōu)化”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)Java怎么將千萬(wàn)級(jí)別數(shù)據(jù)生成文件并優(yōu)化這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI