本文介绍了如何用多U程来构量 Batch 框架Q将大量的数据迁Ud IBM DB2 Content Manager 8.3 中。通过本文的学习,读者可以了解如何通过使用多线E调?IBM DB2 Content Manager API 构徏的框架来启动Q暂停,恢复Q停止,攄{操作?/p>
在用 API 导入大量数据的过E中Q如果没有框架很难有效的Ҏ个过E控Ӟ仅仅通过日志来分析解决问题L很浪Ҏ_q且效率不太理想?/p>
本文的内Ҏ在了如何使用多线E和配置文g来构?Batch 框架来处理大数量导入的问题?/p>
随着 IBM DB2 Content ManagerQ简U?IBM CMQ品的不断成熟Q越来越多的内容理pȝ需要迁Ud IBM CM 中来Q这些需要迁Uȝ数据通常首先把结构化的内容导到文本文件中Q与之相对应的图像和 pdf 文g通常攑֜对应的文件夹中,囑փ?pdf 对应的文件夹路径也通常存放在文本文件中Q然后迁Uȝ序遍历文本文Ӟ把对应的 Item q移?IBM CM 中。这些需要迁Uȝ数据通常都有几百 GQ如何有效的控制q移q程是一个很大的挑战Q因此我们必LZ个轻量?batch 处理框架来控制整个数据的q移周期Q记录处理过E中的错误,保证数据的一致性?/p>
同时Q在?API 导入数据的过E中Q被导入数据L千边万化Q无效的映射导入数据?DB2 Content Manager 的项Q导致工作变得复杂,同时使的设计和代码冗余,q且佉K用,l护和扩展步ؓ艰难?/p>
Z克服所提到的挑战,q个 batch 框架必须要有以下功能Q?
要框架有交互性,我们必须有三个个U程Q客LU程Q服务端U程Q工作线E。客LU程负责发出工作指oQ服务端U程接受q些指oq调用工作线E来做实际的工作。对于客L和服务器交互Q在没有 web 服务器支持的情况下,我们可以采用一U古老但是很有效的做法:socket ~程?Java socket 对象?accept Ҏ会一直阻塞直到客L有程序输入,当客L有新的命令输入的时候,服务器端?socket 中读出命令,然后执行命o。下面是CZE序QClient.java 代表客户端程序,Server.java 代表服务器端E序QWorker.java 代表工作E序 ,Config.java 代表pȝ中一些参数配|?/p>
清单 1. 客户端程?/strong>
package com.ibm.batch.sample; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.Socket; import org.apache.log4j.Logger; public class Client { private Config config = null; public void setConfig(Config config) { this.config = config; } private Logger logger = Logger.getLogger(Client.class); public void sendCommand(String command) { Socket socket = null; OutputStream out = null; BufferedWriter writer = null; try { // establish the connection with server. socket = new Socket(config.getHost(), config.getSocketPort()); out = socket.getOutputStream(); writer = new BufferedWriter(new OutputStreamWriter(out)); // send the command to server writer.write(command); writer.flush(); } catch (IOException e) { logger.error(e.getMessage(), e); throw new RuntimeException(e); } } } |
package com.ibm.batch.sample; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; import com.ibm.batch.sample.util.ResourceUtils; public class Server { private Config config = null; private boolean processing = true; private Worker worker = null; public void setConfig(Config config) { this.config = config; } public static void main(String[] args) { Server server = new Server(); // create the work thread Worker worker = server.createWorker(args); worker.start(); server.receiveAndExecuteCommand(); } private Worker createWorker(String[] args) { Worker worker = new Worker(); this.worker = worker; return worker; } /** * receive the command from client and execute the command. the method is * keeping running until client send the 'stop' command. * * @throws Exception */ public void receiveAndExecuteCommand() { ServerSocket serverSocket = buildSocketConnection(); // loop until client send 'stop' command while (processing) { Socket socket = null; try { socket = serverSocket.accept(); String commandLine = readCommandFromSocket(socket); executeCommand(commandLine); } catch (Exception e) { throw new RuntimeException(e); } finally { ResourceUtils.closeSocket(socket); } } } private void executeCommand(String commandLine) { // TODO Auto-generated method stub } /** * read the command from the socket * * @param socket * @return */ private String readCommandFromSocket(Socket socket) { InputStream in = null; BufferedReader bufferedReader = null; String commandLine = null; try { in = socket.getInputStream(); bufferedReader = new BufferedReader(new InputStreamReader(in)); commandLine = bufferedReader.readLine(); } catch (IOException e) { throw new RuntimeException(e); } finally { ResourceUtils.closeInputStream(in); ResourceUtils.closeReader(bufferedReader); } return commandLine; } /** * build the socket. * * @return */ private ServerSocket buildSocketConnection() { // prepare the socket for client to connect. ServerSocket serverSocket; try { serverSocket = new ServerSocket(config.getSocketPort()); } catch (java.net.BindException e1) { throw new RuntimeException("Socket port already in use.", e1); } catch (IOException ioe) { throw new RuntimeException(ioe); } return serverSocket; } } |
package com.ibm.batch.sample; import org.apache.log4j.Logger; public class Worker extends Thread { Logger logger = Logger.getLogger(Worker.class); /** * the main method for create item function. */ public void run() { createItem(); } /** * do the real job */ private void createItem() { } } |
大数量的数据q移一般是在周末或者晚上进行,但是如果客户的历史数据太大,在周末或者晚上数据可能处理不完,Z不媄响生产环境的性能Q我们必能够在客户的工作时间暂~处理或者降低处理的频率Q把 cpu {资源让l客L序,也就是说处理U程 worker 的工作可?suspend 或?slowdow 。ؓ了让 worker U程知道需?suspend 当前处理Q我们可以在 worker 内部讄一个布变?isSuspendQ当E序在@环创?CM item 的时候,我们每次都判断一下这个布变?isSuspendQ当其ؓ ture 的时候,E序p用线E的 wait Ҏ中断当前U程的处理,wait Ҏq可以接受一个以微秒为单位的旉参数Q当旉到达 wait 指定的时间的时候,E序l箋创徏 CM Item 。ؓ了多U程之间的变量可见性,我们必须?worker ?isSuspend 变量?suspendTime 讄?volatile 。同理我们设|一个布变?isSlowdown 以及 slowdowTime 。示例程序如下:
package com.ibm.batch.sample; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import org.apache.log4j.Logger; import com.ibm.batch.sample.util.ResourceUtils; public class Worker extends Thread { Logger logger = Logger.getLogger(Worker.class); private volatile boolean isSlowdown = false; private volatile Double slowdownTime; private volatile boolean isSuspend; private volatile Double suspendTime; public void setSlowdown(boolean isSlowdown) { this.isSlowdown = isSlowdown; } public void setSlowdownTime(Double slowdownTime) { this.slowdownTime = slowdownTime; } public void setSuspend(boolean isSuspend) { this.isSuspend = isSuspend; } public void setSuspendTime(Double suspendTime) { this.suspendTime = suspendTime; } public boolean isSlowdown() { return isSlowdown; } public Double getSlowdownTime() { return slowdownTime; } public boolean isSuspend() { return isSuspend; } public Double getSuspendTime() { return suspendTime; } protected Object semaphore = new Object(); private Config config; public void setConfig(Config config) { this.config = config; } |
/** * the main method for create item function. */ public void run() { BufferedReader reader = null; try { reader = getFileReader(); String oneLine = null; while ((oneLine = reader.readLine()) != null) { if (isSlowdown()) { sleep4GivenTime(); } if (isSuspend()) { suspend4SomeTime(); } createItem(oneLine); } } catch (Exception e) { throw new RuntimeException(e); } finally { ResourceUtils.closeReader(reader); } } /** * current thread sleep for some time,the unit is minute. */ protected void sleep4GivenTime() { try { Thread.sleep((long) (slowdownTime.doubleValue() * 1000)); } catch (InterruptedException e) { // do nothing } } |
/** * suspend working for given time. */ protected void suspend4SomeTime() { synchronized (semaphore) { try { Double suspendTime = getSuspendTime(); if (suspendTime != null) { double suspendTimeDouble = suspendTime.doubleValue() * 60 * 1000; semaphore.wait((long) suspendTimeDouble); } else { semaphore.wait(); } } catch (InterruptedException e) { // tell user that the processing started logger.info("suspend is over,system is continue processing ."); } } } /** * do the real job * * @throws Exception */ private void createItem(String oneLine) throws Exception { } private BufferedReader getFileReader() throws FileNotFoundException { String fileName = config.getFileName(); File processingFile = new File(fileName); BufferedReader reader = new BufferedReader(new FileReader( processingFile)); return reader; } } |
在程序暂停处理以后,我们可以提前l止 suspendQ让框架l箋处理Q也是框架必须?resume 功能。我们调?Worker.java 对象上的 notify Ҏ来实现这个功能,CZ如下Q?/p>
清单 7.Resume
public class Worker extends Thread { /** * resume the working. */ public void continueWorking() { cleanSuspend(); synchronized (semaphore) { semaphore.notify(); } } } |
有时候用户因Z些原因(例如修改配置文gQ想停止E序的执行,所以框架必L stop 的功能,但是 stop 的时候我们必L意记录程序处理到的行敎ͼq样客户再开始执行的时候能够从上次执行的断点l执行,也就是框架具备了 re-start 功能Q这?batch E序必须具备的一U很重要的功能,re-start 功能有多U实现方法,我们q里采取一U简单的ҎQ在 stop 的时候,把当前处理的记录C个文本文件中去,下次启动的时候从上次最后处理的对象开始进行处理。所以我们在 Worker.java 中增加一?keepProcessing 布尔变量Q在循环创徏 CM Item 的时?, 我们每次都判断一下这个值是否ؓ trueQ如果ؓ false 的话Q我们就停止循环处理Q在 Worker.java 中还要增加一?moveReaderToLastProcess ҎQ把 reader 重新定向Cơ处理点?/p>
清单 8. 停止和重?/strong>
public class Worker extends Thread { private volatile boolean keepProcessing; public boolean isKeepProcessing() { return keepProcessing; } public void setKeepProcessing(boolean keepProcessing) { this.keepProcessing = keepProcessing; } /** * the main method for create item function. */ public void run() { BufferedReader reader = null; try { long lastProcessedRow = config.getLastProcessedRow(); reader = moveReaderToLastProcess(lastProcessedRow); String oneLine = null; connectToCM(); while (((oneLine = reader.readLine()) != null) && isKeepProcessing()) { if (isSlowdown()) { sleep4GivenTime(); } if (isSuspend()) { suspend4SomeTime(); } createItem(oneLine); lastProcessedRow++; } logCurrentProcessingLine(lastProcessedRow); } catch (Exception e) { throw new RuntimeException(e); } finally { ResourceUtils.closeReader(reader); } } private void logCurrentProcessingLine(long lastProcessedRow) { config.setLastProcessedRow(lastProcessedRow); } /** * move current reader position to last process postion * @return * @throws IOException */ private BufferedReader moveReaderToLastProcess(long lastProcessedRow) throws IOException { // get the file reader BufferedReader reader = getFileReader(); // move the reader to the start row -1. int count = 0; while (count < lastProcessedRow-1) { reader.readLine(); count++; } return reader; } } |
刚才我们调用?createItem Ҏ是直接抛出异常的Q但是这L处理实际上是错误的,因ؓ?batch 处理q程中,我们不希望在处理某一?item 出错D剩余?item 不再处理Q所以我们在 catch 里面对异常进行分cd理,我们 catch 住非查异常(runtime exceptionQ,通常非检查异常是不可以恢复的Q所以我们直接抛出,让程序结束处理。对于其余的异常Q我们只是在日志中记录下来,q不抛出。在全部处理l束以后Q用户可以检查日志来q行相应的处理。示例代码如下:
public class Worker extends Thread { /** * do the real job * * @throws Exception */ private void createItem(String oneLine) throws Exception { try { //create the item from one line }catch (RuntimeException e) { throw e; }catch (Exception e) { logger.error(e.getMessage(),e); } } } |
下面的内Ҏ在了如何使用配置文g来处理导入的问题?/p>
通过调用和运?API 来处理数据的导入Q我们首先定义一个基本信息的配置文gQ用来制定连接的信息Q其他配|文件的目录Q工作的目录{有兛_入需要的参数。然后定义导入数据和 DB2 Content Manager 的项的映配|文件。配|文件定义结束后Q我们就可以调用QPQ来启动相应的导入流E,在程序运行过E中Q可以动态的更改配置Q从而有效的处理导入的Q务?/p>
在开发过E中Q您可以灉|地定义各U配|文件以便实现多U导入规则,同时在程序运行中q行数据校验Q以防止冗余和非法数据被错误导入?/p>
下面的一些配|和代码CZQ以此介l了如何定义配置文gQ然后管?API 来完成导入的d?/p>
定义基本信息配置文gQ在该文件中Q须先设?IBM DB2 Content Manager 的一些连接参敎ͼ 如:
contentManagerDatabase=iCMnlsdb // 定义调用的数据库名字 contentManagerUsername=iCMadmin // 定义用户? contentManagerPassword= password // 定义q接密码 contentManagerSchema=ICMADMIN // 定义具体?schema |
您可以在代码中用以上参数来实现对 IBM DB2 Content Manager 的连接,代码CZQ?/p>
DKDatastoreICM dsICM = new DKDatastoreICM(); // 创徏q接 dsICM.connect("iCMnlsdb", "iCMadmin", "password", "SCHEMA=ICMADMIN"); |
q需指定哪个文g夹存放映文Ӟ以及需导入的数据文Ӟ如:
mappingFilePath=config/rapid/mapping // 映射文g路径 dataFileFolder=config/rapid/data // 数据文g路径 |
也可定义一些参数来增强该导入的程控制Q如Q?/p>
runPhase=2 // 指定是第二阶D导入,在导入时需更新已有的数?/pre> |
定义映射文gQ该配置文g主要用于用h要导入的数据映射?IBM DB2 Content Manager ?Item Type 中,您可自由定制该文Ӟ使用户遵循您定义的规范顺利完成数据迁UR如Q?/p>
C001.del=c01 C002.del=c01 |
该定义中 C001.del ?C002.del 是需要导入的数据文gQc01 是对应的 Item Type 名字。这U定义方法可实现多个数据文件导入同一?Item Type 中?/p>
具体的对应关pd下:
position=1|name=COMPANYNAME position=2|name=COMPANYID position=3|name=INPUTVALUE position=-1|name=SPECIALVALUE|value=C1 |
q个映射关系反映了数据文件中列数?Item Type ?attribute 的关p,如第一列在 Item Type 中代表了名字?COMPANYNAME ?attribute 。您也可定义一些特D规则,如将 position 设ؓ负数Q以便反映该列是一个特D的 attribute, 它的值是固定的?比如?position 设ؓ -1 Ӟ名ؓ SPECIALVALUE ?attribute 的值L?C1 ?/p>
若您惛_现将一个数据文件导入多?Item Type 中,可在数据文g中加入一个特D列Q在映射文g中指定该列的列数Q以及当该列的值和多种 Item Type 的映关pR如Q?/p>
C003.del(position:3) |
q样QC003.del ׃是单一的对应一?Item TypeQ而是先去取第三列 INPUTVALUE 的|再去对应表中查找到关联的 Item Type 。该对应表可设成Q?/p>
Value1=c01 Value2=c02 |
若第三列 INPUTDOCID 的gؓ Value1 Ӟ其对应的 Item Type ?c01Q同L当gؓ Value2 Ӟ会将该行数据导入?c02 ?Item Type 中?/p>
调用 API 完成操作的代码示例:在编写代码过E中Q需要调?DB2 Content Manager ?API 来完?Item Type 以及它包含的 attribute 的创建。上文已l出了通过参数来连?Content Manager 的方法,下面的示例代码用得到?DKDatastoreICM 来实现具体的操作Q?/p>
清单 10. API 调用
// Create an item / DDO / Root Component DKDDO ddo = dsICM.createDDO("S_withChild", itemPropertyOrSemanticType); //createDDO(<Overall Item Type>, <Item Property / Semantic Type>); // Adding Multivalue Attributes to DDOs, multiple type can be used, //here just give some example ddo.setData(ddo.dataId(DKConstant.DK_CM_NAMESPACE_ATTR,"S_varchar"), "this is a string value"); //string ddo.setData(ddo.dataId(DKConstant.DK_CM_NAMESPACE_ATTR,"S_date"), java.sql.Date.valueOf("2001-08-12")); //date ddo.setData(ddo.dataId(DKConstant.DK_CM_NAMESPACE_ATTR,"S_double"), new Double("123")); //double |
通过本文的介l,怿您对多线E构建的 Batch 框架实现大量数据q移的过E,和通过配置文g的管理的 API 实现数据导入的过E也有了一定的了解和学习。您可灵zd实现一对一Q一对多Q多对多{各U映关p,您也可以利用多线E来实现其他的功能的开发,~写出更加富有创造性的软g?/p>
学习
获得产品和技?/strong>