前面已经说明全量表适用DataX工具进行同步,因此对DataX进行一个简单的学习
DataX 是一个离线同步工具,致力于实现包括关系型数据库(MySQL、Oracle等)、HDFS、Hive、ODPS、HBase、FTP等各种异构数据源之间稳定高效的数据同步功能。
为了解决异构数据源(异构数据源指的是相同的数据在不同的地方存储的结构不同)同步问题,DataX将复杂的网状的同步链路变成了星型数据链路,DataX作为中间传输载体负责连接各种数据源。当需要接入一个新的数据源的时候,只需要将此数据源对接到DataX,便能跟已有的数据源做到无缝数据同步(这里不管是从哪里读的数据,统统转化为DataX规定的结构,再根据要写入的数据源的类型,转化为对应的结构)。
DataX本身作为离线数据同步框架,采用Framework + plugin架构构建。将数据源读取和写入抽象成为Reader/Writer插件,纳入到整个同步框架中。
FrameWork是整个的核心,不管数据源从哪来,到这里都转化为固定的数据结构
PS:流控:速度的控制
下图是一个DataX作业生命周期的时序图:
一个Job是一个进程,一个Task是一个线程
举一个例子,用户提交了一个DataX作业(一个Job),并且配置了总的并发度为20,目的是对一个有100张分表(默认1张表为一个Task,实际上一张表可以继续切分为多个Task)的mysql数据源进行同步。DataX的调度决策思路是:
1)DataX Job根据分库分表切分策略,将同步工作分成100个Task。
2)根据配置的总的并发度20(可以同时运行20个Task),以及每个Task Group的并发度5(每个Task Group可以同时运行5个Task),DataX计算共需要分配4个TaskGroup。
3)4个TaskGroup平分100个Task,每一个TaskGroup负责运行25个Task。
功能 | DataX | Sqoop |
---|---|---|
运行模式 | 单进程多线程 | MR |
分布式 | 不支持,可以通过调度系统规避 | 支持(MR就是分布式) |
流控 | 有流控功能 | 需要定制 |
统计信息 | 已有一些统计,上报需定制 | 没有,分布式的数据收集不方便 |
数据校验 | 在core部分有校验功能 | 没有,分布式的数据收集不方便 |
监控 | 需要定制 | 需要定制 |
综上,DataX功能更加强大,速度更加快,但是DataX对于数据格式的支持较少(只支持gzip压缩格式,lzo等都不支持)
第一步,先下载,下载地址:http://datax-opensource.oss-cn-hangzhou.aliyuncs.com/datax.tar.gz
第二步,解压:tar -zxvf datax.tar.gz -C /opt/module/
第三步,自检:python /opt/module/datax/bin/datax.py /opt/module/datax/job/job.json
出现如下内容,则表明安装成功:
命令:python bin/datax.py path/to/your/job.json (job.json是自己编写的同步文件)
查看DataX配置文件模板命令:python bin/datax.py -r mysqlreader -w hdfswriter
上面的mysqlreader和hdfswriter可以根据自己的需要修改为对应的输入源和输出源
上图是查询给出的模板,json最外层是一个job,job包含setting和content两部分,setting是对整个job的配置,content是用于配置数据源和数据的目的地。
还可以通过官方文档查看对应的Reader和Writer的具体参数:https://github.com/alibaba/DataX/blob/master/README.md https://gitee.com/mirrors/DataX/blob/master/README.md
点击对应的读、写,可以查看对应的参数
通常情况下,离线数据同步任务需要每日定时重复执行,故HDFS上的目标路径通常会包含一层日期,以对每日同步的数据加以区分,也就是说每日同步数据的目标路径不是固定不变的,因此DataX配置文件中HDFS Writer的path参数的值应该是动态的。为实现这一效果,就需要使用DataX传参的功能。
DataX传参的用法如下,在JSON配置文件中使用${param}引用参数,在提交任务时使用-p"-Dparam=value"
传入参数值,具体示例如下:
在需要动态传入参数的地方这样写:
"fileType": "text",
"path": "/base_province/${dt}",
"writeMode": "append"
其中dt就是要传入的参数名
在提交任务时,使用命令:python bin/datax.py -p"-Ddt=2020-06-14" job/base_province.json
案例需求:同步gmall数据库中base_province表数据到HDFS的/base_province目录
需求分析:要实现该功能,需选用MySQLReader和HDFSWriter,MySQLReader具有两种模式分别是TableMode和QuerySQLMode,前者使用table,column,where等属性声明需要同步的数据;后者使用一条SQL查询语句声明需要同步的数据。
首先,创建配置文件:vim /opt/module/datax/job/base_province.json
编写配置文件:
{
"job": {
"setting": {
"speed": {
"channel": 1
}
},
"content": [
{
"reader": {
"name": "mysqlreader",
"parameter": {
"username": "root",
"password": "123456",
"column": [
"id",
"name",
"region_id",
"area_code",
"iso_code",
"iso_3166_2"
],
"where":"id>=3",
"splitPk": "id",
"connection": [
{
"table": [
"base_province"
],
"jdbcUrl": [
"jdbc:mysql://hadoop102:3306/gamll"
]
}
]
}
},
"writer": {
"name": "hdfswriter",
"parameter": {
"defaultFS": "hdfs://hadoop102:8020",
"fileType": "text",
"path": "/base_province",
"fileName": "base_province",
"column": [
{
"name": "id",
"type": "bigint"
},
{
"name": "name",
"type": "string"
},
{
"name": "region_id",
"type": "string"
},
{
"name": "area_code",
"type": "string"
},
{
"name": "iso_code",
"type": "string"
},
{
"name": "iso_3166_2",
"type": "string"
}
],
"writeMode": "append",
"fieldDelimiter": "\t",
"compress":"gzip"
}
}
}
]
}
}
注:mysqlreader里的jdbcUrl是一个数组,可以写多个url(阿里内部支持多个url,外部写一个即可),但是这里写多个的意思是一个数据库连接的多种参数形式,比如:"jdbc:mysql://bad_ip:3306/database", "jdbc:mysql://127.0.0.1:bad_port/database", "jdbc:mysql://127.0.0.1:3306/database"
这三种形式是为了保障至少有一个能连接上这个数据库,而不支持连接多个数据库。
hdfswriter里的writeMode参数为append时,写入数据不做任何处理,会在fileName后加一些字符,保证文件名不会冲突;参数为nonConflict时,如果目录下有fileName前缀的文件,直接报错。
最终结果:
红色部分就是为了防止文件重复加的字符
Reader参数说明:
Writer参数说明:
注意事项:
HDFS Writer没有提供nullFormat参数,这意味着用户无法指定null值写到HDFS的存储格式。HDFS默认将null值存储为空字符串(""),而Hive默认的null值存储格式为\N,因此如果不对null值做处理,在查询表的时候会将null值显示为""。
解决方法一:修改HDFS Writer源码,增加自定义null值存储格式的逻辑,可以参考https://blog.csdn.net/u010834071/article/details/105506580
(没有阅读代码的基础不推荐)
解决方法二:在Hive建表时自定义null值的存储格式为空字符串(推荐),如下:
DROP TABLE IF EXISTS base_province;
CREATE EXTERNAL TABLE base_province
(
`id` STRING COMMENT '编号',
`name` STRING COMMENT '省份名称',
`region_id` STRING COMMENT '地区ID',
`area_code` STRING COMMENT '地区编码',
`iso_code` STRING COMMENT '旧版ISO-3166-2编码,供可视化使用',
`iso_3166_2` STRING COMMENT '新版IOS-3166-2编码,供可视化使用'
) COMMENT '省份表'
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
NULL DEFINED AS ''
LOCATION '/base_province/';
NULL DEFINED AS ‘’ 这里将’'定义为null
PS:Hive默认使用Hadoop的压缩格式,因此可以查出来gzip类型的压缩文件中的数据,Hadoop不支持的压缩格式需要额外去写
Setting参数说明:
上面的errorLimit一般不配,采用默认
配置文件内容如下:
{
"job": {
"setting": {
"speed": {
"channel": 1
}
},
"content": [
{
"reader": {
"name": "mysqlreader",
"parameter": {
"username": "root",
"password": "123456",
"connection": [
{
"querySql": [
"select id,name,region_id,area_code,iso_code,iso_3166_2 from base_province where id>=3;"
],
"jdbcUrl": [
"jdbc:mysql://hadoop102:3306/gamll"
]
}
]
}
},
"writer": {
"name": "hdfswriter",
"parameter": {
"defaultFS": "hdfs://hadoop102:8020",
"fileType": "text",
"path": "/base_province",
"fileName": "base_province",
"column": [
{
"name": "id",
"type": "bigint"
},
{
"name": "name",
"type": "string"
},
{
"name": "region_id",
"type": "string"
},
{
"name": "area_code",
"type": "string"
},
{
"name": "iso_code",
"type": "string"
},
{
"name": "iso_3166_2",
"type": "string"
}
],
"writeMode": "append",
"fieldDelimiter": "\t",
"compress":"gzip"
}
}
}
]
}
}
在使用了querySql后,第一种方式的table、column这些配置失效(一般直接删掉),即querySql的优先级大于column、where选项。
第一步,在向HDFS同步数据前,要确保目标路径已存在:hadoop fs -mkdir /base_province
第二步,进入DataX根目录,执行:python bin/datax.py job/base_province.json
第三步,查看DataX运行结果:
2021-10-13 11:13:14.930 [job-0] INFO JobContainer -
任务启动时刻 : 2021-10-13 11:13:03
任务结束时刻 : 2021-10-13 11:13:14
任务总计耗时 : 11s
任务平均流量 : 66B/s
记录写入速度 : 3rec/s
读出记录总数 : 32
读写失败总数 : 0
第四步,查看HDFS文件,命令(linux查看gzip文件命令):hadoop fs -cat /base_province/* | zcat
3 山西 1 140000 CN-14 CN-SX
4 内蒙古 1 150000 CN-15 CN-NM
5 河北 1 130000 CN-13 CN-HE
6 上海 2 310000 CN-31 CN-SH
7 江苏 2 320000 CN-32 CN-JS
8 浙江 2 330000 CN-33 CN-ZJ
9 安徽 2 340000 CN-34 CN-AH
10 福建 2 350000 CN-35 CN-FJ
11 江西 2 360000 CN-36 CN-JX
12 山东 2 370000 CN-37 CN-SD
14 台湾 2 710000 CN-71 CN-TW
15 黑龙江 3 230000 CN-23 CN-HL
16 吉林 3 220000 CN-22 CN-JL
17 辽宁 3 210000 CN-21 CN-LN
18 陕西 7 610000 CN-61 CN-SN
19 甘肃 7 620000 CN-62 CN-GS
20 青海 7 630000 CN-63 CN-QH
21 宁夏 7 640000 CN-64 CN-NX
22 新疆 7 650000 CN-65 CN-XJ
23 河南 4 410000 CN-41 CN-HA
24 湖北 4 420000 CN-42 CN-HB
25 湖南 4 430000 CN-43 CN-HN
26 广东 5 440000 CN-44 CN-GD
27 广西 5 450000 CN-45 CN-GX
28 海南 5 460000 CN-46 CN-HI
29 香港 5 810000 CN-91 CN-HK
30 澳门 5 820000 CN-92 CN-MO
31 四川 6 510000 CN-51 CN-SC
32 贵州 6 520000 CN-52 CN-GZ
33 云南 6 530000 CN-53 CN-YN
13 重庆 6 500000 CN-50 CN-CQ
34 西藏 6 540000 CN-54 CN-XZ
方式 | 优点 | 缺点 |
---|---|---|
TableMode | 可以切分任务(channel并发度不能填1,splitPk要填值) | |
QuerySQLMode | 固定不能切分任务,一个SQL语句不能切分,如果写多个才可能切分 |
案例要求:同步HDFS上的/base_province目录下的数据到MySQL gmall 数据库下的test_province表。
需求分析:要实现该功能,需选用HDFSReader和MySQLWriter。
创建配置文件:vim /opt/module/datax/job/base_province.json
编写配置文件:
{
"job": {
"setting": {
"speed": {
"channel": 1
}
},
"content": [
{
"reader": {
"name": "hdfsreader",
"parameter": {
"path": "/base_province",
"defaultFS": "hdfs://hadoop102:8020",
"column": [
"*"
],
"fileType": "text",
"encoding": "UTF-8",
"fieldDelimiter": "\t",
"compress": "gzip",
"nullFormat": "\\N"
}
},
"writer": {
"name": "mysqlwriter",
"parameter": {
"writeMode": "replace",
"username": "root",
"password": "123456",
"column": [
"id",
"name",
"region_id",
"area_code",
"iso_code",
"iso_3166_2"
],
"connection": [
{
"jdbcUrl": "jdbc:mysql://hadoop102:3306/gmall?useUnicode=true&characterEncoding=utf-8",
"table": [
"test_province"
]
}
]
}
}
}
]
}
}
HDFS Reader参数说明:
这里column中的列的类型是DataX里的类型,不是HDFS里的类型
nullFormat是将\N识别为null值
MySQL Writer参数说明:
writeMode参数为insert时,当主键/唯一性索引冲突时会写不进去冲突的行;当参数为replace时,没有遇到主键/唯一性索引冲突时,与 insert into 行为一致,冲突时会用新行替换原有行所有字段。
注意:数据写入MySQL里的表时,要先确保这个表存在,若不存在,要先创建。
第一步,在MySQL中创建gmall.test_province表:
DROP TABLE IF EXISTS `test_province`;
CREATE TABLE `test_province` (
`id` bigint(20) NOT NULL,
`name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`region_id` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`area_code` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`iso_code` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`iso_3166_2` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
第二步,进入DataX个目录,执行命令:python bin/datax.py job/test_province.json
最后,查看DataX结果:
2021-10-13 15:21:35.006 [job-0] INFO JobContainer -
任务启动时刻 : 2021-10-13 15:21:23
任务结束时刻 : 2021-10-13 15:21:35
任务总计耗时 : 11s
任务平均流量 : 70B/s
记录写入速度 : 3rec/s
读出记录总数 : 34
读写失败总数 : 0
DataX提供了包括通道(并发)、记录流、字节流三种流控模式,可以随意控制你的作业速度,让你的作业在数据库可以承受的范围内达到最佳的同步速度。
关键优化参数如下:
参数 | 说明 |
---|---|
job.setting.speed.channel | 总并发数 |
job.setting.speed.record | 总record限速 |
job.setting.speed.byte | 总byte限速 |
core.transport.channel.speed.record | 单个channel的record限速,默认值为10000(10000条/s) |
core.transport.channel.speed.byte | 单个channel的byte限速,默认值1024*1024(1M/s) |
注意事项:
1)如果配置了总的record限速,就必须配置单个channel的record限速
2)如果配置了总的byte限速,就必须配置单个的channel的byte限速
3)如果配置了总的record和byte限速,channel并发数参数就会失效。因为配置了总record限速和总byte限速之后。实际channel并发数是通过计算得到的。
计算公式:
min(总byte限速/单个channel的byte限速,总record限速/单个channel的record限速)
例如设置channel为10,总record限速为80000,单个record限速为10000。首先,即使只设置channel为10,不设置别的,并发度也不一定正好为10,只是尽可能的贴近于这个值;其次因为总record限速80000,单个record限速10000,所以有8个record,小于10,限制了channel,最终并发度贴近于8。
配置实例:
{
"core": {
"transport": {
"channel": {
"speed": {
"byte": 1048576 //单个channel byte限速1M/s
}
}
}
},
"job": {
"setting": {
"speed": {
"byte" : 5242880 //总byte限速5M/s
}
},
...
}
}
DataX Job内的Channel并发数提高时,内存的占用也会显著增加,因为DataX作为数据交换通道(数据源和目的地之间的通道),在内存中会缓存较多的数据。例如Channel中会有一个Buffer,作为临时的数据交换的缓冲区,而在部分Reader和Writer中,也会存在一些Buffer。因此为了防止OOM(内存溢出)等错误,需要调大JVM的堆内存。
第一种调整方式:直接更改datax.py脚本,找到-Xms(是JVM初始分配的内存,设置太大可能直接就起不来)和-Xmx(是JVM分配的最大内存)这两个地方,修改为需要的内存大小
第二种调整方式:在使用命令提交Job的时候,加上一个参数,例如python datax/bin/datax.py --jvm="-Xms8G -Xmx8G" /path/to/your/job.json
PS:内存一般可以设置为4G或者8G