一年前写过一个百万级别
数据库数据生成配置xml文件的程序,程序目的是用来把数据库里面的数据生成xml文件.程序可以配置多少文件生成到一个文件中去。
\8`'L Yz'I7g@0 程序刚开始设计的时候说的是最多百万级别数据,最多50W数据生成到一个xml文件里面去,所以在做测试的
时候自己也只是造了100W的数据并没有做过多数据量的测试,然后问题就来了....由于程序使用的局点数据量巨大,需要生成xml文件的客户资料接近千
万级别的程度,而现场对程序的配置大约是100W条数据生成一个xml文件里面去,程序在这样的大数据量下面偶尔会有崩溃。51Testing软件测试网zLOq#\p[
51Testing软件测试网g/S-aT]3Dz\\ 最近几天现场催的比较紧,最近抽空把这个问题处理了一下,在解决问题的过程中我把解决的步骤和方法记录了下来,正好和大家共享一下。
;N w$O{L%mE0LyR,A ^Q0 现场提的问题概况:51Testing软件测试网6T6FlC6i[XA*K
/Q| R-A$I){.@0 数据量:生成xml,每个文件100W+ 条的数据
8hN[2v/E051Testing软件测试网,F#n,\ fF 内存控制:最好不要超过512M51Testing软件测试网6]2J'h$dj;]F
$}j._/NL;w0U%L
W0 问题详情:在处理70W左右的时候内存溢出51Testing软件测试网E3S
C(U1}ut/Se`!]
'bYr/j#\.yV,f0 一、先来看一下程序要生成的xml文件的结构
f0xv&^-[`LJ&[ r p0- <File>
- <FileType>1</FileType>
- <RType>12</RType>
- <Version>03</Version>
- <BNo>004</BNo>
- <FileQ>5</FileQ>
- <FNo>0006</FNo>
- <RecordNum>1000000</RecordNum>
- <!-- 上面是文件头 下面是百万个<RecordList> -->
- <RecordList>
- <Msisdn>10350719507</Msisdn>
- <State>1</State>
- <StartDate>20110303</StartDate>
- <Date>20110419</Date>
- <Balance>45000</Balance>
- </RecordList>
- ... <!-- 可能百万个 <RecordList> 块-->
- </File>
|
51Testing软件测试网!yv&hf%V?Ur 二、给大家说一下如何把大数据生成xml文件51Testing软件测试网agv Q#j
51Testing软件测试网's(O#{ X*y1Y&d 1、小数据量的情况下 < 1W条数据
4Y_9U/P0|l!I,p051Testing软件测试网
X Af9ibI 比较好用的方法是使用开源框架,比如XStream 直接把javabean 生成 xml
l2CUu~b&qf0 HoCF"ge0 优点:api操作简单,方便维护51Testing软件测试网&\Xq"_u
r)eo'Se!t H6NL HC0 缺点:数据量大的情况下太消耗内存51Testing软件测试网r5Q3d{"Xk\oS
51Testing软件测试网C&M3y-K#g){+u-^ 2、大数据量生成一个xml文件(本程序采用的方法)51Testing软件测试网l)_+hZ9r-J
!o?ue)n%`w0 自己做的一个可以使用极少的内存生成无限制大的xml文件框架由3部分生成xml文件51Testing软件测试网T {+jU@wB
G
3A5plJ'D_uq0 第一部分:生成文件头51Testing软件测试网1kf0J6Vi'qEq2E5T
51Testing软件测试网?"d;r_N-\(mk 例如:xxx.toXML(Object obj, String fileName)51Testing软件测试网QX|;F-F
51Testing软件测试网P;gws/J3V4X*~ 第二部分:通过每次向文件里面追加3000(可配置)条数据的形式生成文件块51Testing软件测试网}6Z9f^4\m5Qt
51Testing软件测试网"_tgy!h]O4uI1a9{ 例如:xxx.appendXML(Object object); //object 可以是ArrayList 或者一个单独的javaBean51Testing软件测试网,y9KQ&ZRqY"R
51Testing软件测试网qN1{R5zR9Mk第三部分:生成xml文件尾巴
a2t;xF)mG&b+U05XM6}$h?;v2P0 例如:xxx.finishXML();51Testing软件测试网k\+A*}7[{ k
5M
B+@#wt0{+a0 程序中的调用:调用xxx.toXML(Object obj, String fileName)
生成文件头之后,可以循环从数据库中读取数据生成ArrayList,通过xxx.appendXML(Object object)
方法追加到xml文件里面,xxx.finishXML() 对文件进行收尾
"t7A(ZER0 rMS Z;zN.Gr"F0 对框架说明:我上面提供的例子有文件头 + 文件块 + 文件尾巴. 如果和你们的实际使用文件不太一致的话,可以参考上面提供的思路修改一下即可,主要的方法是把相同的文件块部分分离出来通过追加的形式写入xml文件.
\[{1yB6C
{0(?n te"fh0 有了思路之后,大家可以尝试着自己写一个类似的大数据处理框架(千万级别以上),如何有什么需要帮助的可以直接联系我,因为是公司的程序,不太敢放出来,怕......51Testing软件测试网;T]8_(D9hV
51Testing软件测试网Pn*iBj 三、我是如何测试性能和优化的51Testing软件测试网4n(k.L SN;|3K$a#E
a1w-]jU!Ut`0 1、手动排除
5fXTO2RLC[5]2E6v0E;jq'DR k0 根据文件崩溃时候的日志发现是在生成xml的框架里面报的错误,第一想到的是框架有些资源没有释放.于是把自己做的文件生成框架整体的排查了一
遍,并且自己写个简单程序生成200万条数据,使用xml框架生成一个xml文件,整个生成过程中任务管理器(xp)查看程序对应的java进程使用的内
存基本在20M左右,因此排除框架的问题.怀疑是数据库查询和调用框架的部门出现问题.
'eE|H6DH1hg02NEG7mR:[&gp+a0 检测了一遍主程序的关键部分代码,优化了一下字符串处理.手动的释放一些对象的内存(例如:调用ArrayList.clear(),或者把对
象置空等),分配512内存后运行程序,60万数据的时候内存溢出,因为能主动释放的对象都已经释放掉了,还是没有解决,果断放弃看代码,准备使用
JProfile进行内存检测.51Testing软件测试网'amA-ICM7Y
u,Q2~/pY0 2、手动排除没有解决,借助内存分析工具JProfile进行排除
b!kNOY{6N051Testing软件测试网iVN6qH4[ 通过在数据库中生成300W条数据,在JProfile上面多跑程序,一边运行,一边调用JProfile
提供的执行GC按钮主动运行垃圾回收,运行50W数据后,通过检测中发现 java.long.String[] 和
oracle.jdbc.driver.Binder[] 两个对象的数目一直保持在自增状态,而且数目基本上差不多,对象数目
都在200W以上,由于java.long.String[]对象是需要依赖对象而存在的,因此断定问题就出在
oracle.jdbc.driver.Binder[]上面,由于改对象存在引用导致String[]不能正常回收.
$X'j&k,u#u`$O,}051Testing软件测试网Dqhl3A 3、通过在JProfile对象查看对象的管理51Testing软件测试网-{(]+z].Q}AZk.WF
};PV2J'u{a
p0 检测到oracle.jdbc.driver.Binder 被
oracle.jdbc.driver.T4CPreparedStatement
引起,而T4CPreparedStatement正好是Oracle对jdbc
OraclePreparedStatement的具体实现,因此断定是在数据库处理方面出现的问题导致
oracle.jdbc.driver.Binder对象不能正常释放,通过再一次有目的的检测代码,排查jdbc数据查询的问题,把问题的矛头直至数据
库的批处理和事务处理.因此程序是每生成一个文件成功后,会把已经处理的数据转移到对应的历史表中进行备份,而再个表操作的过程中使用了批处理和事务,使
用批处理主要是保证执行速度,使用事务主要是保证同时成功和失败。
8vb,U5@Z&i'M+~0N [@_.WJ+f(s0 4、又因此程序每次从数据库中查询3000条数据处理,所以准备监控oracle.jdbc.driver.Binder的对象数目是否和查询
次数对应.,通过在程序中Sysout输出查询次数 + JProfile运行GC测试
Binder,数据匹配,证实是java在数据库批处理的过程中有些问题.51Testing软件测试网_,wX:mF9`8V n
51Testing软件测试网&t;t
D|*ldNu 5、专门把批处理代码提取出来通过JProfile内存分析.最终问题定位完毕.51Testing软件测试网J0F!z^(M
51Testing软件测试网 ]DPP?x5sz 原因如下:100W数据生成一个文件的过程中,等文件生成完毕之后才能把数据库中的数据备份到历史表中,这个时候才能进行事务的提交,也就是执
行commit(), 并且删除原表数据,100W数据按照3000一批写入文件,每批次只是通过
PreparedStatement.addBatch();加入到批次里面去,并没有执行
PreparedStatement.executeBatch(),而是在commit()之前统一调用的
PreparedStatement.executeBatch(),这样的话PreparedStatement就会缓存100W条数据信息,造成了内
存溢出.51Testing软件测试网9`.A7K;Mb
G
51Testing软件测试网(k9U!qn0`Xe7X8b 错误的方法如下:
6m] Y^)@5T+Y2A"T-a!` A0g*h@vKRw b0
@m~3?YnQ0- try{
- conn.setAutoCommit(false);
- pst = conn.prepareStatement(insertSql);
- pstDel = conn.prepareStatement(delSql);
- pstUpdate = conn.prepareStatement(sql);
- ...
-
- for (int i = 1; i <= totalSize; i++) {
-
- client.appendXML(list);
-
- }
-
- client.finishXML();
- pst.executeBatch();
- pstDel.executeBatch();
- }
- ...
- finally {
- try {
- if (isError) {
- conn.rollback();
- }
- else
- conn.commit();
- ...
- }
- ...
- }
- 正确的方法如下
m*@T.{
H-BK'c6\Y051Testing软件测试网 OnF8x2]#^vl
- try{
- conn.setAutoCommit(false);
- pst = conn.prepareStatement(insertSql);
- pstDel = conn.prepareStatement(delSql);
- pstUpdate = conn.prepareStatement(sql);
- ...
-
- for (int i = 1; i <= totalSize; i++) {
- list = 从数据库中查询3000条数据
- client.appendXML(list);
-
- pst.executeBatch();
- pstDel.executeBatch();
- }
- client.finishXML();
-
- }
- ...
- finally {
- try {
- if (isError) {
- conn.rollback();
- }
- else
- conn.commit();
- ...
- }
- ...
- }
|
H*IvD Q2i-H0 如果碰到和我一样的需要给大家一个提醒。
#EN5cmw3wz0;] g%]]4_`0 oracle在每次执行executeBatch();进行批处理的时候,当前connection对应的rownum会根据操作的结果发生变化。51Testing软件测试网W:_ oZ]'Q.fkh
51Testing软件测试网] vg+@$Tq5WQ 在执行pst.executeBatch(); 之后,当前连接的 rownum 数就会发生变化. 因此凡是通过rownum查询数据的程序都要小心这一点51Testing软件测试网(rF AzQ
fPA
51Testing软件测试网*Sa3J4i"_zf 下一篇将整理写java大数据(千万级别以上的)处理,包括 ftp大数据处理、文件生成大数据处理、数据库转移大数据处理、文件读取大数据处理等等。51Testing软件测试网%qAfUfu0@