业务场景
我们选取了DATAX作为数据抽取引擎,数据存储的目标库是apche doris数据库,业务需求是在将数据从源端库抽取到目标端时需要新增一个full_name字段,full_name是拼接了前面所有字段信息的大文本字段,用于全文检索。由于full_name字段建立了中文倒排索引,使用了DUPLICATE KEY数据模型,不支持数据行的修改,在综合考虑下决定在DATAX数据抽取任务过程中新增自定义transformer来实现。
实现原理
DATAX使用JAVA编写的,源码中提供了transformer模块,支持数据提取后数据加工转换能力,通过实现Transformer类来对行记录Record进行修改、新增等操作。
DATAX源码下载
DATAX源码地址,我这里直接使用的master分支。
DATAX源码编译
源码下载完成之后,需要对源码进行打包编译
在编译前我对根目录下pom文件的一些不需要使用的reader和writer模块进行了注释,减少编译时间。
<modules>
<module>common</module>
<module>core</module>
<module>transformer</module>
<!-- reader -->
<module>mysqlreader</module>
<module>oraclereader</module>
<module>dorisreader</module>
<!-- writer -->
<module>mysqlwriter</module>
<module>oraclewriter</module>
<module>doriswriter</module>
<module>selectdbwriter</module>
<module>adbmysqlwriter</module>
<module>plugin-rdbms-util</module>
<module>plugin-unstructured-storage-util</module>
<module>datax-example</module>
</modules>
执行编译命令: mvn clean package -DskipTests assembly:assembly
编译完成后在项目根目录下会有target包。
自定义transformer
datax的启动入口在datax-core模块下,datax-core依赖了datx-transformer模块,我们在core模块或者transformer下自定义代码逻辑即可。
注意:内部加载的transformer注册名称必须以**dx_**开头。注册名在后面的json配置文件中使用。
package com.alibaba.datax.core.transport.transformer;
import com.alibaba.datax.common.element.Column;
import com.alibaba.datax.common.element.Record;
import com.alibaba.datax.common.element.StringColumn;
import com.alibaba.datax.transformer.Transformer;
/**
* @projectName: DataX
* @package: com.alibaba.datax.transformer.transformer
* @className: ConcatTransformer
* @author: chuanxilaomuji
* @description: TODO
* @date: 2024/10/22 9:57
* @version: 1.0
*/
public class JskjConcatTransformer extends Transformer {
public JskjConcatTransformer() {
setTransformerName("dx_concat_transformer");
}
@Override
public Record evaluate(Record record, Object... paras) {
if (paras.length < 2) {
throw new IllegalArgumentException("需要提供参与拼接的列和目标列名");
}
// 判断是否为 "concat_all",如果是,则拼接所有列
String option = (String) paras[1];
String separator = paras.length > 3 ? (String) paras[3] : ""; // 可选分隔符
StringBuilder fullNameBuilder = new StringBuilder();
// 如果传入的参数为 "concat_all",拼接所有列的内容
if ("concat_all".equals(option)) {
// 遍历所有列,将每个列的数据拼接起来
int columnCount = record.getColumnNumber();
for (int i = 0; i < columnCount; i++) {
Column column = record.getColumn(i);
if (column != null && column.asString() != null) {
if (fullNameBuilder.length() > 0) {
fullNameBuilder.append(separator);
}
fullNameBuilder.append(column.asString());
}
}
} else {
// 否则,按照指定的列拼接
String[] columnsToConcatenate = option.split(",");
for (String col : columnsToConcatenate) {
int colIndex = Integer.parseInt(col.trim());
Column column = record.getColumn(colIndex);
if (column != null && column.asString() != null) {
if (fullNameBuilder.length() > 0) {
fullNameBuilder.append(separator);
}
fullNameBuilder.append(column.asString());
}
}
}
// 将拼接好的 full_name 列添加到 record 中
record.addColumn(new StringColumn(fullNameBuilder.toString()));
return record;
}
}
自定义transformer类后,需要在注册器中注册。注册类位于core模块下core.transport.transformer包路径下的TransformerRegistry类中。
static {
/**
* add native transformer
* local storage and from server will be delay load.
*/
registTransformer(new SubstrTransformer());
registTransformer(new PadTransformer());
registTransformer(new ReplaceTransformer());
registTransformer(new FilterTransformer());
registTransformer(new GroovyTransformer());
registTransformer(new DigestTransformer());
registTransformer(new JskjConcatTransformer());
}
运行调试
注意:datax运行是要有python环境来执行bin目录下的.py文件的,我这里装的是python2.7.5版本,必须使用python2.6以上的版本,不要用python3。
找到core模块下的Engine类,修改main方法中的参数进行调试,这里配置的datax.home就是代码实际运行地址;本地调试standalone,jobid必须为-1,修改job_RW_YX_SJQX.json内容即可,注意datax.home配置到bin目录的上一级
public static void main(String[] args) throws Exception {
int exitCode = 0;
try {
System.setProperty("datax.home","E:\\DataX\\target\\datax\\datax");
String[] datxArgs = {"-job", "E:\\DataX\\target\\datax\\datax\\job\\job_RW_YX_SJQX.json", "-mode", "standalone", "-jobid", "-1"};
Engine.entry(datxArgs);
// Engine.entry(args);
} catch (Throwable e) {
exitCode = 1;
LOG.error("\n\n经DataX智能分析,该任务最可能的错误原因是:\n" + ExceptionTracker.trace(e));
if (e instanceof DataXException) {
DataXException tempException = (DataXException) e;
ErrorCode errorCode = tempException.getErrorCode();
if (errorCode instanceof FrameworkErrorCode) {
FrameworkErrorCode tempErrorCode = (FrameworkErrorCode) errorCode;
exitCode = tempErrorCode.toExitValue();
}
}
System.exit(exitCode);
}
System.exit(exitCode);
}
job_RW_YX_SJQX.json
{
"job": {
"setting": {
"speed": {
"channel": 1
}
},
"content": [{
"reader": {
"name": "mysqlreader",
"parameter": {
"username": "username",
"password": "password",
"connection": [{
"querySql": ["SELECT DWBM , RYBM , QXBM FROM SJQX WHERE 1=1 "],
"jdbcUrl": ["jdbc:dm://ip:port/database"]
}]
}
},
"transformer": [{
"name": "dx_concat_transformer",
"parameter": {
"columnIndex": "1",
"paras": ["concat_all", "full_name", ""]
}
}],
"writer": {
"name": "mysqlwriter",
"parameter": {
"username": "username",
"password": "password",
"column": ["DWBM", "RYBM", "QXBM", "full_name"],
"connection": [{
"jdbcUrl": "jdbc:mysql://ip:port/database",
"table": ["SJQX"]
}]
}
}
}]
}
}
注意:writer.parameter.column下也要新增full_name,DATAX的CommonRdbmsWriter类在构造insert语句时会校验Record的字段数和插入字段数是否一致,我刚刚在transformer添加了record,writer对应要新增一个字段。
if (record.getColumnNumber() != this.columnNumber) {
debug测试完成后引入项目
项目引入DATAX
我这边是将修改后源码打包通过本地引入的方式进行使用,推荐放入公司私服进行管理;这里jar包版本自定义修改的。
<dependency>
<groupId>com.datax</groupId>
<artifactId>datax-core</artifactId>
<version>1.0.2</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/lib/datax-core-1.0.2.jar</systemPath>
</dependency>
<dependency>
<groupId>com.datax</groupId>
<artifactId>datax-transformer</artifactId>
<version>1.0.2</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/lib/datax-transformer-1.0.2.jar</systemPath>
</dependency>
<dependency>
<groupId>com.datax</groupId>
<artifactId>datax-common</artifactId>
<version>1.0.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/lib/datax-common-1.0.0.jar</systemPath>
</dependency>
然后在代码中构建代码逻辑,通过Engine.entry(args)来启动DATAX;对应的JSON文件也需要自己构造并自定目录,这里就不展示了。
System.setProperty("datax.home", dataxMain);
String[] args = {"-job", jsonFilePath, "-mode", "standalone", "-jobid", "-1"};
Engine.entry(args);
ok,完成。