DataX 是阿里巴巴集团内被广泛使用的离线数据同步工具/平台,实现包括 MySQL、Oracle、SqlServer、Postgre、HDFS、Hive、ADS、HBase、TableStore(OTS)、MaxCompute(ODPS)、DRDS 等各种异构数据源之间高效的数据同步功能。
详细介绍见 https://github.com/alibaba/DataX/blob/master/introduction.md ,本文不对DataX的基本情况进行介绍,主要记录几个注意事项
本次在公司项目中主要用来进行oracle到oracle的数据同步工作,将研究过程记录于此。
OracleReader插件详细介绍:https://github.com/alibaba/DataX/blob/master/oraclereader/doc/oraclereader.md
OracleWriter插件详细介绍:https://github.com/alibaba/DataX/blob/master/oraclewriter/doc/oraclewriter.md
1、Oracle同步不支持更新操作,因为OracleWriter只支持insert into…(当主键/唯一性索引冲突时会写不进去冲突的行),mysql支持replace into(也就是更新),也就是Mysql支持更新,Oracle不支持更新。那么增量同步在Oracle这里只能是流水型数据。
2、preSql说明:job对应的json文件中,connection中的table必须是数据库存在的表,否则连接错误。执行顺序是先连表,再执行preSql,因此如果全量同步时,如果需要临时表,则需要额外编写程序完成操作。
3、postSql说明:在同步任务完成后可以进行的操作,如删除原表,更新临时表名为原表名,实现全量同步的同时,尽可能减小对原系统的影响。
4、可以配置出错限制条件,当数据出错超过限制条件时,程序会结束执行,但已开启的同步通道还会继续执行到完成
"errorLimit": {
//先选择record
"record": 0,
//百分比 1表示100%
"percentage": 0.02
}
5、splitPk:OracleReader插件中会有此参数,如果指定splitPk,表示用户希望使用splitPk代表的字段进行数据分片,DataX因此会启动并发任务进行数据同步,这样可以大大提供数据同步的效能。
推荐splitPk用户使用表主键,因为表主键通常情况下比较均匀,因此切分出来的分片也不容易出现数据热点。目前splitPk仅支持整形、字符串型数据切分,splitPk如果不填写,将视作用户不对单表进行切分,OracleReader使用单通道同步全量数据。
该处由于业务需求,想知道具体是如何根据 splitPk 进行数据切分的,看源码得知:
首先计算需要将数据切分的份数,对单表会根据设置的 channel数 * 5 得到
源码在 DataX-master\plugin-rdbms-util\src\main\java\com\alibaba\datax\plugin\rdbms\reader\util\ReaderSplitUtil.java 类中doSplit 方法内,eachTableShouldSplittedNumber = eachTableShouldSplittedNumber * 5;
会首先根据指定的 splitPk 和 上一步得到的切分份数 进行抽样,sql如下:
SELECT * FROM ( SELECT ID FROM REG_MARPRIPINFO SAMPLE (0.1) WHERE (ID IS NOT NULL) ORDER BY DBMS_RANDOM.VALUE) WHERE ROWNUM <= 40 ORDER by ID ASC;
ID是设置的splitPk,40是设置了 channel=8计算得到。
从这里可以看出对表的切割不是均分,而是随机抽样的结果,无论是整形、字符串型都是该方式划分区间。
暂不确定SAMPLE是否对视图生效
6、日志输出问题,日志文件名的设置方式在 datax.py 中,具体代码如下:
# jobResource 可能是 URL,也可能是本地文件路径(相对,绝对)
jobResource = args[0]
if not isUrl(jobResource):
jobResource = os.path.abspath(jobResource)
if jobResource.lower().startswith("file://"):
jobResource = jobResource[len("file://"):]
jobParams = ("-Dlog.file.name=%s") % (jobResource[-20:].replace('/', '_').replace('.', '_'))
可以看到取了路径的后20个字符作为日志名,因此就会发现默认生成的日志名是混乱的,另外日志文件名后的时间戳是在由java代码控制的,配置文件在datax/conf/logback.xml
内。见下图
图中我将控制台的日志输出进行了关闭,同时日志文件的时间戳不再由java代码控制,改为python代码控制
如果要以job对应的json名作为日志文件名,并添加时间戳,可参考下边代码:
jobResource = args[0]
if not isUrl(jobResource):
jobResource = os.path.abspath(jobResource)
if jobResource.lower().startswith("file://"):
jobResource = jobResource[len("file://"):]
# print(jobResource)
o_file_name = jobResource.split("/")[-1].replace('.', '_')
timestamp = datetime.datetime.now().strftime("%H_%M_%S.%f")[:-3] # 注意导包 import datetime
file_name = o_file_name + "-" + timestamp
print(file_name)
# 给文件后缀添加时间戳
jobParams = ("-Dlog.file.name=%s") % (file_name)
生成的日志文件名如下图所示:
7、python3启动datax.py,参考:https://github.com/WeiYe-Jing/datax-web/tree/master/doc/datax-web/datax-python3 将其中的三个文件替换原始的 datax/bin目录下的三个即可
8、datax的程序是通过datax.py启动的,是通过python的开启子进程调用执行的
9、idea中远程调试代码方法参考该文:https://www.kanzhun.com/jiaocheng/167192.html
10、读取oracle的数据,并非采用分页方式,而是通过指定jdbc的fetchSize参数来实现,可参考 https://www.cnblogs.com/baimingqian/p/11761942.html 的介绍,查询数据的源码如下:
rs = DBUtil.query(conn, querySql, fetchSize);
// query 底层代码如下:
public static ResultSet query(Connection conn, String sql, int fetchSize, int queryTimeout)
throws SQLException {
// make sure autocommit is off
conn.setAutoCommit(false);
Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY);
stmt.setFetchSize(fetchSize);
stmt.setQueryTimeout(queryTimeout);
return query(stmt, sql);
}
// 不断获取数据,transportOneRecord 用来生成一条记录并交给writer执行写入操作
while (rs.next()) {
rsNextUsedTime += (System.nanoTime() - lastTime);
this.transportOneRecord(recordSender, rs,
metaData, columnNumber, mandatoryEncoding, taskPluginCollector);
lastTime = System.nanoTime();
}
11、连接vertica时,writer对应的驱动要放到根目录下的lib内,而非官方给出的路径,否则会找不到驱动
12、连接vertica时,如果找不到驱动,会一直重试连接,经调试查看源码发现rdbms对应的重试次数在代码中写死了,会重试9次,然后抛出异常,源码如下:
public static Connection getConnection(final DataBaseType dataBaseType,
final String jdbcUrl, final String username, final String password, final String socketTimeout) {
try {
return RetryUtil.executeWithRetry(new Callable<Connection>() {
@Override
public Connection call() throws Exception {
return DBUtil.connect(dataBaseType, jdbcUrl, username,
password, socketTimeout);
}
}, 9, 1000L, true); // 此处的9 即为重试次数
} catch (Exception e) {
throw DataXException.asDataXException(
DBUtilErrorCode.CONN_DB_ERROR,
String.format("数据库连接失败. 因为根据您配置的连接信息:%s获取数据库连接失败. 请检查您的配置并作出修改.", jdbcUrl), e);
}
}
该代码在\DataX-master\plugin-rdbms-util\src\main\java\com\alibaba\datax\plugin\rdbms\util\DBUtil.java
中第325行