`
Donald_Draper
  • 浏览: 951249 次
社区版块
存档分类
最新评论

百万级数据-程序迁移后续

阅读更多
百万级数据-程序迁移:http://donald-draper.iteye.com/blog/2327909
    在上面这一篇文章中,内存为2G的情况下,单线程分页数为10万,批量保存为5000的情况下,更新120万的数据,需要耗时20分钟左右,同时JVM被占满,由于以前认为数据更新一次就少,就没有优化;后来一次更新的记录达到百万,应用扛不住,现在总于抽出时间,来做一些优化。以前是单线程处理所有分页,现在每页通过一个线程去更新,每个线程获取一个jdbc连接,注意数据量过大,分页数小的情况下,jdbc连个可能同时需要建立多个,我们要保证数据库允许最大连接数够用,Oracle默认为100。
今天打算每一页用一个线程去更新,主要思路如下:
#######需要用到的变量
pageUpateSize:分页数
threadPoolSize:线程数
batchSize:批量保存数

sums为需要更新的记录数,我们测试的为126万,大于10万才分页更新
##############################
主线程核心代码:
ExecutorService exec = null;
int batches = 0;
if( sums > 100000){
	if(sums % pageUpateSize ==0){
		batches = sums/pageUpateSize;
	}
	else{
		batches = sums/pageUpateSize  + 1;
	}
}
AtomicInteger counts = new AtomicInteger(0);//更新记录数计数器
CountDownLatch doneSignal = new CountDownLatch(batches); 
exec = Executors.newFixedThreadPool(threadPoolSize);
for(int i =1;i<=batches;i++){
        //getConnection(),获取数据库连接
	exec.submit(new PageUpdateThread(getConnection(), tableName,(i-1)*pageUpateSize+1,(i)*pageUpateSize,counts,doneSignal));
}
doneSignal.await();//等待所有分页线程结束
logger.info("============All Insert Sizes:"+counts.get());




分页更新线程:
/**
 * 分页更新线程
 * @author donald
 * @date 2017-4-13
 * @time 下午4:37:07
 */
public class PageUpdateThread implements Runnable {
	private static final Logger log = LoggerFactory.getLogger(PageUpdateThread.class);
	private static int batchSize = 2500;
	private Connection con;
	private String tableName;
	private int startPos;
	private int endPos;
	private final  AtomicInteger totalCount;
	private final CountDownLatch doneSignal;
	private SynService synService = null;
	private String threadName;
	/**
	 * 
	 * @param con
	 * @param tableName
	 * @param startPos
	 * @param endPos
	 * @param totalCount
	 * @param doneSignal
	 */
	public PageUpdateAllThread(Connection con, String tableName,
			 int startPos, int endPos,
			AtomicInteger totalCount,CountDownLatch doneSignal) {
		super();
		this.con = con;
		this.startPos = startPos;
		this.endPos = endPos;
		this.totalCount = totalCount;
		this.doneSignal = doneSignal;
	}
	/**
	 * 
	 */
	private void init(){
		synService = new SynService();
		threadName = Thread.currentThread().getName();
	}
	@Override
	public void run() {
		init();
		try {
			log.info(threadName+"正在更新记录:"+startPos+","+endPos);
			work();
			log.info(threadName+"更新记录完毕:"+startPos+","+endPos);
		} catch (BatchUpdateException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		finally{
			doneSignal.countDown();
		}
	}
	/**
	 * 
	 * @throws BatchUpdateException
	 * @throws SQLException
	 */
	private void work() throws BatchUpdateException, SQLException{
		ResultSet addRs = null;
		PreparedStatement ps = null;
		List<PageData> insertList = new ArrayList<PageData>();
		//分页语句
		String sql = "SELECT * FROM (SELECT t.*, ROWNUM as rowno FROM ( SELECT * FROM "
				+ tableName
				+ " ORDER BY CREATETIME"
				+ " ) t WHERE ROWNUM <= ?)" + " WHERE rowno >= ?";
		log.info(threadName+"======Search insert records sql:" + sql + ",startPos:"
				+ startPos + ",endPos:" + endPos);
		int counts = 0;// 记录数
		try {
			ps = con.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE,
					ResultSet.CONCUR_READ_ONLY);
			ps.setInt(1, endPos);
			ps.setInt(2, startPos);
			addRs = ps.executeQuery();
			while (addRs.next()) {
				HashMap dataMap = null;
				dataMap = switch(addRs);//将记录放在Map中
				insertList.add(pd);
				if (counts % batchSize == 0 && counts > 0) {
					long childStartTime = System.currentTimeMillis();
					synService.batchInsertSync(tableName + "Mapper.save", insertList);
					long childEndTime = System.currentTimeMillis();
					log.info(threadName+"保存2500记录所用时间s:"
							+ (childEndTime - childStartTime) / 1000.00);
					insertList.clear();
					log.info(threadName+"============Records:" + counts);
				}
				if (addRs.isLast()) {
					synService.batchInsertSync(tableName + "Mapper.save", insertList);
					insertList.clear();
				}

				pd = null;
				counts++;
				totalCount.incrementAndGet();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		finally {
		        insertList = null;
			sql = null;
			if (addRs != null) {
				addRs.close();
				addRs = null;
			}
			if (ps != null) {
				ps.close();
				ps = null;
			}
			if (con != null) {
				con.close();
			}
		}
	}
}

下面我们来测试:
########################################
硬件环境如下:
硬件酷睿i7,4核处理器,JVM内存2G,记录数126万,数据库oracle
#########################################
JVM虚拟机参数配置:
-server
-XX:+UseConcMarkSweepGC
-XX:+PrintGCDetails
-Xloggc:E:\gc.log


测试的过程中,我们主要调试着3个参数:
pageUpateSize:分页数
threadPoolSize:线程数
batchSize:批量保存数


先调线程数:
参数设置及内存消耗和所用时间情况:
线程数,分页数,批量保存数,消耗内存最大值(G),耗时(s)
8,30000,  5000, 1.039, 353.661

Jconsole内存使用情况,垃圾回收次数和时间:
时间: 
2017-04-13 14:50:43
已用: 
 1,023,612 KB
已提交: 
 1,155,084 KB
最大值: 
 2,038,528 KB
GC 时间: 
ParNew上的      45.212 秒 (4,582收集)
ConcurrentMarkSweep上的       0.620 秒 (20收集)

VisualVM-内存使用情况图:




参数设置及内存消耗和所用时间情况:
线程数,分页数,批量保存数,消耗内存最大值(G),耗时(s)
12,30000,  5000, 1.645, 254.612

Jconsole内存使用情况,垃圾回收次数和时间:
时间: 
2017-04-13 15:09:40
已用: 
   296,974 KB
已提交: 
 1,741,000 KB
最大值: 
 2,038,528 KB
GC 时间: 
ParNew上的      42.666 秒 (3,767收集)
ConcurrentMarkSweep上的       0.177 秒 (15收集)

VisualVM-内存使用情况图:




相对于8个线程,使用时间减少99秒,新生代和老年代的垃圾回收次数和时间减少,但同时内存的最大使用量增大了500M左后,
这是因为线程内更新的有记录,线程数增量,相应的峰值内存增加,内存占用变化较大。

参数设置及内存消耗和所用时间情况:
线程数,分页数,批量保存数,消耗内存最大值(G),耗时(s)
16,30000,  5000, 1.607,187.303

Jconsole内存使用情况,垃圾回收次数和时间:
时间: 
2017-04-13 15:23:08
已用: 
   840,564 KB
已提交: 
 1,770,688 KB
最大值: 
 2,038,528 KB
GC 时间: 
ParNew上的      29.268 秒 (2,560收集)
ConcurrentMarkSweep上的       0.163 秒 (13收集)

VisualVM-内存使用情况图:



相对于12个线程,使用时间减少67秒,新生代和老年代的垃圾回收次数和时间减少,但同时内存的最大使用量没有多大变化,
线程数增量。
小节:
在分页数和批量保存数相同的情况下,线程数越多,所用时间越少,
同时所耗内存最大值变大,新生代和老年代的垃圾回收次数和时间减少;

下面来调分页数量参数:
参数设置及内存消耗和所用时间情况:
线程数,分页数,批量保存数,消耗内存最大值(G),耗时(s)
8,30000,  5000, 1.039, 353.661
Jconsole内存使用情况,垃圾回收次数和时间:
时间: 
2017-04-13 14:50:43
已用: 
 1,023,612 KB
已提交: 
 1,155,084 KB
最大值: 
 2,038,528 KB
GC 时间: 
ParNew上的      45.212 秒 (4,582收集)
ConcurrentMarkSweep上的       0.620 秒 (20收集)
以上面参数配置,内存使用量,耗时作为对比基础


参数设置及内存消耗和所用时间情况:
线程数,分页数,批量保存数,消耗内存最大值(G),耗时(s)
8,20000,  5000, 0.851, 411.734

Jconsole内存使用情况,垃圾回收次数和时间:
时间: 
2017-04-13 15:40:23
已用: 
   202,855 KB
已提交: 
   893,508 KB
最大值: 
 2,038,528 KB
GC 时间: 
ParNew上的      42.290 秒 (4,530收集)
ConcurrentMarkSweep上的       0.236 秒 (23收集)

VisualVM-内存使用情况图:



在8个线程和批量保存数为5000的情况下,分页数量减少10000,内存的峰值减少了188M,
但所耗时间增加了58秒,新生代和老年代的垃圾回收次数和时间增加;

参数设置及内存消耗和所用时间情况:
线程数,分页数,批量保存数,消耗内存最大值(G),耗时(s)
8,10000,  5000, 0.622, 696.168

Jconsole内存使用情况,垃圾回收次数和时间:
时间: 
2017-04-13 16:07:15
已用: 
   398,122 KB
已提交: 
   622,284 KB
最大值: 
 2,038,528 KB
GC 时间: 
ParNew上的      41.930 秒 (4,970收集)
ConcurrentMarkSweep上的       0.301 秒 (29收集)

VisualVM-内存使用情况图:



在线程数和批量保存数相同情况下,
在8个线程和批量保存数为5000的情况下,分页数量减少,内存的峰值减少,
但所耗时间增加,新生代和老年代的垃圾回收次数和时间增加;

再来调批量保存数量:
以上一个参数配置,内存使用量,耗时作为对比基础作为对比基础
参数设置及内存消耗和所用时间情况:
线程数,分页数,批量保存数,消耗内存最大值(G),耗时(s)
8,10000,  2500, 0.474, 664.131

Jconsole内存使用情况,垃圾回收次数和时间:
时间: 
2017-04-13 16:31:18
已用: 
   256,886 KB
已提交: 
   535,884 KB
最大值: 
 2,038,528 KB
GC 时间: 
ParNew上的      38.173 秒 (4,693收集)
ConcurrentMarkSweep上的       0.380 秒 (32收集)

VisualVM-内存使用情况图:



在8个线程,分页数为10000的情况下,批量保存数减少,内存的峰值减少,
但所耗时间增加,新生代和老年代的垃圾回收次数和时间增加;


总结:
在内存充足的情况,线程数量越多,所耗时间越少,新生代和老年代的垃圾回收次数和时间减少,但同时内存的峰值越大,在本次测试中,更新126万数据,内存峰值为1.607G,所耗时间为187.303s;平均每秒处理6737条记录,这个是在硬件酷睿i7,4核处理器,JVM内存2G情况下取得,假设有2个CPU,CPU为8核的,总共有16线程,内存为8G,保守估计,再想同时记录数和批量保存记录数的情况下每秒可以处理的记录数为(2x8x8)/(1x4x2)x6737,约每秒11万,拿相同时间可以处理的记录数来看,187.303s可以处理2057万数据。当然线程不是越多要好,凡是要有一个度,以前看过一篇文章,线程数最好为2xSum(CPU)xCore(CPU);从测试来看当线程数为Sum(CPU)xCore(CPU)的4倍时,性能相当好,不过这个要看具体的场景,仁者见仁智者见智啦;
如果内存不够用的情况,对处理耗时没有要求的话,我们可以减少线程数,分页数和批量保存数;现在有一台服务,奶奶的普通PC,4G的内存,由于服务器上还有其他数据库在跑,最后JVM只有650M的内存可用,由于应用服务为Server模式,默认为内存的1/4,但现在只有650M,由于应用没有时间上的要求,所以取线程数为8,分页数为10000,批量保存数为2500,更新120万的数据,10分钟还是可以接受的,内存峰值还不到500M。


  • 大小: 33.5 KB
  • 大小: 46.8 KB
  • 大小: 37.8 KB
  • 大小: 49.5 KB
  • 大小: 46.7 KB
  • 大小: 53.9 KB
0
0
分享到:
评论

相关推荐

    基于某某平台的数据源迁移oracle -mysql

    基于某某系统平台的数据源迁移 1 一、背景 4 二、环境准备 4 1、软件 4 2、jar包 4 3、web应用 4 三、mysql数据库的安装 4 四、数据库的创建并用客户端软件连接 5 1、创建数据库 5 2、应用 navicat连接mysql 数据库 ...

    大数据迁移实践之路.pdf

    交易 量的攀升导致了后台数据库数据量的激增,从⽽影响了联机程序响应时间,也增加了系统各类资源开销和后续数据分析的处理时间。为保障 系统稳定运⾏,项⽬组从增加系统资源、优化资源配置、优化重点程序和升级系统...

    mongodbeer:将 Beer 示例应用程序从 Couchbase 迁移到 MongoDB

    作为一个有趣的推文的后续,我已经在 MongoDB 实例上从 Couchbase 迁移了 Beer Sample 应用程序。 将我的 Java 从 Couchbase 移动到 MongoDB - Tugdual Grall (@tgrall) [removed][removed] 我只更改了几行代码、...

    大数据迁移实践之路

    交易量的攀升导致了后台数据库数据量的激增,从而影响了联机程序响应时间,也增加了系统各类资源开销和后续数据分析的处理时间。为保障系统稳定运行,项目组从增加系统资源、优化资源配置、优化重点程序和升级系统...

    基于微信小程序的课堂签到系统课程报告参考

    是前后端分离开发,设计模式上采用了mvc的设计模式,令数据层,业务层,视图层可以解耦合,有利于后续的更新维护工作,架构上则是采用了B/S架构,直接从后台调用HTML界面。 相较于传统的名单签到打卡,微信小程序...

    kaggle-Digit-Recognizer:Kaggle入门级比赛-数字识别

    这是V1版本,后续版本迁移到ApacheCN组织的kaggle项目里。这个repo就停止更新了。 目录 简介 kaggle入门题目,训练数据已经处理成向量并与标签一一对应,判断测试数据对应的标签。 项目基本思路 在solutions中存储...

    AccessLook

    4、可以通过导出 XML 数据与其它数据库进行数据迁移。 三、授权方式 本软件的授权方式是免费软件,您可以免费使用并自由传播和使用本软件。 四、特别申明 请勿用作商业用途。在任何情况下,在由于使用或运行本...

    AX DBBuilder 2007(最新多语言版)

    统一的资源文件格式和自定义资源结构功能,以及开放统一的 类接口,可在应用程序中方便调用结构定义,来执行一些数据 库相关操作,如执行数据库自动更新,刷新前端栏位显示、 数据检查等等。...

    宙斯:宙斯运维监控平台,服务端程序

    宙斯监控系统本项目为运维监控服务端程序,后续会继续宙斯监控系统开发。从个人git迁移过来项目中zql查询语言解析解析库: : 在线语句转换工具: : collectd输出到zues的一个插件,通过collected可以实现zues的一些...

    O&O DiskImage Pro电脑系统备份还原软件.rar

    O&O DiskImage Pro 是来自德国的一款方便实用的系统镜像制作工具和自动化Windows数据备份与恢复软件,全新基于文件的备份...这对于后续新老系统的数据迁移非常有帮助,O&O DiskImage 17还可以创建虚拟驱动器的增量和差

    sqldeveloper-21.4.3.x64+jdk1.8

    SQL Developer提供了PL/SQL程序的端到端开发,运行查询工作表的脚本,管理数据库的DBA控制台,报表接口,完整的数据建模的解决方案,并且能够支持将你的第三方数据库迁移至Oracle。 SQL Developer可以连接到任何...

    (重要)AIX command 使用总结.txt

    slibclean //清除处理程序遗留的旧分页信息 smit mkps //建立交换区空间信息 swapon -a //启动所有的分页空间 ##交换区命令end ##查看HACMP, 外部硬盘信息: lscfg -v lscfg -v | grep -E "pdisk|hdisk" //可查看...

    matlab如何删除一个程序代码-CS-Notes:计算机科学学习笔记。移至https://superlish.github.io/

    matlab如何删除一个程序代码 CS-Notes Computer science learning notes. 迁移至 博文访问: 后续更新在. Content 数据结构及算法 设计模式 程序设计编程语言 区块链 分布式系统 reference 数据库 Linux 计算机网络 ...

    File 文件登记簿

     关于此程序的后续开发,主要是打印页面管理,现在嫌麻烦,直接分成传真电报和阅办单两个固定在程序里了,下一步可以继续做非常灵活的打印页面管理,象管理其他内容一样管理打印页面,逐单元格管理打印内容。...

    基于vue+node+socket+vant+mysql实现的在线客服系统.zip

    自1998年首次发布以来,MySQL以其卓越的性能、可靠性和可扩展性,成为全球范围内Web应用程序、企业级解决方案以及其他各种数据处理场景的首选数据库平台之一。 以下是对MySQL数据库的详细介绍: 核心特性与优势 ...

    RoomAsset:一个帮助程序库,可帮助将Room与现有的预填充数据库一起使用[不推荐使用]

    该库为开发人员提供了一种简单的方法,可将其Android应用程序与现有SQLite数据库(可能已预先填充数据)一起发货,并管理其初始创建以及后续版本发布所需的任何升级。 它被实现为Room的扩展,提供了一种将Room与...

    基于maven项目的SSM框架与layu前端框架的代码

    Spring MVC属于Spring Framework的后续产品,已经融合在Spring Web Flow里面,它原生支持的Spring特性,让开发变得非常简单规范。Spring MVC 分离了控制器、模型对象、分派器以及处理程序对象的角色,这种分离让...

    NOCC工程系统集成2标招标文件-技术部分

    10.1 TCC、ACC、集团信息中心、企业运营信息系统既有数据迁移 260 10.2 基础数据核查及录入 261 10.3 后续线路接入要求 261 11 主要设备数量表 261 第三篇 线网通信系统 266 1 线网传输系统 266 1.1 概述 266 1.2 ...

    similarity:SimMetrics 字符串匹配算法的 SQL Server 包装器

    后续版本已迁移到Java。 C#包装器受此启发 提供了支持的字符串模糊匹配函数的。 动机 该项目的动机是在数据分析和数据科学工作中经常需要模糊匹配(近似字符串匹配)算法。 中缺少这些算法。 许多项目没有时间、...

Global site tag (gtag.js) - Google Analytics