【java/线程】基于生产者/队列/消费者模型的表迁移器
【代码下载地址】
https://files.cnblogs.com/files/heyang78/newcpmgrt_20220714ev.rar?t=1657805048
【前作】
https://www.cnblogs.com/heyang78/p/16410831.html
【图解】
【开发理由】
基于生产者/队列/消费者模型的表迁移器比前作解耦程度更高,也不会造成读快写慢而造成的数据堆积。
【关键语句】
收到空消息退出是使DstWriter退出的关键一句,也是SrcReader在退出前的必要步骤,否则会造成DstWriter处于等待而无法退出。
【代码】
提供连接的DBUtil类
package com.hy.lab.newcpmgrt; import java.sql.Connection; import java.sql.DriverManager; /** * 连Oracle数据库,提供连接的工具类 */ class DbUtil { //-- 以下为连接Oracle数据库的四大参数 private static final String DRIVER = "oracle.jdbc.OracleDriver"; private static final String URL = "jdbc:oracle:thin:@127.0.0.1:1521/orcl"; private static final String USER = "luna"; private static final String PSWD = "1234"; public static Connection getConn() throws Exception{ Class.forName(DRIVER); Connection conn = DriverManager.getConnection(URL, USER, PSWD); return conn; } }
表迁移器
package com.hy.lab.newcpmgrt; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.List; import java.util.concurrent.CountDownLatch; /** * 基于消费者队列生产者模型的迁移器 */ class Migrater { // 成员变量:来源连接 private Connection srcConn; // 成员变量:来源表 private String srcTb; // 成员变量:去向连接 private Connection dstConn; // 成员变量:去向表 private String dstTb; // 批处理大小,可灵活调整 private final int BATCH_SIZE=10000; // 内含写去向表工人数量,写完后srcReader会关闭来源去向连接 private CountDownLatch cdl; /** * 构造函数 * * @param srcConn 来源连接 * @param srcTb 来源表 * @param dstConn 去向连接 * @param dstTb 去向表 */ public Migrater(Connection srcConn, String srcTb, Connection dstConn, String dstTb) { this.srcConn=srcConn; this.srcTb=srcTb; this.dstConn=dstConn; this.dstTb=dstTb; // 计算写去向表工人数量 int workerCnt=(int)Math.ceil((double)getSrcCount()/(double)BATCH_SIZE); cdl=new CountDownLatch(workerCnt); } private int getSrcCount(){ final String sql=String.format("select count(*)from %s",srcTb); try(PreparedStatement ps=srcConn.prepareStatement(sql); ResultSet rs=ps.executeQuery()){ while(rs.next()){ return rs.getInt(1); } return -1; }catch(Exception e){ return -2; } } /** * 迁移 */ public void migrate(){ DataQueue<List<String[]>> dq=new DataQueue<>(5); SrcReader readerThread=new SrcReader(this,dq,BATCH_SIZE); readerThread.start(); DstWriter writeThread=new DstWriter(this,dq,readerThread); writeThread.start(); } public String getQuerySql(){ return String.format("select * from %s",this.srcTb); } public void transfer(List<String[]> rowList) throws Exception{ Connection conn=this.dstConn; conn.setAutoCommit(false); final String insertSql=String.format("insert into %s(id,f1,f2,f3,f4,f5,f6,f7,f8,f9) values(?,?,?,?,?,?,?,?,?,?)",this.dstTb); try(PreparedStatement pstmt=this.srcConn.prepareStatement(insertSql)){ for(String[] arr:rowList){ for(int i=0;i<arr.length;i++){ String cellValue=arr[i]; pstmt.setString(i+1,cellValue); } pstmt.addBatch(); } pstmt.executeBatch(); conn.commit(); pstmt.clearBatch(); }catch(Exception e){ e.printStackTrace(); } } public Connection getSrcConn() { return srcConn; } public String getSrcTb() { return srcTb; } public Connection getDstConn() { return dstConn; } public String getDstTb() { return dstTb; } public CountDownLatch getCdl() { return cdl; } public static void main(String[] args){ try{ Connection srcConn= DbUtil.getConn(); Connection dstConn= DbUtil.getConn(); // 设定源表存在有数据,去向表存在无数据,两表表结构一致 Migrater mgrt=new Migrater(srcConn,"emp625_from",dstConn,"emp625_to"); mgrt.migrate(); System.out.println("迁移开始"); }catch(Exception ex){ ex.printStackTrace(); } } }
数据队列类
package com.hy.lab.newcpmgrt; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; /** * SrcReader和DstWriter共享的数据队列 * @param <T> 数据队列类型 */ class DataQueue<T> { private BlockingQueue<T> queue; private int limit; public DataQueue(int limit){ this.limit=limit; queue=new ArrayBlockingQueue<>(limit); } public synchronized void put(T data) throws InterruptedException{ while(queue.size()==limit){ // 此处while/if皆可 wait(); } queue.add(data); this.notifyAll(); } public synchronized T take() throws InterruptedException{ while(queue.isEmpty()){ // 此处while/if皆可 wait(); } T data=queue.poll(); this.notifyAll(); return data; } }
源端数据读取器
package com.hy.lab.newcpmgrt; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; /** * Source Reader * 从源端读取数据的生产者 */ class SrcReader extends Thread{ Migrater mgrt; DataQueue<List<String[]>> queue; int batchSize; public SrcReader(Migrater mgrt, DataQueue<List<String[]>> queue, int batchSize){ this.mgrt=mgrt; this.queue=queue; this.batchSize=batchSize; } public void run(){ final String sql=mgrt.getQuerySql(); try(PreparedStatement pstmt=mgrt.getSrcConn().prepareStatement(sql); ResultSet rs=pstmt.executeQuery();){ final int colCnt=10;// 假定列数为10,实际上要用Metadata取 List<String[]> rowList=new ArrayList<>(batchSize); int count=0; while(rs.next()){ String[] arr=new String[colCnt]; for(int i=0;i<colCnt;i++){ arr[i]=rs.getString(i+1); } rowList.add(arr); count++; if(count==batchSize){ queue.put(rowList); count=0; rowList=new ArrayList<>(batchSize); } } rs.close(); if(rowList.size()>0){ queue.put(rowList); } mgrt.getCdl().await(10, TimeUnit.SECONDS); List<String[]> endLs=new ArrayList<>(); endLs.add(null); queue.put(endLs); System.out.println("SrcReader任务完成"); mgrt.getSrcConn().close(); mgrt.getDstConn().close(); }catch(Exception ex){ ex.printStackTrace(); } } }
目标端数据写入器
package com.hy.lab.newcpmgrt; import java.util.List; /** * Destination Writer * 用于启动工人向目标端写数据 */ class DstWriter extends Thread{ Migrater mgrt; DataQueue<List<String[]>> queue; SrcReader srcReader; public DstWriter(Migrater mgrt, DataQueue<List<String[]>> queue, SrcReader srcReader){ this.mgrt=mgrt; this.queue=queue; this.srcReader=srcReader; } public void run(){ try{ while(true){ List<String[]> rowList=queue.take(); String[] arr=rowList.get(0); if(arr!=null) { new Thread(new Worker(mgrt, rowList)).start(); Thread.sleep(10); System.out.println("DstWriter运作中"); }else{ break; } } System.out.println("DstWriter任务完成"); }catch(Exception ex){ ex.printStackTrace(); } } }
负责实际写入的工人
package com.hy.lab.newcpmgrt; import java.util.List; import java.util.concurrent.CountDownLatch; /** * 实际向目标端写数据的工人 * 由DstWriter产生 */ class Worker implements Runnable{ Migrater mgrt; List<String[]> rowList; CountDownLatch cdl; public Worker(Migrater mgrt, List<String[]> rowList){ this.mgrt=mgrt; this.rowList=rowList; this.cdl=mgrt.getCdl(); } @Override public void run() { try{ mgrt.transfer(rowList); rowList=null; }catch(Exception ex){ ex.printStackTrace(); }finally { cdl.countDown(); } } }
【输出】
迁移开始
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
DstWriter运作中
SrcReader任务完成
DstWriter任务完成
Process finished with exit code 0
【数据库情况】
luna@ORCL>truncate table emp625_to; 表被截断。 luna@ORCL>select count(*) from emp625_to; COUNT(*) ---------- 500012 已选择 1 行。 luna@ORCL>truncate table emp625_to; 表被截断。 luna@ORCL>select count(*) from emp625_to; COUNT(*) ---------- 500012 已选择 1 行。
【建表及充值语句】
create table emp625_from( id number(10), f1 nvarchar2(10), f2 nvarchar2(10), f3 nvarchar2(10), f4 nvarchar2(10), f5 nvarchar2(10), f6 nvarchar2(10), f7 nvarchar2(10), f8 nvarchar2(10), f9 nvarchar2(10), primary key(id) ); create table emp625_to( id number(10), f1 nvarchar2(10), f2 nvarchar2(10), f3 nvarchar2(10), f4 nvarchar2(10), f5 nvarchar2(10), f6 nvarchar2(10), f7 nvarchar2(10), f8 nvarchar2(10), f9 nvarchar2(10), primary key(id) ); insert into emp625_from select rownum, dbms_random.string('*',dbms_random.value(1,10)), dbms_random.string('*',dbms_random.value(1,10)), dbms_random.string('*',dbms_random.value(1,10)), dbms_random.string('*',dbms_random.value(1,10)), dbms_random.string('*',dbms_random.value(1,10)), dbms_random.string('*',dbms_random.value(1,10)), dbms_random.string('*',dbms_random.value(1,10)), dbms_random.string('*',dbms_random.value(1,10)), dbms_random.string('*',dbms_random.value(1,10)) from dual connect by level<500013;
此作完成,又进步一点。
END