最近在做一个银行的生产数据脱敏系统,今天写代码时遇到了一个“瓶颈”,脱敏系统需要将生产环境上Infoxmix里的数据原封不动的Copy到另一台 Oracle数据库服务器上,然后对Copy后的数据作些漂白处理。为了将人为干预的因素降到最低,在系统设计时采用Java代码对数据作Copy,思路

    首 先在代码与生产库间建立一个Connection,将读取到的数据放在ResultSet对象,然后再与开发库建立一个Connection。从 ResultSet取出数据后通过TestConnection插入到开发库,以此来实现Copy。代码写完后运行程序,速度太慢了,一秒钟只能Copy 一千条数据,生产库上有上亿条数据,按照这个速度同步完要到猴年马月呀,用PreparedStatement批处理速度也没有提交多少。我想能不能用多 线程处理,多个人干活总比一个人干活速度要快。
    假设生产库有1万条数据,我开5个线程,每个线程分2000条数据,同时向开发库里插数据,Oracle支持高并发这样的话速度至少会提高好多倍,按照这 个思路重新进行了编码,批处理设置为1万条一提交,统计插入数量的变量使用 java.util.concurrent.atomic.AtomicLong,程序一运行,传输速度飞快CPU利用率在70%~90%,现在一秒钟可 以拷贝50万条记录,没过几分钟上亿条数据一条不落地全部Copy到目标库。

在查询的时候我用了如下语句

  1. String queryStr = "SELECT * FROM xx";
  2. ResultSet coreRs = PreparedStatement.executeQuery(queryStr);

实习生问如果xx表里有上千万条记录,你全部查询出来放到ResultSet, 那内存不溢出了么?Java在设计的时候已经考虑到这个问题了,并没有查询出所有的数据,而是只查询了一部分数据放到ResultSet,数据“用完”它 会自动查询下一批数据,你可以用setFetchSize(int rows)方法设置一个建议值给ResultSet,告诉它每次从数据库Fetch多少条数据。但我不赞成,因为JDBC驱动会根据实际情况自动调整 Fetch的数量。另外性能也与网线的带宽有直接的关系。
相关代码

package com.dlbank.domain;  
   
 import java.sql.Connection;  
 import java.sql.PreparedStatement;  
 import java.sql.ResultSet;  
 import java.sql.Statement;  
 import java.util.List;  
 import java.util.concurrent.atomic.AtomicLong;  
   
 import org.apache.log4j.Logger;  
   
 /**
  * <p>
  * title: 数据同步类
  * </p>
  * <p>
  * Description: 该类用于将生产核心库数据同步到开发库
  * </p>
  * 
  * @author Tank Zhang
  */  
 public class CoreDataSyncImpl implements CoreDataSync {  
       
     private List<String> coreTBNames; // 要同步的核心库表名
     private ConnectionFactory connectionFactory;  
     private Logger log = Logger.getLogger(getClass());  
       
     private AtomicLong currentSynCount = new AtomicLong(0L); // 当前已同步的条数
       
     private int syncThreadNum;  // 同步的线程数
   
     @Override  
     public void syncData(int businessType) throws Exception {  
           
         for (String tmpTBName : coreTBNames) {  
             log.info("开始同步核心库" + tmpTBName + "表数据");  
             // 获得核心库连接
             Connection coreConnection = connectionFactory.getDMSConnection(4);  
             Statement coreStmt = coreConnection.createStatement();  
             // 为每个线程分配结果集
             ResultSet coreRs = coreStmt.executeQuery("SELECT count(*) FROM "+tmpTBName);  
             coreRs.next();  
             // 总共处理的数量
             long totalNum = coreRs.getLong(1);  
             // 每个线程处理的数量
             long ownerRecordNum =(long) Math.ceil((totalNum / syncThreadNum));   
             log.info("共需要同步的数据量:"+totalNum);  
             log.info("同步线程数量:"+syncThreadNum);  
             log.info("每个线程可处理的数量:"+ownerRecordNum);  
             // 开启五个线程向目标库同步数据
             for(int i=0; i < syncThreadNum; i ++){  
                 StringBuilder sqlBuilder = new StringBuilder();  
                 // 拼装后SQL示例
                 // Select * From dms_core_ds Where id between 1 And 657398
                 // Select * From dms_core_ds Where id between 657399 And
     // 1314796
                 // Select * From dms_core_ds Where id between 1314797 And
     // 1972194
                 // Select * From dms_core_ds Where id between 1972195 And
     // 2629592
                 // Select * From dms_core_ds Where id between 2629593 And
     // 3286990
                 // ..
                 sqlBuilder.append("Select * From ").append(tmpTBName)  
                         .append(" Where id between " ).append(i * ownerRecordNum +1)  
                         .append( " And ")  
                         .append((i * ownerRecordNum + ownerRecordNum));  
                 Thread workThread = new Thread(  
                         new WorkerHandler(sqlBuilder.toString(),businessType,tmpTBName));  
                 workThread.setName("SyncThread-"+i);  
                 workThread.start();  
             }  
             while (currentSynCount.get() < totalNum);  
             // 休眠一会儿让数据库有机会commit剩余的批处理(只针对JUnit单元测试,因为单元测试完成后会关闭虚拟器,使线程里的代码没有机会作提交操作);
             // Thread.sleep(1000 * 3);
             log.info( "核心库"+tmpTBName+"表数据同步完成,共同步了" + currentSynCount.get() + "条数据");  
         }  
     }// end for loop
       
     public void setCoreTBNames(List<String> coreTBNames) {  
         this.coreTBNames = coreTBNames;  
     }  
   
     public void setConnectionFactory(ConnectionFactory connectionFactory) {  
         this.connectionFactory = connectionFactory;  
     }  
       
     public void setSyncThreadNum(int syncThreadNum) {  
         this.syncThreadNum = syncThreadNum;  
     }  
       
     // 数据同步线程
     final class WorkerHandler implements Runnable {  
         ResultSet coreRs;  
         String queryStr;  
         int businessType;  
         String targetTBName;  
         public WorkerHandler(String queryStr,int businessType,String targetTBName) {  
             this.queryStr = queryStr;  
             this.businessType = businessType;  
             this.targetTBName = targetTBName;  
         }  
         @Override  
         public void run() {  
             try {  
                 // 开始同步
                 launchSyncData();  
             } catch(Exception e){  
                 log.error(e);  
                 e.printStackTrace();  
             }  
         }  
         // 同步数据方法
         void launchSyncData() throws Exception{  
             // 获得核心库连接
             Connection coreConnection = connectionFactory.getDMSConnection(4);  
             Statement coreStmt = coreConnection.createStatement();  
             // 获得目标库连接
             Connection targetConn = connectionFactory.getDMSConnection(businessType);  
             targetConn.setAutoCommit(false);// 设置手动提交
             PreparedStatement targetPstmt = targetConn.prepareStatement("INSERT INTO " + targetTBName+" VALUES (?,?,?,?,?)");  
             ResultSet coreRs = coreStmt.executeQuery(queryStr);  
             log.info(Thread.currentThread().getName()+"'s Query SQL::"+queryStr);  
             int batchCounter = 0; // 累加的批处理数量
             while (coreRs.next()) {  
                 targetPstmt.setString(1, coreRs.getString(2));  
                 targetPstmt.setString(2, coreRs.getString(3));  
                 targetPstmt.setString(3, coreRs.getString(4));  
                 targetPstmt.setString(4, coreRs.getString(5));  
                 targetPstmt.setString(5, coreRs.getString(6));  
                 targetPstmt.addBatch();  
                 batchCounter++;  
                 currentSynCount.incrementAndGet();// 递增
                 if (batchCounter % 10000 == 0) { // 1万条数据一提交
                     targetPstmt.executeBatch();  
                     targetPstmt.clearBatch();  
                     targetConn.commit();  
                 }  
             }  
             // 提交剩余的批处理
             targetPstmt.executeBatch();  
             targetPstmt.clearBatch();  
             targetConn.commit();  
             // 释放连接
             connectionFactory.release(targetConn, targetPstmt,coreRs);  
         }  
     }  
 }

java实现高性能的数据同步的更多相关文章

  1. Java多线程学习笔记——从Java JVM对多线程数据同步的一些理解

       我们知道在多线程编程中,我们很大的一部分内容是为了解决线程间的资源同步问题和线程间共同协作解决问题.线程间的同步,通俗我们理解为僧多粥少,在粥有限情况下,我们怎么去防止大家有秩序的喝到粥,不至于 ...

  2. java线程基础巩固---数据同步引入并结合jconsole,jstack以及汇编指令认识synchronized关键字

    对于多线程编程而言其实老生成谈的就是数据同步问题,接下来就会开始接触这块的东东,比较麻烦,但是也是非常重要,所以按部就班的一点点去专研它,下面开始. 数据同步引入: 这里用之前写过的银行叫号的功能做为 ...

  3. Java线程安全与数据同步

    import java.util.HashMap; import java.util.concurrent.TimeUnit; public class Test { public static vo ...

  4. java读写锁实现数据同步访问

    锁机制最大的改进之一就是ReadWriteLock接口和它的唯一实现类ReentrantReadWriteLock.这个类有两个锁,一个是读操作锁,另一个是写操作锁.使用读操作锁时可以允许多个线程同时 ...

  5. java 线程数据同步

    java 线程数据同步 由买票实例 //java线程实例 //线程数据同步 //卖票问题 //避免重复卖票 //线程 class xc1 implements Runnable{ //定义为静态,可以 ...

  6. Java多线程初学者指南(9):为什么要进行数据同步

    Java中的变量分为两类:局部变量和类变量.局部变量是指在方法内定义的变量,如在run方法中定义的变量.对于这些变量来说,并不存在线程之间共享的问题.因此,它们不需要进行数据同步.类变量是在类中定义的 ...

  7. JAVA通过Gearman实现MySQL到Redis的数据同步(异步复制)

    MySQL到Redis数据复制方案 无论MySQL还是Redis,自身都带有数据同步的机制,像比较常用的 MySQL的Master/Slave模式 ,就是由Slave端分析Master的binlog来 ...

  8. java数据同步陷阱

    并发,我的理解就是同时运行多个程序.同时,难以避免的就是数据的同步问题,如果数据同步问题处理不好就很容易造成程序出现bug,当然,对于其造成的危害,不加详述. 首先,来看一个简单的例子,当然,这个例子 ...

  9. java——多线程的实现方式、三种办法解决线程赛跑、多线程数据同步(synchronized)、死锁

    多线程的实现方式:demo1.demo2 demo1:继承Thread类,重写run()方法 package thread_test; public class ThreadDemo1 extends ...

随机推荐

  1. Using an LPC-Link2 as an LPC4370 evaluation board

    https://www.lpcware.com/content/faq/lpcxpresso/using-lpclink2-as-lpc4370-eval As well as being a sta ...

  2. 手把手教你使用C#操作SQLite数据库,新建数据库,创建表,插入,查询,删除,运算符,like

    目录: 一.新建项目,添加引用 二.创建数据库 三.创建表 四.插入数据  五.查询数据  六.删除数据  七.运算符 八.like语句 我的环境配置:windows 64,VS,SQLite(点击下 ...

  3. 数据库数据格式化之Kettle Spoon

    前言 现在的数据库种类越来越多,数据库备份的格式也越来越复杂,所以数据格式化一直是一个老生常谈的问题.据库备份文件格式那么多,既有SQL的,也有BAK的,还有TXT的等.数据库种类也有很多,MySQL ...

  4. JS删除String里某个字符的方法

    关于JS删除String里的字符的方法,一般使用replace()方法.但是这个方法只会删除一次,如果需要将string里的所以字符都删除就要用到正则. 1 2 3 4 var str = " ...

  5. Golang 处理 Json(一):编码

    JSON 是一种数据格式描述语言.以 key 和 value 构成的哈系结构,类似 Javascript 中的对象,python 中的字典.通常 json 格式的 key 是字符串,其值可以是任意类型 ...

  6. 十步轻松搞定IIS+PHP环境搭建

    突然心血来潮想着自己一直使用Apache+php的模式,想要了解一下IIS+php的模式.说起来也算是九曲十八弯吧! 第一部分:以ISAPI.dll 扩展的形式 结果按照资料上面说的我就是找不到一个i ...

  7. The Win32 Rundll and Rundll32 Interface Related Topics

    The Win32 Rundll and Rundll32 Interface Related Topics Microsoft Knowledge Base Article Q164787 Appl ...

  8. msgpack的数据序列和还原

    msgpack的数据序列和还原 msgpack不仅可以序列一些常规的数据类型的数据,比如:string.datetime.integer...... 还能序列olevariant.stream 这就非 ...

  9. sqlite数据库实现字符串查找的方法(instr,substring,charindex替代方案)

    sqlite数据库是一款轻型的数据库,是遵守ACID的关联式数据库管理系统,资源占用低,执行效率高,可以跨平台使用,已被广泛使用.作为一款轻量级的数据库,功能自然会有所欠缺,比如数据库加密,用户权限设 ...

  10. 迭代dict的key和value

    我们了解了如何迭代 dict 的key和value,那么,在一个 for 循环中,能否同时迭代 key和value?答案是肯定的. 首先,我们看看 dict 对象的 items() 方法返回的值: & ...