需求来源

我们系统作为组织用户的数据源, 很多下级单位系统需要不定时的获取用户全量表的数据,因为安全的原因我们不提对外接口,而是将数据读取出来并生成csv文件,然后进行压缩为gz文件之后, 放在他们指定安全的ftp上然后他们下级单位的系统去ftp上读取文件进行解析录入到自己的系统

组织表有10万条数据, 用户表有70万数据 ,用户表记录当前用户的组织的id,这个我们需要转换为实际组织的编码,而且用户的附属信息在其他的表中我们需要连表查询

加了几天的班给弄完了,必须记录下来以后肯定会用到的

方案第一版(单线程)

  1. 先将组织10万条数据一次查询出来放在内存中(用于用户的组织id转化为实际组织名称,不然每次都要查询数据库)
  2. 然后每次查询1万条数据的用户,之后将用户的组织id转换
  3. 然后将用户的相关信息按照要求,放入到csv格式的文件中

耗时: 30分钟左右

经过测试和分析,速度的瓶颈主要是查询数据库这个过程太耗时间了,大部分的实际都卡在ORM了, 在Navicat中查询很快,但是在代码中使用JPA查询非常慢,因为ORM架构涉及到数据映射和转换等各种处理, 可能你在Navicat查询1~2秒那么在代码中就可能需要几分钟 这个取决数据怎么传递的,过程是怎么解析,有没有缓存等,有很多因数需要实际去测试才行

方案第二版(多线程)

我们服务器的cpu是6核的

在第一版的基础上,我们添加了多线程, 线程数为cpu核心*3每条线程查询的数据量为: 总数条数/cput核心*3

耗时: 5分钟左右

经过测试发现,会把服务弄堆溢出了 ,并且用户查询的时候有问题,比如我在代码执行的时候添加了几个用户那么是查询不出来的因为我们先求得总数量然后分配给每个线程查询,这个查询的数量在开始就固定下来了

排查为啥堆会溢出,我们通过工具发现, 一次性创建了18个线程每个线程查询4万左右的数据,那么同一时间相当于查询了70万数据到内存中,我们老年的的的大小是2.5G ,系统启动后大小时占用了老年的500mb左右, 启动跑批程序后,老年代一直拔高直到占满老年代, 因为对象还被引用中,那么是回收不掉的,所以gc尝试了几次后就GG了

然后我自己亲自计算了下大小,具体怎么计算可以百度下对象占用大小计算方式就知道了,我下面只是大概按照对象计算规则大致的字节估算了下,而实际的大小只会比这个大不会小

用户的数据: 908(字节) 大约一条数据的大小 , 700000条*912=638(MB)
组织的数据 380(字节) 大约一条数据的大小 , 100000条*384=38(MB)

在过程我们使用Map重新组装了组织的数据38*2=76(mb)

在过程我们使用List重新用户的的数据638*2=1,276(mb)

因为计算会有偏差那么我们可以得知在跑这个程序的过程中,在内存中常驻的内存大约1.3(G)~1.5(G) ,然后在算上过程中各种组件工具为了服务这么多数据所使用的对象,基本上就2G以上了,在加上系统本身就占用500~700左右,这就溢出了

如果想看真实动态堆大小变化可以使用监控工具或者jstat都行

那么有人会说我们可以调大点堆内存啊, 这个是不可取的因为数据量会越来越多,你不可能一直调整内存啊,如果数据量过千万了,那么你需要多大的内存才行?

方案第三版(多线程)

在第二版的基础上我们进行了改良

  1. 不计算总数,不计算线程数, 而是每次查询1万条数据一个线程,一直到查询结果小于1万或者结果为空结束
  2. 我们利用线程池的核心线程数控制同一时间只处理多少线(多少数据量),防止内存溢出并且程最大化利用线程,使用队列控制线程最高创建的数量

核心线程和最大线程: 15 控制并发量-保证内存中最多同一时间只处理15万左右的数据量
队列: 1000 控制创建线程的峰值, 超出了就使用线程池默认的处理机制给丢弃任务


105~121 主要是获取配置的参数信息,比如ftp的ip账户和密码
115~117是将组织机构的信息查询出来,然后转换为id和code,便于之后用户可以通过id直接可以拿到对应的code,(hash是O(1)算法)
121~127是获取需要写的文件地址,如果存在了那么就删除


131~133 : 创建了线程通信的控制变量(乐观锁cas)
135: 死循环创建线程,每100毫秒创建一次
137~152: 每次查询1万用户,和用于控制创建线程的结束时机

153~171: 处理用户数据的转换

172: 计算总数

174~178: 将1万条处理完毕的数据,追加到文件的结尾,数据会压缩为gz (因为文件统一时间只能一次i/o那么不会产生线程安全的问题)

189~191: 判断所有真实的业务线程都处理完毕了
194~205: 给文件结尾追加描述信息 , 数据会压缩为gz
208 : 内部代码会将生成的csv文件推送给多个ftp

在测试环境(50多万用户数据和5万组织数据)的最终效果:

结果8498毫秒也就是只需要8秒就行了,在生产环境最多也就10秒左右

方案第四版(多线程-最终版)

第三版跑了1个月没啥问题,突然又一天,公司服务器的数据库,特别慢导致数据读取特别慢,这时候我们还在不停的创建线程,最后把线程池的队列弄满了, 万万没想到的是,线程池队列满了会把主线程给杀死,而子线程还能跑, 导致文件最后一行的说明没有加上去

也就是说,我们不能相信公司数据库性能和用户服务的性能 , 我们在没有读取完毕数据的时候会一直创建新的线程去读取数据

设计的时候为了防止在跑批任务执行过程中有人添加了数据,所以我们没有控制读取的次数,而是一直读取到为空结束, 那么我们线程就不知道啥时候才算创建完毕,所以会一直创建新的线程,直到某一线程读取数据为空,那么才会停止创建,在这个过程中具体创建了多少线程,取决于对方的数据库和对方的用户服务的性能

我们上线前是测试过得,10秒左右就能读取完毕,而我们控制的是每100毫秒才创建一个线程那么10秒内最多也不会创建超过100个线程,那么为了防止网络波动队列设置1000是没有啥问题的

玩玩没想到啊,突然公司mysql和用户服务的性能就不行了,读取1万条数据就等了10~20几秒那么70万条数据读取时间就将近5分钟了 ,这样就导致我们线程池队列溢出了, 主线程被干死了(跑批的线程) ,已经创建的子线程还是正常的, 但是主线程是用来监控子线程执行情况,当全部子线程都执行完毕后,那么主线程就在文件最后写入说明, 所以主线程死了文件结尾说明信息就没写入,导致别人的系统无法识别文件内容

经过研究解决方案如下:

  1. 改进组织读取的方式也采用多线程方式
  2. 减少读取数据的数量从每次读取1万-> 每次读取1千
  3. 控制线程创建的频率, 之前是不控制完全依赖于对方接口的速度, 现在改为我们自己控制创建线程的次数
  4. 优化读写 , 之前读写文件都在一个线程中 ,现在进行读写分离 ,异步读数据,异步写数据,任意一个线程读取完毕后立马就写,好处就是更高效的利用文件的i/o,不至于某些线程读取数据的过程中i/o被闲置

获取组织优化相关代码

人员全量跑批优化代码


经过测试只需要10秒左右, 虽然和第三版差不多速度,但是保证了稳定性

最后总结: 先保证稳定在保证速度, 而不要本末倒置了,我就是犯了这个问题,希望大家以此为戒

点赞 -收藏-关注-便于以后复习和收到最新内容 有其他问题在评论区讨论-或者私信我-收到会在第一时间回复 在本博客学习的技术不得以任何方式直接或者间接的从事违反中华人民共和国法律,内容仅供学习、交流与参考 免责声明:本文部分素材来源于网络,版权归原创者所有,如存在文章/图片/音视频等使用不当的情况,请随时私信联系我、以迅速采取适当措施,避免给双方造成不必要的经济损失。 感谢,配合,希望我的努力对你有帮助^_^

Java多线程-将全量用户表70万数据压缩并生成CSV文件和推送到FTP上(最快快方式)相关推荐

  1. twitter推特全量用户收集与发文采集

    twitter推特全量用户收集与发文采集 twitter推特全量用户收集与发文采集 为了研究各国的总统选举期间的民调,x项目需要M国全量推特用户的发文数据,以此为背景,需要将推特大部分用户(70%)的 ...

  2. JxlExcelUtil生成Excel供SFTP推送Java实现获取目录下最新的excel

    外汇交易中心,某版本需求变更:交易后将每天定时生成的全量黄金成交编号映射关系文件推送至金交所. 设计: 定时任务跑批实现. 1.根据传入的参数是否是给黄金交易所判断 isSFTP.传参, 以下方法组装 ...

  3. POI以SAX方式解析Excel2007大文件(包含空单元格的处理) Java生成CSV文件实例详解...

    http://blog.csdn.net/l081307114/article/details/46009015 http://www.cnblogs.com/dreammyle/p/5458280. ...

  4. Java实现生成csv文件并导入数据

    一.需求: 下载列表,在没有过滤之前下载列表所有数据,点击过滤之后,下载过滤之后对数据,生成csv文件. 二.思路: 先根据条件(是否过滤了数据)筛选出数据,将数据导入csv文件,生成文件并返回. 三 ...

  5. java导出csv文件乱码_记一次java生成csv文件乱码的解决过程 (GB2312编码)

    系统:win7 (格式:中文(简体,中国)) 工具:Eclipse (默认编码utf-8) 服务两个:[restful接口]  和 [服务*** server]. 场景:[服务*** server]多 ...

  6. Winform中导出Excel数据量百万级的处理办法-导出为csv文件

    场景 Winform中通过NPOI导出Excel的三种方式(HSSFWorkbook,XSSFWorkbook,SXSSFWorkbook)附代码下载: https://blog.csdn.net/B ...

  7. Java生成CSV文件

    1.新CSVUtils.java文件: package com.saicfc.pmpf.internal.manage.utils;import java.io.BufferedWriter; imp ...

  8. java调用百度推送详解_Java 以 Post 方式实现百度 Sitemap 实时推送

    Sitemap 可方便网站管理员通知搜索引擎他们网站上有哪些可供抓取的网页.最简单的 Sitemap 形式,就是XML 文件,在其中列出网站中的网址以及关于每个网址的其他元数据(上次更新的时间.更改的 ...

  9. python亿级mysql数据库导出_Python实现将MySQL数据库表中的数据导出生成csv格式文件的方法...

    本文实例讲述了python实现将MySQL数据库表中的数据导出生成csv格式文件的方法.分享给大家供大家参考,具体如下: #!/usr/bin/env python # -*- coding:utf- ...

最新文章

  1. SpringBoot (四) :SpringBoot整合使用JdbcTemplate
  2. idea中build项目之后生成的target看不见
  3. 指针08 - 零基础入门学习C语言48
  4. C# 获取视频文件播放时长
  5. 【Spring】Spring 中的bean 和我们java中的bean有什么区别以及spring 模拟实现
  6. Axure 经典实例高保真原型下载(Axure高保真企业办公oa系统OA协同办公后台管理会议管理用户管理统计分析活动管理+考勤管理+档案管理+行政支持管理)
  7. 【NOIP2015】运输计划
  8. HP SD2 DAT160小磁带机故障
  9. 2021年9月电子学会图形化四级编程题解析含答案:成语接龙
  10. 有对象的程序猿都是怎么写代码的
  11. 分布式存储引擎大厂实战——一文了解分布式存储的可靠性
  12. gulp:删除文件或文件夹——del
  13. Python爬虫入门教程06:爬取数据后的词云图制作
  14. matplotlib模拟重力场中粒子的分布
  15. 高效的磁力搜索引擎 -_高效的企业测试-结论(6/6)
  16. [MySQL学习]常用SQL语句大全总结
  17. python清空屏幕
  18. 浏览器主页被2345劫持简单处理方法
  19. iOS开发——Siri语音识别
  20. 简单100行代码一页完成(表白代码-爱心跳动)

热门文章

  1. Deepfakes教程及各个换脸软件下载
  2. mysql表的增删改select 和 where
  3. 2007工程院院士增选候选名单
  4. 武汉大学计算机学院辅导员,武汉大学2021年辅导员拟录名单出炉,多数为名校博士毕业生...
  5. C++学习——第一节课
  6. C#,数值计算,矩阵的乔莱斯基分解(Cholesky decomposition)算法与源代码
  7. 第一类曲面积分转化为二重积分
  8. 浅谈《数学之美》①——自然语言处理
  9. 【创业指导】如何理解 IPD 体系中的市场管理「MM」流程?这篇文章就够了
  10. 币圈人警惕!5大错误足以摧毁你的一切