java千万级别数据生成文件思路和优化
来源:互联网 发布:mac安装myeclipse 编辑:程序博客网 时间:2024/05/21 09:25
java千万级别数据生成文件思路和优化
一年前写过一个百万级别数据库数据生成配置xml文件的程序,程序目的是用来把数据库里面的数据生成xml文件.程序可以配置多少文件生成到一个文件中去.
程序刚开始设计的时候说的是最多百万级别数据,最多50W数据生成到一个xml文件里面去,所以在做测试的时候自己也只是造了100W的数据并没有做过多数据量的测试,然后问题就来了....由于程序使用的局点数据量巨大,需要生成xml文件的客户资料接近千万级别的程度,而现场对程序的配置大约是100W条数据生成一个xml文件里面去,程序在这样的大数据量下面偶尔会有崩溃.
最近几天现场催的比较紧,最近抽空把这个问题处理了一下,在解决问题的过程中我把解决的步骤和方法记录了下来,正好和大家共享一下
现场提的问题概况:
数据量:生成xml,每个文件100W+条的数据
内存控制:最好不要超过512M
问题详情:在处理70W左右的时候内存溢出
一、先来看一下程序要生成的xml文件的结构
Xml代码
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>
二、给大家说一下如何把大数据生成xml文件
1、小数据量的情况下 < 1W条数据
比较好用的方法是使用开源框架,比如XStream直接把javabean 生成 xml
优点:api操作简单,方便维护
缺点:数据量大的情况下太消耗内存
2、大数据量生成一个xml文件(本程序采用的方法)
自己做的一个可以使用极少的内存生成无限制大的xml文件框架由3部分生成xml文件
第一部分:生成文件头
例如: xxx.toXML(Object obj, String fileName)
第二部分:通过每次向文件里面追加3000(可配置)条数据的形式生成文件块
例如:xxx.appendXML(Object object); //object可以是ArrayList 或者一个单独的javaBean
第三部分:生成xml文件尾巴
例如:xxx.finishXML();
程序中的调用:调用xxx.toXML(Object obj,String fileName)生成文件头之后,可以循环从数据库中读取数据生成ArrayList,通过xxx.appendXML(Object object)方法追加到xml文件里面,xxx.finishXML()对文件进行收尾
对框架说明:我上面提供的例子有文件头 +文件块 + 文件尾巴.如果和你们的实际使用文件不太一致的话,可以参考上面提供的思路修改一下即可,主要的方法是把相同的文件块部分分离出来通过追加的形式写入xml文件.
有了思路之后,大家可以尝试着自己写一个类似的大数据处理框架(千万级别以上),如何有什么需要帮助的可以直接联系我,因为是公司的程序,不太敢放出来,怕......
三、我是如何测试性能和优化的
1、手动排除
根据文件崩溃时候的日志发现是在生成xml的框架里面报的错误,第一想到的是框架有些资源没有释放.于是把自己做的文件生成框架整体的排查了一遍,并且自己写个简单程序生成200万条数据,使用xml框架生成一个xml文件,整个生成过程中任务管理器(xp)查看程序对应的java进程使用的内存基本在20M左右,因此排除框架的问题.怀疑是数据库查询和调用框架的部门出现问题.
检测了一遍主程序的关键部分代码,优化了一下字符串处理.手动的释放一些对象的内存(例如:调用ArrayList.clear(),或者把对象置空等),分配512内存后运行程序,60万数据的时候内存溢出,因为能主动释放的对象都已经释放掉了,还是没有解决,果断放弃看代码,准备使用JProfile进行内存检测.
2、手动排除没有解决,借助内存分析工具JProfile进行排除
通过在数据库中生成300W条数据,在JProfile上面多跑程序,一边运行,一边调用JProfile提供的执行GC按钮主动运行垃圾回收,运行50W数据后,通过检测中发现 java.long.String[]和 oracle.jdbc.driver.Binder[]两个对象的数目一直保持在自增状态,而且数目基本上差不多,对象数目都在200W以上,由于java.long.String[]对象是需要依赖对象而存在的,因此断定问题就出在oracle.jdbc.driver.Binder[]上面,由于改对象存在引用导致String[]不能正常回收.
3、通过在JProfile对象查看对象的管理
检测到oracle.jdbc.driver.Binder被 oracle.jdbc.driver.
T4CPreparedStatement引起,而T4CPreparedStatement正好是Oracle对jdbcOraclePreparedStatement的具体实现,因此断定是在数据库处理方面出现的问题导致oracle.jdbc.driver.Binder对象不能正常释放,通过再一次有目的的检测代码,排查jdbc数据查询的问题,把问题的矛头直至数据库的批处理和事务处理.因此程序是每生成一个文件成功后,会把已经处理的数据转移到对应的历史表中进行备份,而再个表操作的过程中使用了批处理和事务,使用批处理主要是保证执行速度,使用事务主要是保证同时成功和失败。
4、又因此程序每次从数据库中查询3000条数据处理,所以准备监控oracle.jdbc.driver.Binder的对象数目是否和查询次数对应.,通过在程序中Sysout输出查询次数 + JProfile运行GC测试 Binder,数据匹配,证实是java在数据库批处理的过程中有些问题.
5、专门把批处理代码提取出来通过JProfile内存分析.最终问题定位完毕.
原因如下:100W数据生成一个文件的过程中,等文件生成完毕之后才能把数据库中的数据备份到历史表中,这个时候才能进行事务的提交,也就是执行commit(),并且删除原表数据,100W数据按照3000一批写入文件,每批次只是通过PreparedStatement.addBatch();加入到批次里面去,并没有执行PreparedStatement.executeBatch(),而是在commit()之前统一调用的PreparedStatement.executeBatch(),这样的话PreparedStatement就会缓存100W条数据信息,造成了内存溢出.
错误的方法如下:
Java代码
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. }
正确的方法如下
try{
Java代码
1. conn.setAutoCommit(false);
2. pst = conn.prepareStatement(insertSql);
3. pstDel = conn.prepareStatement(delSql);
4. pstUpdate = conn.prepareStatement(sql);
5. ...
6. //totalSize = 100W数据 / 3000一批次
7. for (int i = 1; i <= totalSize; i++) {
8. list = 从数据库中查询3000条数据
9. client.appendXML(list);
10.
11. pst.executeBatch();
12. pstDel.executeBatch();
13. }
14. client.finishXML();
15.
16.
17. ...
18. inally {
19. try {
20. if (isError) {
21. conn.rollback();
22. }
23. else
24. conn.commit();
25. ...
26. }
27. ...
如果碰到和我一样的需要给大家一个提醒.oracle在每次执行executeBatch();进行批处理的时候,当前connection对应的rownum会根据操作的结果发生变化.
在执行pst.executeBatch();之后,当前连接的 rownum数就会发生变化. 因此凡是通过rownum查询数据的程序都要小心这一点
java千万级别数据处理(2)-千万级别FTP下载
这个也是以前做过的一个程序,目的主要是去ftp主机(最多100左右)去取xx数据文件.
千万级别只是个概念,代表数据量等于千万或者大于千万的数据
本分享不牵扯分布式采集存储之类的.是在一台机器上处理数据,如果数据量很大很大的话,可以考虑分布式处理,如果以后我有这方面的经验,会及时分享的.
1、程序采用的ftp工具, apache 的 commons-net-ftp-2.0.jar
2、千万级别ftp核心关键的部分--列目录到文件,只要是这块做好了,基本上性能就没有太大的问题了.
可以通过apache发送ftp命令 "NLST"的方式列目录到文件中去
# ftp列目录执行的命令以环境变量的配置优先,不配置则使用默认的列目录方式 NLST
Java代码
1. # DS_LIST_CMD = NLST
2. public File sendCommandAndListToFile(String command,String localPathName) throws IOException
3. {
4. try {
5. return client.createFile(command, localPathName);
6. } catch (IOException e) {
7. log.error(e);
8. throw new IOException("the command "+command +" is incorrect");
9. }
10. }
当然应该还有其他形式的,大家可以自己研究一下
十万级别以上的数据量的话千万不要使用下面这种方式,如果用的话 ====找死
FTPFile[] dirList = client.listFiles();
3、分批次从文件中读取要下载的文件名. 加载到内存中处理,或者读取一个文件名就下载一个文件,不要把所有的数据都加载到内存,如果很多的话会出问题
为啥要分批次?
因为是大数据量,如果有1000W条记录,列出来的目录文件的大小 1G以上吧
4、文件下载的核心代码----关于文件的断点续传,获得ftp文件的大小和本地文件的大小进行判断,然后使用ftp提供的断点续传功能
下载文件一定要使用二进制的形式
client.enterLocalPassiveMode();//设置为被动模式
ftpclient.binary(); //一定要使用二进制模式
Java代码
1. /** 下载所需的文件并支持断点续传,下载后删除FTP文件,以免重复
2. * @param pathName 远程文件
3. * @param localPath 本地文件
4. * @param registerFileName 记录本文件名称目录
5. * @param size 上传文件大小
6. * @return true 下载及删除成功
7. * @throws IOException
8. * @throws Exception
9. */
10. public boolean downLoad(String pathName, String localPath) throws IOException {
11. boolean flag = false;
12. File file = new File(localPath+".tmp");//设置临时文件
13. FileOutputStream out = null;
14. try{
15. client.enterLocalPassiveMode();// 设置为被动模式
16. client.setFileType(FTP.BINARY_FILE_TYPE);//设置为二进制传输
17. if(lff.getIsFileExists(file)){//判断本地文件是否存在,如果存在并且长度小于FTP文件的长度时断点续传;返之新增
18. long size = this.getSize(pathName);
19. long localFileSize = lff.getSize(file);
20. if(localFileSize > size){
21. return false;
22. }
23. out = new FileOutputStream(file,true);
24. client.setRestartOffset(localFileSize);
25. flag = client.retrieveFile(new String(pathName.getBytes(),client.getControlEncoding()),out);
26.
27. out.flush();
28. } else{
29. out = new FileOutputStream(file);
30. flag = client.retrieveFile(new String(pathName.getBytes(),client.getControlEncoding()),out);
31.
32. out.flush();
33. }
34.
35. }catch(IOException e){
36. log.error(e);
37. log.error("file download error !");
38. throw e;
39. }finally{
40. try{
41. if(null!=out)
42. out.close();
43. if(flag)
44. lff.rename(file, localPath);
45. }catch(IOException e){
46. throw e;
47. }
48. }
49. return flag;
50. }
51. /**
52. * 获取文件长度
53. * @param fileNamepath 本机文件
54. * @return
55. * @throws IOException
56. */
57. public long getSize(String fileNamepath) throws IOException{
58. FTPFile [] ftp = client.listFiles(new String(fileNamepath.getBytes(),client.getControlEncoding()));
59. return ftp.length==0 ? 0 : ftp[0].getSize();
60. }
61.
62. 检测本地文件是否已经下载,如果下载文件的大小.
63.
64. /**
65. *本地文件的 获取文件的大小
66. * @param file
67. * @return
68. */
69. public long getSize(File file){
70. long size = 0;
71. if(getIsFileExists(file)){
72. size = file.length();
73. }
74. return size;
75. }
5、因为程序要跑最多100多个线程,在线程监控上做了一些处理,可以检测那些死掉的线程,并及时的拉起来。
t.setUncaughtExceptionHandler(new ThreadException(exList));
原理:给每个线程添加 UncaughtExceptionHandler,死掉的时候把线程对应的信息加入到一个list里面,然后让主线程每隔一段时间扫描一下list,如果有数据,直接重新建一个线程运行即可
6、如果程序是常驻内存的话,别忘记了在finally中关闭掉不用的ftp连接
7、做大数据库采集程序必须考虑到的一件事情 磁盘空间已满的处理
java虚拟机对于磁盘空间已满,在英文环境下的 linux aix机器上一般报
There is not enough space in the file system
中文环境下一般报 "磁盘空间已满"
大家可以使用下面的代码进行验证
Java代码
1. //检测磁盘控件是否已满的异常
Java代码
1. //linux aix There is not enough space in the file system
2. // window There is not enough space in the file system
3. if(e.toString().contains("enough space")||e.toString().contains("磁盘空间已满"))
4. {
5. log.error("channel "+channel_name + " There is not enough space on the disk ");
6. Runtime.getRuntime().exit(0);
7. }
- java千万级别数据生成文件思路和优化
- java千万级别数据生成文件思路和优化
- java千万级别数据生成文件思路和优化
- java千万级别数据生成文件思路和优化
- java千万级别数据生成文件思路和优化
- java千万级别数据生成文件思路和优化
- Mysql千万级别数据优化方案
- Mysql千万级别数据优化方案
- Mysql千万级别数据优化方案
- mysql数据库千万级别数据的查询优化和分页测试
- mysql数据库千万级别数据的查询优化和分页测试
- mysql数据库千万级别数据的查询优化和分页测试
- mysql数据库千万级别数据的查询优化和分页测试
- mysql数据库千万级别数据的查询优化和分页测试
- mysql数据库千万级别数据的查询优化和分页测试
- java如何读取1千万级别的数据
- 千万级别的数据存储
- java千万级别数据处理(2)-千万级别FTP下载
- C++的多态的几种形式
- MFC中文档与视图(二)
- 李进良:我对4G招标模式中国电信回应的回应与期望
- 第三方从UC账号免激活异步登录DX 3.1 方法
- Varchar和NVarchar的理解
- java千万级别数据生成文件思路和优化
- 走近SSH之Hibernate--HQL
- vs2008编译boost
- 正则表达式30分钟入门教程
- informix数据库移植步骤
- JAVA 基础练习
- oracle 数据库中的空间大小管理
- 间谍相机开发_NullPointerException 错误
- QThread使用的一个例子----多线程