数据同步过程中的数据转换——DataX Transformer实践

前言

最近遇到一个需求,需要从一个表里读取数据,并解密其中一个字段,然后写入另一个表中,表的数据量大概是一千多万,听上去是一个很简单的需求。我手上正好有一个日常在用的Springboot + MyBatis-Plus的框架,我想着直接用MyBatis-Plus3.5.4开始支持的一个流式查询来完成这一功能,但是实践起来发现数据同步的效率十分低。后来想到可不可以用DataX来做数据同步,因为DataX作为一个数据同步工具,效率十分高,去看了DataX的官网,发现它支持数据转换功能,它提供了Transformer来帮助我们实现一些数据传输过程中定制化的需求。
在这里插入图片描述

原理

Transformer在DataX的架构中介于Reader和Writer之间,每一条数据记录(Record)从Reader进入Transformer过程后,根据配置文件中指定的字段,对字段进行一些逻辑处理,并将处理后的数据更新到Record对应字段中,然后输出到Writer。我们可以自定义那部分处理逻辑,以实现我们的定制化需求。
在这里插入图片描述

实践

第一步:拉取DataX源码、下载DataX

  • 进入到阿里DataX的官网
  • 通过git拉取DataX源码
    在这里插入图片描述
  • 下载官方已经给我们提供的DataX(文档往下滑,有个DataX下载地址)
    在这里插入图片描述

第二步:源码解读

  • 一步步的解读源码可以帮助我们快速分析出他的代码结构,并帮助我们找到 我们应该在哪里开发我们自己的定制化代码
  • 在拉下来的代码中找到transformer模块,我们可以看到里面只有两个抽象类,一个是复杂型Transformer,一个是普通的Transformer
    在这里插入图片描述
  • 我们点到普通的Transformer类中,发现这是一个抽象类,其中evaluate方法的注释告诉我们这个方法就是核心的处理逻辑,于是我们猜测想要实现自定义的transformer得继承这个类并实现里面的evaluate方法。双击类名,Ctrl + H打开该类的血缘结构,可以看到下面有很多的实现类,是官方已经写好的可以直接提供给我们使用的transformer,我们随便点一个双击进去。
    在这里插入图片描述
  • 可以看到这个FilterTransformer实现类继承了Transformer父类,同时实现了evaluate方法,并在里面写了具体的处理逻辑,我们之前的猜想也得到了应验。现在我们需要定位FilterTransformer这个类文件的位置,点击左侧目录上面的定位按钮,找到这个文件在core模块下的其中一个叫transformer文件夹中,这里面还有其他的Transformer实现类,我们后面要自定义的逻辑代码也将写在这一部分。
    在这里插入图片描述

第三步:自定义Transformer实现类

  • 这里我的业务逻辑是需要实现一个加密解密,我需要用到hutool中的SecureUtil工具类进行我的加密解密操作,因此在core模块的pom文件中添加了hutool的依赖
  • 同时找到util文件夹下,创建我们自己的工具类,将加密解密的逻辑提取到一个工具类中,与业务解耦
    在这里插入图片描述
package com.alibaba.datax.core.util;

import cn.hutool.crypto.SecureUtil;

import java.nio.charset.StandardCharsets;
import java.util.Objects;

public class AesUtil {
	public final static byte[] KEY = "123456".getBytes(StandardCharsets.UTF_8);
	
	//加密
	public static String encrypt(String value) {
		if (Objects.isNull(value)){
			return null;
		}
		return SecureUtil.aes(KEY).encryptHex(value);
	}
	
	//解密
	public static String decrypt(String value) {
		if (Objects.isNull(value)){
			return null;
		}
		return SecureUtil.aes(KEY).decryptStr(value);
	}
	
}
  • 在transformer文件夹下创建我们的实现类并继承Transformer,在构造函数中指定我们的transformer函数名字,在evaluate中实现具体逻辑
    在这里插入图片描述
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.common.exception.DataXException;
import com.alibaba.datax.core.util.AesUtil;
import com.alibaba.datax.transformer.Transformer;

import java.util.Arrays;
import java.util.Objects;

public class AesTransformer extends Transformer {
	final static String AES_TYPE_ENCODE = "encode";
	final static String AES_TYPE_DECODE = "decode";
	
	public AesTransformer() {
		//定义该Transformer的函数名字,在后面配置文件中通过该名字来让datax知道我们需要使用哪个transformer
		setTransformerName("dx_aes");
	}
	
	//record参数表示一条数据记录
	//paras数组表示传入的参数列表,paras数组至少为一个,表示要操作的字段的索引(字段索引从0开始);其它的数组元素可以自己规定
	//在这里我们希望paras有两个参数,第一个表示要加密解密的字段的索引值,第二个表示要进行哪种操作,加密还是解密
	@Override
	public Record evaluate(Record record, Object... paras) {
		int columnIndex;
		String aesType;
		
		//校验传入的参数列表,并接收参数
		try {
			if (paras.length != 2) {
				throw new RuntimeException("dx_aes 的参数个数必须为2");
			}
			columnIndex = (Integer) paras[0];
			aesType = (String) paras[1];
			if (!Objects.equals(aesType, AES_TYPE_ENCODE) && !Objects.equals(aesType, AES_TYPE_DECODE)){
				throw new RuntimeException("dx_aes 的第二个参数必须为encode或decode");
			}
		} catch (Exception e) {
			throw DataXException.asDataXException(TransformerErrorCode.TRANSFORMER_ILLEGAL_PARAMETER, "paras:" + Arrays.asList(paras).toString() + " => " + e.getMessage());
		}

		//处理数据
		//1.获取到要操作的字段
		Column column = record.getColumn(columnIndex);
		try {
			//2.获取到字段值
			String oriValue = column.asString();
			if (oriValue == null) {
				return record;
			}

			//3.判断操作类型,并执行加密或者解密,然后把字段的新值重新设置回record中
			if(column.getType() == Column.Type.STRING && aesType.equals(AES_TYPE_DECODE)) {
				String newValue = AesUtil.decrypt(oriValue);
				record.setColumn(columnIndex, new StringColumn(newValue));
			} else if (column.getType() == Column.Type.STRING && aesType.equals(AES_TYPE_ENCODE)) {
				String newValue = AesUtil.encrypt(oriValue);
				record.setColumn(columnIndex, new StringColumn(newValue));
			}
		} catch (Exception e) {
			throw DataXException.asDataXException(TransformerErrorCode.TRANSFORMER_RUN_EXCEPTION, e.getMessage(), e);
		}

		//4.返回record
		return record;
	}
}

  • 注册我们编写好的的transformer类,找到TransformerRegistry类,并在静态代码块中模仿他的代码加一行我们自己的就好了
    在这里插入图片描述

第四步:编译打包

  • 在这一步中可能会遇到些问题,我也会把我遇到的问题及解决思路写出来给大家参考。
  • 由于我们在整个过程中只改动了core模块的代码,因此我们在打包的时候只需要打包公共模块,别的reader和writer组件不需要打包。我们找到整个项目的pom文件,在其中的modules标签中,只保留图中这三个module,这三个是公共模块,必须要编译打包的,别的模块由于我们没有改动,因此不需要编译打包,全部注释掉即可,可以节约很多时间。
    在这里插入图片描述
  • 重新加载maven项目后,找到maven管理界面,在datax-all下的Lifecycle中,先执行clean,执行完后执行package
    在这里插入图片描述
  • 这里我遇到了一个问题,我们找到对应出错的位置,也就是transformer模块下,有个assembly目录,打开里面的xml文件,我们在他的id标签中随便加个文本就可以解决问题
    在这里插入图片描述
    在这里插入图片描述
  • 再重新执行clean和package,发现又是一样的错误,只不过这次出现在了core模块,和上步一样的解决思路,我们找到core模块的assembly目录,打开里面的xml文件,我们在他的id标签中随便加个文本
    在这里插入图片描述
    在这里插入图片描述
  • 等出现这个就代表打包成功了,我们找到core模块下target目录,这个jar包就是我们要用的(为什么别的两个不需要,因为我们只改了core的代码,所以只需要这个)
    在这里插入图片描述
    在这里插入图片描述

第五步:替换jar包

  • 打开我们之前下载的DataX,解压,找到里面的lib目录
    在这里插入图片描述
  • 把我们准备的core的jar包直接拖进这个里面,这时候会跳出一个同名文件是否替换,选择是,替换掉它原来的jar包就好了

第六步:编写配置文件运行DataX

  • 打开job目录,一般配置文件都写在job中
    在这里插入图片描述
  • 配置文件编写示例,在这里我们对name字段需要进行解密,在transformer配置中,name指定我们定义的函数名,表示要使用哪个transformer,parameter输入我们的参数
{
    "job": {
        "setting": {
            "speed": {
                "channel":1
            },
            "errorLimit": {
                "record": 0,
                "percentage": 0.02
            }
        },
        "content": [
            {
                "transformer": [
                    {
                        "name": "dx_aes",
                        "parameter":
                            {
                            "columnIndex":1,
                            "paras":["decode"]
                            }
                    }
                ],
                "reader": {
                    "name": "mysqlreader",
                    "parameter": {
                        "username": "***",
                        "password": "*******",
                        "column" : [
                                      "id",
                                      "name"
                                ],
                        "connection": [
                            {
                                "jdbcUrl": ["jdbc:mysql://localhost:3306/test"],
                                "table": ["test1"]
                            }
                        ]
                    }
                },
                "writer": {
                    "name": "mysqlwriter",
                    "parameter": {
                        "username": "***",
                        "password": "********",
                        "writeMode": "insert",
                        "column" : [
                                     "id",
                                      "name"

                                ],
                        "connection": [
                            {
                                "jdbcUrl": "jdbc:mysql://localhost:3306/test",
                                "table": ["test2"]
                            }
                        ]
                    }
                }
            }
        ]
    }
}

  • 打开cmd控制台,在datax目录下直接输入cmd,回车
    在这里插入图片描述
  • 根据自己的配置文件名,修改下命令即可
    python .\bin\datax.py .\job\job.json
    在这里插入图片描述
  • 16
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值