Java千万级别数据生成文件思路和优化

上一篇 / 下一篇  2012-04-26 11:04:30 / 个人分类:Java

一年前写过一个百万级别数据库数据生成配置xml文件的程序,程序目的是用来把数据库里面的数据生成xml文件.程序可以配置多少文件生成到一个文件中去。

.`(FuBp&H0  程序刚开始设计的时候说的是最多百万级别数据,最多50W数据生成到一个xml文件里面去,所以在做测试的 时候自己也只是造了100W的数据并没有做过多数据量的测试,然后问题就来了....由于程序使用的局点数据量巨大,需要生成xml文件的客户资料接近千 万级别的程度,而现场对程序的配置大约是100W条数据生成一个xml文件里面去,程序在这样的大数据量下面偶尔会有崩溃。

%FS]^D0

0O_ lw?uD%t p0  最近几天现场催的比较紧,最近抽空把这个问题处理了一下,在解决问题的过程中我把解决的步骤和方法记录了下来,正好和大家共享一下。51Testing软件测试网x*J `*h)IkG

51Testing软件测试网%Lga;Y:nD&G&Z7Sf

  现场提的问题概况:

)V;Z#nb0h QqM051Testing软件测试网h;u)X DX~R

  数据量:生成xml,每个文件100W+ 条的数据

(O2z{.g0O0

mq qg1KSj0  内存控制:最好不要超过512M51Testing软件测试网J:V i.VkTsR!OL

51Testing软件测试网$["G#c&yxG'j@$c

  问题详情:在处理70W左右的时候内存溢出

d1@qR-}bl-v3oP0

'?8b@ D3c:T7_V0  一、先来看一下程序要生成的xml文件的结构51Testing软件测试网g^:e6i&}x-g:y%e

  1. <File>  
  2.   <FileType>1</FileType>  
  3.   <RType>12</RType>  
  4.   <Version>03</Version>  
  5.   <BNo>004</BNo>  
  6.   <FileQ>5</FileQ>  
  7.   <FNo>0006</FNo>  
  8.   <RecordNum>1000000</RecordNum>  
  9.   <!-- 上面是文件头  下面是百万个<RecordList>  -->  
  10.   <RecordList>  
  11.     <Msisdn>10350719507</Msisdn>  
  12.     <State>1</State>  
  13.     <StartDate>20110303</StartDate>  
  14.     <Date>20110419</Date>  
  15.     <Balance>45000</Balance>  
  16.   </RecordList>  
  17.    ...  <!-- 可能百万个  <RecordList> 块-->  
  18.  </File>
51Testing软件测试网{ ccN1\ ib

  二、给大家说一下如何把大数据生成xml文件

'q6y+r j^+cK051Testing软件测试网K Oj-u2xIV

  1、小数据量的情况下 < 1W条数据51Testing软件测试网GKBU`Bk6p

2b)Qm^DG0  比较好用的方法是使用开源框架,比如XStream 直接把javabean 生成 xml

aR&Br\7sd0ON0

@;xWyz&[1^0  优点:api操作简单,方便维护

Nya1R1s(@H0

p0S3LI7h3SR0  缺点:数据量大的情况下太消耗内存51Testing软件测试网w:_%U$oVxt

z HhK7`:Q0|0  2、大数据量生成一个xml文件(本程序采用的方法)

d%EKm*bM!sI051Testing软件测试网W'}~1Z QQU}

  自己做的一个可以使用极少的内存生成无限制大的xml文件框架由3部分生成xml文件

I4^(`GW%JH0

2@4B:Gqd1_T0  第一部分:生成文件头51Testing软件测试网 t?+l-CN{Q^_

0^WS?6t0  例如:xxx.toXML(Object obj, String fileName)51Testing软件测试网9Ok+b ]%\

51Testing软件测试网5F qeO1D

  第二部分:通过每次向文件里面追加3000(可配置)条数据的形式生成文件块

(v+|6E3_"\ WCn-k[@+P0

wq#g&V\D,a8h7U~0  例如:xxx.appendXML(Object object); //object 可以是ArrayList 或者一个单独的javaBean

1e'IB5_/L IJ~0

4i6P(F.OtIV0第三部分:生成xml文件尾巴51Testing软件测试网ZSPG2Htc)H

51Testing软件测试网%H.^ I\[6LW

  例如:xxx.finishXML();

p2l |b'[051Testing软件测试网.u7~f6pVl Zn

  程序中的调用:调用xxx.toXML(Object obj, String fileName) 生成文件头之后,可以循环从数据库中读取数据生成ArrayList,通过xxx.appendXML(Object object) 方法追加到xml文件里面,xxx.finishXML() 对文件进行收尾

mG-} mx.u2}051Testing软件测试网!E"cU }pZ1J S-t,S

  对框架说明:我上面提供的例子有文件头 + 文件块 + 文件尾巴. 如果和你们的实际使用文件不太一致的话,可以参考上面提供的思路修改一下即可,主要的方法是把相同的文件块部分分离出来通过追加的形式写入xml文件.

3|*_%e*I%V2A0rBX@0

$zM$K3k&h|0  有了思路之后,大家可以尝试着自己写一个类似的大数据处理框架(千万级别以上),如何有什么需要帮助的可以直接联系我,因为是公司的程序,不太敢放出来,怕......51Testing软件测试网:]e2m9} M'V#j m

(J*cU!]l8t&P|0  三、我是如何测试性能和优化的51Testing软件测试网8gX7jY#d?3G0z

;l fG!U5w0  1、手动排除

k4vW['} @m&d051Testing软件测试网 K-VoB4HqjruA"S\

  根据文件崩溃时候的日志发现是在生成xml的框架里面报的错误,第一想到的是框架有些资源没有释放.于是把自己做的文件生成框架整体的排查了一 遍,并且自己写个简单程序生成200万条数据,使用xml框架生成一个xml文件,整个生成过程中任务管理器(xp)查看程序对应的java进程使用的内 存基本在20M左右,因此排除框架的问题.怀疑是数据库查询和调用框架的部门出现问题.

M9N,EL;F{W051Testing软件测试网 aJ7{fg G2Xe

  检测了一遍主程序的关键部分代码,优化了一下字符串处理.手动的释放一些对象的内存(例如:调用ArrayList.clear(),或者把对 象置空等),分配512内存后运行程序,60万数据的时候内存溢出,因为能主动释放的对象都已经释放掉了,还是没有解决,果断放弃看代码,准备使用 JProfile进行内存检测.

#o}[Qp2A JKhR0

;T"_A0eD!w/G0  2、手动排除没有解决,借助内存分析工具JProfile进行排除

h&~,| R Dk0c0tY(_Q0

;I$U9w#G8L%E0  通过在数据库中生成300W条数据,在JProfile上面多跑程序,一边运行,一边调用JProfile 提供的执行GC按钮主动运行垃圾回收,运行50W数据后,通过检测中发现 java.long.String[] 和 oracle.jdbc.driver.Binder[] 两个对象的数目一直保持在自增状态,而且数目基本上差不多,对象数目 都在200W以上,由于java.long.String[]对象是需要依赖对象而存在的,因此断定问题就出在 oracle.jdbc.driver.Binder[]上面,由于改对象存在引用导致String[]不能正常回收.51Testing软件测试网2j4SIGN"_,pc2a

z:} y!X zVE5z0  3、通过在JProfile对象查看对象的管理

a;B:H` r!n*ao)[ v0

J1JJ4Z'S0r5?5k0  检测到oracle.jdbc.driver.Binder 被 oracle.jdbc.driver.T4CPreparedStatement 引起,而T4CPreparedStatement正好是Oracle对jdbc OraclePreparedStatement的具体实现,因此断定是在数据库处理方面出现的问题导致 oracle.jdbc.driver.Binder对象不能正常释放,通过再一次有目的的检测代码,排查jdbc数据查询的问题,把问题的矛头直至数据 库的批处理和事务处理.因此程序是每生成一个文件成功后,会把已经处理的数据转移到对应的历史表中进行备份,而再个表操作的过程中使用了批处理和事务,使 用批处理主要是保证执行速度,使用事务主要是保证同时成功和失败。51Testing软件测试网#cI#b sv

51Testing软件测试网3WTD)T.]K@Ev

  4、又因此程序每次从数据库中查询3000条数据处理,所以准备监控oracle.jdbc.driver.Binder的对象数目是否和查询 次数对应.,通过在程序中Sysout输出查询次数 + JProfile运行GC测试 Binder,数据匹配,证实是java在数据库批处理的过程中有些问题.

HVc |K L'{ h0

M g3\.S i s!d:|%k#u0  5、专门把批处理代码提取出来通过JProfile内存分析.最终问题定位完毕.

1AZ[ J7w;T'je0

5LgG;F^ _ Z0  原因如下:100W数据生成一个文件的过程中,等文件生成完毕之后才能把数据库中的数据备份到历史表中,这个时候才能进行事务的提交,也就是执 行commit(), 并且删除原表数据,100W数据按照3000一批写入文件,每批次只是通过 PreparedStatement.addBatch();加入到批次里面去,并没有执行 PreparedStatement.executeBatch(),而是在commit()之前统一调用的 PreparedStatement.executeBatch(),这样的话PreparedStatement就会缓存100W条数据信息,造成了内 存溢出.

%t&F e+E{7f1T4[051Testing软件测试网Z%@9Xc2|.J7bk1h;s

  错误的方法如下:51Testing软件测试网V4OAz`q-~

51Testing软件测试网;Y/bK9`n

51Testing软件测试网|XAlD#Br+[

  1. try{  
  2.             conn.setAutoCommit(false);  
  3.             pst = conn.prepareStatement(insertSql);  
  4.             pstDel = conn.prepareStatement(delSql);  
  5.             pstUpdate = conn.prepareStatement(sql);  
  6.             ...   
  7.             //totalSize = 100W数据 / 3000一批次 
  8.             for (int i = 1; i <= totalSize; i++) {  
  9.                   
  10.                 client.appendXML(list);  
  11.                  
  12.             }  
  13.             // 错误的使用方法 
  14.             client.finishXML();  
  15.             pst.executeBatch();  
  16.             pstDel.executeBatch();  
  17.         }  
  18.          ...  
  19.         finally {  
  20.             try {  
  21.                 if (isError) {  
  22.                     conn.rollback();  
  23.                 }  
  24.                 else 
  25.                     conn.commit();  
  26.                ...  
  27.             }  
  28.           ...  
  29.         }
  30. 正确的方法如下51Testing软件测试网%N [7l j.Y P ?_A

    (|;J*a7X `B,Lr0
    1. try{            
    2.   conn.setAutoCommit(false);  
    3.             pst = conn.prepareStatement(insertSql);  
    4.             pstDel = conn.prepareStatement(delSql);  
    5.             pstUpdate = conn.prepareStatement(sql);  
    6.             ...   
    7.             //totalSize = 100W数据 / 3000一批次 
    8.             for (int i = 1; i <= totalSize; i++) {  
    9.                 list = 从数据库中查询3000条数据  
    10.                 client.appendXML(list);  
    11.  
    12.                pst.executeBatch();  
    13.                pstDel.executeBatch();  
    14.             }  
    15.             client.finishXML();  
    16.               
    17.         }  
    18.          ...  
    19.         finally {  
    20.             try {  
    21.                 if (isError) {  
    22.                     conn.rollback();  
    23.                 }  
    24.                 else 
    25.                     conn.commit();  
    26.                ...  
    27.             }  
    28.           ...  
    29.         }
    51Testing软件测试网2hN.c Y `m2G

      如果碰到和我一样的需要给大家一个提醒。51Testing软件测试网/P+x?5BZ8P!Wn2DH v)d]

    51Testing软件测试网Z K6n"GU'v

      oracle在每次执行executeBatch();进行批处理的时候,当前connection对应的rownum会根据操作的结果发生变化。51Testing软件测试网mBxO&YnX

    51Testing软件测试网4W,E{;W0jc5|

      在执行pst.executeBatch(); 之后,当前连接的 rownum 数就会发生变化. 因此凡是通过rownum查询数据的程序都要小心这一点

    +Wdv;M k0

    iC&b6`t.~ w} f0  下一篇将整理写java大数据(千万级别以上的)处理,包括 ftp大数据处理、文件生成大数据处理、数据库转移大数据处理、文件读取大数据处理等等。51Testing软件测试网aE.c0^I


TAG:

 

评分:0

我来说两句

Open Toolbar