……

第一章、datax入门

一. DataX3.0概览

DataX 是一个异构数据源离线同步工具,致力于实现包括关系型数据库(MySQL、Oracle等)、HDFS、Hive、ODPS、HBase、FTP等各种异构数据源之间稳定高效的数据同步功能。

(这是一个单机多任务的ETL工具)

img

下载地址:http://datax-opensource.oss-cn-hangzhou.aliyuncs.com/datax.tar.gz

1、设计理念

为了解决异构数据源同步问题,DataX将复杂的网状的同步链路变成了星型数据链路,DataX作为中间传输载体负责连接各种数据源。当需要接入一个新的数据源的时候,只需要将此数据源对接到DataX,便能跟已有的数据源做到无缝数据同步。

2、当前使用现状

DataX在阿里巴巴集团内被广泛使用,承担了所有大数据的离线同步业务,并已持续稳定运行了6年之久。目前每天完成同步8w多道作业,每日传输数据量超过300TB。

此前已经开源DataX1.0版本,此次介绍为阿里云开源全新版本DataX3.0,有了更多更强大的功能和更好的使用体验。Github主页地址:https://github.com/alibaba/DataX

二、DataX3.0框架设计

DataX本身作为离线数据同步框架,采用Framework + plugin架构构建。将数据源读取和写入抽象成为Reader/Writer插件,纳入到整个同步框架中。

 

img

  • Reader:Reader为数据采集模块,负责采集数据源的数据,将数据发送给Framework。

  • Writer: Writer为数据写入模块,负责不断向Framework取数据,并将数据写入到目的端。

  • Framework:Framework用于连接reader和writer,作为两者的数据传输通道,并处理缓冲,流控,并发,数据转换等核心技术问题。

三. DataX3.0插件体系

DataX目前已经有了比较全面的插件体系,主流的RDBMS数据库、NOSQL、大数据计算系统都已经接入,目前支持数据如下图

四、DataX3.0核心架构

DataX 3.0 开源版本支持单机多线程模式完成同步作业运行,本小节按一个DataX作业生命周期的时序图,从整体架构设计非常简要说明DataX各个模块相互关系。

 

img

核心模块介绍:

  • DataX完成单个数据同步的作业,我们称之为Job,DataX接受到一个Job之后,将启动一个进程来完成整个作业同步过程。DataX Job模块是单个作业的中枢管理节点,承担了数据清理、子任务切分(将单一作业计算转化为多个子Task)、TaskGroup管理等功能。

  • DataXJob启动后,会根据不同的源端切分策略,将Job切分成多个小的Task(子任务),以便于并发执行。Task便是DataX作业的最小单元,每一个Task都会负责一部分数据的同步工作。

  • 切分多个Task之后,DataX Job会调用Scheduler模块,根据配置的并发数据量,将拆分成的Task重新组合,组装成TaskGroup(任务组)。每一个TaskGroup负责以一定的并发运行完毕分配好的所有Task,默认单个任务组的并发数量为5。

  • 每一个Task都由TaskGroup负责启动,Task启动后,会固定启动Reader—>Channel—>Writer的线程来完成任务同步工作。

  • DataX作业运行起来之后, Job监控并等待多个TaskGroup模块任务完成,等待所有TaskGroup任务完成后Job成功退出。否则,异常退出,进程退出值非0

1、DataX调度流程:

举例来说,用户提交了一个DataX作业,并且配置了20个并发,目的是将一个100张分表的mysql数据同步到odps里面。 DataX的调度决策思路是:

  • DataXJob根据分库分表切分成了100个Task。

  • 根据20个并发,DataX计算共需要分配4个TaskGroup。

  • 4个TaskGroup平分切分好的100个Task,每一个TaskGroup负责以5个并发共计运行25个Task。

五、DataX 3.0六大核心优势

1、可靠的数据质量监控
  1. 完美解决数据传输个别类型失真问题 DataX旧版对于部分数据类型(比如时间戳)传输一直存在毫秒阶段等数据失真情况,新版本DataX3.0已经做到支持所有的强数据类型,每一种插件都有自己的数据类型转换策略,让数据可以完整无损的传输到目的端。

  2. 提供作业全链路的流量、数据量运行时监控 DataX3.0运行过程中可以将作业本身状态、数据流量、数据速度、执行进度等信息进行全面的展示,让用户可以实时了解作业状态。并可在作业执行过程中智能判断源端和目的端的速度对比情况,给予用户更多性能排查信息。

  3. 提供脏数据探测 在大量数据的传输过程中,必定会由于各种原因导致很多数据传输报错(比如类型转换错误),这种数据DataX认为就是脏数据。DataX目前可以实现脏数据精确过滤、识别、采集、展示,为用户提供多种的脏数据处理模式,让用户准确把控数据质量大关!

2、丰富的数据转换功能

DataX作为一个服务于大数据的ETL工具,除了提供数据快照搬迁功能之外,还提供了丰富数据转换的功能,让数据在传输过程中可以轻松完成数据脱敏,补全,过滤等数据转换功能,另外还提供了自动groovy函数,让用户自定义转换函数。详情请看DataX3的transformer详细介绍。

3、精准的速度控制

还在为同步过程对在线存储压力影响而担心吗?新版本DataX3.0提供了包括通道(并发)、记录流、字节流三种流控模式,可以随意控制你的作业速度,让你的作业在库可以承受的范围内达到最佳的同步速度。

 

"speed": {
  "channel": 8,    ----并发数限速(根据自己CPU合理控制并发数)
  "byte": 524288,  ----字节流限速(根据自己的磁盘和网络合理控制字节数)
  "record": 10000  ----记录流限速(根据数据合理空行数)
}
4、强劲的同步性能

DataX3.0每一种读插件都有一种或多种切分策略,都能将作业合理切分成多个Task并行执行,单机多线程执行模型可以让DataX速度随并发成线性增长。在源端和目的端性能都足够的情况下,单个作业一定可以打满网卡。另外,DataX团队对所有的已经接入的插件都做了极致的性能优化,并且做了完整的性能测试。

5、健壮的容错机制

DataX作业是极易受外部因素的干扰,网络闪断、数据源不稳定等因素很容易让同步到一半的作业报错停止。因此稳定性是DataX的基本要求,在DataX 3.0的设计中,重点完善了框架和插件的稳定性。目前DataX3.0可以做到线程级别、进程级别(暂时未开放)、作业级别多层次局部/全局的重试,保证用户的作业稳定运行。 线程内部重试

DataX的核心插件都经过团队的全盘review,不同的网络交互方式都有不同的重试策略。

6、线程级别重试

目前DataX已经可以实现TaskFailover,针对于中间失败的Task,DataX框架可以做到整个Task级别的重新调度。

第二章、datax实战

0.环境

1.JDK安装配置【完美】

##### 卸载默认环境

yum -y remove java-1.8.0-openjdk*        
yum -y remove tzdata-java*

# 下载安装依赖

yum install -y java-1.8.0-openjdk java-1.8.0-openjdk-devel

# 查到系统默认安装jdk的位置/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.222.b10-0.el7_6.x86_64/jre/bin/java

[root@ansible-server randolph]# ls -lrt /usr/bin/java
lrwxrwxrwx 1 root root 22 8月  23 13:42 /usr/bin/java -> /etc/alternatives/java
[root@ansible-server randolph]# ls -lrt /etc/alternatives/java
lrwxrwxrwx 1 root root 73 8月  23 13:42 /etc/alternatives/java -> /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.222.b10-0.el7_6.x86_64/jre/bin/java

# 加到环境变量

vim /etc/profile

# jdk

export JAVA_HOME=/usr/lib/jvm/java-1.8.0
export JRE_HOME=$JAVA_HOME/jre  
export PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib

source /etc/profile

# 检测

java -version
javac

2.Maven安装配置——用于编译git clone下来的源码

cd /opt/
wget https://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz
tar -zxvf apache-maven-3.6.3-bin.tar.gz
mv apache-maven-3.6.3/ maven # 改名方便操作
vim /etc/profile # 修改配置文件,并在末尾添加

# maven

M2_HOME=/opt/maven # 这里的路径注意下
export PATH=${M2_HOME}/bin:${PATH}

# 重载文件立即生效

source /etc/profile

检查maven安装是否成功
mvn -v
检查maven已经安装
[root@ansible-server opt]# mvn -v
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: /opt/maven
Java version: 1.8.0_222, vendor: Oracle Corporation, runtime: /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.222.b10-0.el7_6.x86_64/jre
Default locale: zh_CN, platform encoding: UTF-8
OS name: "linux", version: "3.10.0-957.27.2.el7.x86_64", arch: "amd64", family: "unix"

3.检查系统版本、python版本

实际上有python2/3都OK,后面datax的bin文件夹下的三个py文件datax.py也有python2/3版本,在执行命令的时候只需要对应即可。

[root@client-1 ~]# cat /etc/redhat-release
CentOS Linux release 7.6.1810 (Core)
[root@ansible-server DataX]# java -version
openjdk version "1.8.0_232"
OpenJDK Runtime Environment (build 1.8.0_232-b09)
OpenJDK 64-Bit Server VM (build 25.232-b09, mixed mode)
[root@ansible-server ~]# python -V
Python 2.7.15
[root@ansible-server ~]# python3 -V
Python 3.6.8

1.DataX安装部署

git clone https://github.com/alibaba/DataX.git
cd DataX
mvn -U clean package assembly:assembly -Dmaven.test.skip=true # maven打包

在这里插入图片描述

等待编译好久… 竟然编译了39分钟!!!

留个纪念:

[INFO] datax/lib/slf4j-api-1.7.10.jar already added, skipping
[INFO] datax/lib/logback-classic-1.0.13.jar already added, skipping
[INFO] datax/lib/logback-core-1.0.13.jar already added, skipping
[INFO] datax/lib/commons-math3-3.1.1.jar already added, skipping
[INFO] datax/lib/hamcrest-core-1.3.jar already added, skipping
[WARNING] Assembly file: /opt/DataX/target/datax is not a regular file (it may be a directory). It cannot be attached to the project build for installation or deployment.
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for datax-all 0.0.1-SNAPSHOT:
[INFO]
[INFO] datax-all .......................................... SUCCESS [03:23 min]
[INFO] datax-common ....................................... SUCCESS [ 22.458 s]
[INFO] datax-transformer .................................. SUCCESS [ 25.734 s]
[INFO] datax-core ......................................... SUCCESS [01:02 min]
[INFO] plugin-rdbms-util .................................. SUCCESS [ 20.741 s]
[INFO] mysqlreader ........................................ SUCCESS [  1.096 s]
[INFO] drdsreader ......................................... SUCCESS [  2.539 s]
[INFO] sqlserverreader .................................... SUCCESS [  1.885 s]
[INFO] postgresqlreader ................................... SUCCESS [  5.029 s]
[INFO] oraclereader ....................................... SUCCESS [  1.047 s]
[INFO] odpsreader ......................................... SUCCESS [ 43.033 s]
[INFO] otsreader .......................................... SUCCESS [ 31.965 s]
[INFO] otsstreamreader .................................... SUCCESS [ 16.498 s]
[INFO] plugin-unstructured-storage-util ................... SUCCESS [03:08 min]
[INFO] txtfilereader ...................................... SUCCESS [  5.172 s]
[INFO] hdfsreader ......................................... SUCCESS [06:13 min]
[INFO] streamreader ....................................... SUCCESS [  1.028 s]
[INFO] ossreader .......................................... SUCCESS [ 12.157 s]
[INFO] ftpreader .......................................... SUCCESS [  6.172 s]
[INFO] mongodbreader ...................................... SUCCESS [  9.056 s]
[INFO] rdbmsreader ........................................ SUCCESS [  1.186 s]
[INFO] hbase11xreader ..................................... SUCCESS [04:06 min]
[INFO] hbase094xreader .................................... SUCCESS [02:57 min]
[INFO] tsdbreader ......................................... SUCCESS [  7.664 s]
[INFO] opentsdbreader ..................................... SUCCESS [02:45 min]
[INFO] cassandrareader .................................... SUCCESS [ 35.874 s]
[INFO] mysqlwriter ........................................ SUCCESS [  0.861 s]
[INFO] drdswriter ......................................... SUCCESS [  1.111 s]
[INFO] odpswriter ......................................... SUCCESS [  1.895 s]
[INFO] txtfilewriter ...................................... SUCCESS [  3.672 s]
[INFO] ftpwriter .......................................... SUCCESS [  3.045 s]
[INFO] hdfswriter ......................................... SUCCESS [  6.932 s]
[INFO] streamwriter ....................................... SUCCESS [  0.863 s]
[INFO] otswriter .......................................... SUCCESS [  1.609 s]
[INFO] oraclewriter ....................................... SUCCESS [  1.247 s]
[INFO] sqlserverwriter .................................... SUCCESS [  0.855 s]
[INFO] postgresqlwriter ................................... SUCCESS [  1.073 s]
[INFO] osswriter .......................................... SUCCESS [  3.064 s]
[INFO] mongodbwriter ...................................... SUCCESS [  3.227 s]
[INFO] adswriter .......................................... SUCCESS [ 20.076 s]
[INFO] ocswriter .......................................... SUCCESS [ 37.687 s]
[INFO] rdbmswriter ........................................ SUCCESS [  1.196 s]
[INFO] hbase11xwriter ..................................... SUCCESS [  6.453 s]
[INFO] hbase094xwriter .................................... SUCCESS [  4.315 s]
[INFO] hbase11xsqlwriter .................................. SUCCESS [03:24 min]
[INFO] hbase11xsqlreader .................................. SUCCESS [02:09 min]
[INFO] elasticsearchwriter ................................ SUCCESS [ 21.244 s]
[INFO] tsdbwriter ......................................... SUCCESS [  1.477 s]
[INFO] adbpgwriter ........................................ SUCCESS [ 24.980 s]
[INFO] gdbwriter .......................................... SUCCESS [01:30 min]
[INFO] cassandrawriter .................................... SUCCESS [  5.859 s]
[INFO] hbase20xsqlreader .................................. SUCCESS [02:00 min]
[INFO] hbase20xsqlwriter .................................. SUCCESS [  1.659 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  39:28 min
[INFO] Finished at: 2019-12-12T14:39:51+08:00
[INFO] ------------------------------------------------------------------------
[root@ansible-server DataX]#

 

4.DataX使用/踩坑

1.配置一个简单例子

做什么好呢,正好手上有三个虚机absible-server client-1 client-2; 我们将absible-server mysql数据库test.user同步到client-1数据库test.user吧;

配置的任务是json格式的,我们假设任务的配置文件叫做mysql2mysql.json 需要用datax.py执行,看一眼datax.py就知道项目是python2的; mysql2mysql.json位置在上一层目录job文件夹下存放;

1.如何跑任务呢:
python /opt/DataX/target/datax/datax/bin/datax.py /opt/DataX/target/datax/datax/job/mysql2mysql.json

成功的话会是这样的:

在这里插入图片描述

但是我相信你需要仔细看一下任务的json文件该如何去配置

2.配置文件格式说明:

可以看到job分为reader和writer两部分,正体现了datax的架构特征: 阿里云开源离线同步工具DataX3.0介绍

在这里插入图片描述

部分参数说明:

jdbcUrl jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8 里面需要配置源IP/目标IP:数据库端口/数据库名?characterEncoding=utf8 控制编码格式,防止写入中文出现错误 username 这个用户,需要在数据库中创建,并赋予其增删改查等权限 创建的可以用来读取主库/修改从库的用户,我直接使用的root用户,并且后面试错后给root用户【允许所有远程机器访问】的权限 参考MySQL用户授权(GRANT) /opt/DataX/target/datax/datax/job/mysql2mysql.json :

{
   "job": {
       "content": [
          {
               "reader": {
                   "name": "mysqlreader",
                   "parameter": {
                       "column": [
                           "id",
                           "name"
                      ],
                       "connection": [
                          {
                               "jdbcUrl": ["jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8"],
                               "table": ["user"]
                          }
                      ],
"password": "asdf",
"username": "root"
                  }
              },
               "writer": {
                   "name": "mysqlwriter",
                   "parameter": {
                       "column": [
                       "id",
                       "name"
                      ],
                       "connection": [
                          {
                               "jdbcUrl": "jdbc:mysql://192.168.255.134:3306/test?characterEncoding=utf8",
                               "table": ["user"]
                          }
                      ],
"password": "asdf",
      "username": "root"
                  }
              }
          }
      ],
       "setting": {
           "speed": {
               "channel": "1"
          }
      }
  }
}

上面的文件配置完,就可以用datax.py跑这个json文件了,当然,我第一次跑,遇到了几处错误

3.跑任务出错与测试

1.Host ‘ansible-server’ is not allowed to connect to this MySQL server 原因:mysql服务器出于安全考虑,默认只允许本地登录数据库服务器 因此主服务器133不能用root访问从服务器数据库:

[root@ansible-server job]# mysql -h192.168.255.134 -uroot -p
Enter password:
ERROR 1130 (HY000): Host '192.168.255.133' is not allowed to connect to this MySQL server

从服务器将mysql的root用户的host从"localhost"改成"%",允许所有远程端访问: 主服务器的root用户也作同样修改,进入mysql库,看下root的host,然后改成%,确认下:

mysql> use mysql;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> select host, user from user;
+-----------+---------------+
| host     | user         |
+-----------+---------------+
| localhost | mysql.session |
| localhost | mysql.sys     |
| localhost | root         |
+-----------+---------------+
3 rows in set (0.28 sec)

mysql> update user set host = '%' where user = 'root';
Query OK, 1 row affected (0.59 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> flush privileges;
Query OK, 0 rows affected (0.14 sec)

mysql> select host, user from user;
+-----------+---------------+
| host     | user         |
+-----------+---------------+
| %         | root         |
| localhost | mysql.session |
| localhost | mysql.sys     |
+-----------+---------------+
3 rows in set (0.00 sec)

 

2.跑同步任务成功【但中文插入错误】

检查下有没有将主库的test.user表同步到从库134的test.user表: 出现了错误,将中文数据显示错误,jdbcurl末尾加上?characterEncoding=utf8 主库test.user表插入一条数据:

在这里插入图片描述

[root@ansible-server job]# python /opt/DataX/target/datax/datax/bin/datax.py /opt/DataX/target/datax/datax/job/mysql2mysql.json

DataX (DATAX-OPENSOURCE-3.0), From Alibaba !
Copyright (C) 2010-2017, Alibaba Group. All Rights Reserved.


2019-12-12 17:04:18.115 [main] INFO VMInfo - VMInfo# operatingSystem class => sun.management.OperatingSystemImpl
2019-12-12 17:04:18.122 [main] INFO Engine - the machine info  =>

osInfo: Oracle Corporation 1.8 25.232-b09
jvmInfo: Linux amd64 3.10.0-957.27.2.el7.x86_64
cpu num: 4

totalPhysicalMemory: -0.00G
freePhysicalMemory: -0.00G
maxFileDescriptorCount: -1
currentOpenFileDescriptorCount: -1

GC Names [PS MarkSweep, PS Scavenge]

MEMORY_NAME                   | allocation_size               | init_size                      
PS Eden Space                 | 256.00MB                       | 256.00MB                      
Code Cache                     | 240.00MB                       | 2.44MB                        
Compressed Class Space         | 1,024.00MB                     | 0.00MB                        
PS Survivor Space             | 42.50MB                       | 42.50MB                        
PS Old Gen                     | 683.00MB                       | 683.00MB                      
Metaspace                     | -0.00MB                       | 0.00MB                        

2019-12-12 17:04:18.143 [main] INFO Engine -
{
"content":[
{
"reader":{
"name":"mysqlreader",
"parameter":{
"column":[
"id",
"name"
],
"connection":[
{
"jdbcUrl":[
"jdbc:mysql://127.0.0.1:3306/test"
],
"table":[
"user"
]
}
],
"password":"****",
"username":"root"
}
},
"writer":{
"name":"mysqlwriter",
"parameter":{
"column":[
"id",
"name"
],
"connection":[
{
"jdbcUrl":"jdbc:mysql://192.168.255.134:3306/test",
"table":[
"user"
]
}
],
"password":"****",
"username":"root"
}
}
}
],
"setting":{
"speed":{
"channel":"1"
}
}
}

2019-12-12 17:04:18.161 [main] WARN Engine - prioriy set to 0, because NumberFormatException, the value is: null
2019-12-12 17:04:18.163 [main] INFO PerfTrace - PerfTrace traceId=job_-1, isEnable=false, priority=0
2019-12-12 17:04:18.164 [main] INFO JobContainer - DataX jobContainer starts job.
2019-12-12 17:04:18.165 [main] INFO JobContainer - Set jobId = 0
2019-12-12 17:04:18.467 [job-0] INFO OriginalConfPretreatmentUtil - Available jdbcUrl:jdbc:mysql://127.0.0.1:3306/test?yearIsDateType=false&zeroDateTimeBehavior=convertToNull&tinyInt1isBit=false&rewriteBatchedStatements=true.
2019-12-12 17:04:18.484 [job-0] INFO OriginalConfPretreatmentUtil - table:[user] has columns:[id,name].
2019-12-12 17:04:18.930 [job-0] INFO OriginalConfPretreatmentUtil - table:[user] all columns:[
id,name
].
2019-12-12 17:04:18.967 [job-0] INFO OriginalConfPretreatmentUtil - Write data [
INSERT INTO %s (id,name) VALUES(?,?)
], which jdbcUrl like:[jdbc:mysql://192.168.255.134:3306/test?yearIsDateType=false&zeroDateTimeBehavior=convertToNull&tinyInt1isBit=false&rewriteBatchedStatements=true]
2019-12-12 17:04:18.967 [job-0] INFO JobContainer - jobContainer starts to do prepare ...
2019-12-12 17:04:18.968 [job-0] INFO JobContainer - DataX Reader.Job [mysqlreader] do prepare work .
2019-12-12 17:04:18.968 [job-0] INFO JobContainer - DataX Writer.Job [mysqlwriter] do prepare work .
2019-12-12 17:04:18.968 [job-0] INFO JobContainer - jobContainer starts to do split ...
2019-12-12 17:04:18.969 [job-0] INFO JobContainer - Job set Channel-Number to 1 channels.
2019-12-12 17:04:18.973 [job-0] INFO JobContainer - DataX Reader.Job [mysqlreader] splits to [1] tasks.
2019-12-12 17:04:18.974 [job-0] INFO JobContainer - DataX Writer.Job [mysqlwriter] splits to [1] tasks.
2019-12-12 17:04:18.990 [job-0] INFO JobContainer - jobContainer starts to do schedule ...
2019-12-12 17:04:18.993 [job-0] INFO JobContainer - Scheduler starts [1] taskGroups.
2019-12-12 17:04:18.996 [job-0] INFO JobContainer - Running by standalone Mode.
2019-12-12 17:04:19.007 [taskGroup-0] INFO TaskGroupContainer - taskGroupId=[0] start [1] channels for [1] tasks.
2019-12-12 17:04:19.010 [taskGroup-0] INFO Channel - Channel set byte_speed_limit to -1, No bps activated.
2019-12-12 17:04:19.010 [taskGroup-0] INFO Channel - Channel set record_speed_limit to -1, No tps activated.
2019-12-12 17:04:19.019 [taskGroup-0] INFO TaskGroupContainer - taskGroup[0] taskId[0] attemptCount[1] is started
2019-12-12 17:04:19.024 [0-0-0-reader] INFO CommonRdbmsReader$Task - Begin to read record by Sql: [select id,name from user
] jdbcUrl:[jdbc:mysql://127.0.0.1:3306/test?yearIsDateType=false&zeroDateTimeBehavior=convertToNull&tinyInt1isBit=false&rewriteBatchedStatements=true].
2019-12-12 17:04:19.043 [0-0-0-reader] INFO CommonRdbmsReader$Task - Finished read record by Sql: [select id,name from user
] jdbcUrl:[jdbc:mysql://127.0.0.1:3306/test?yearIsDateType=false&zeroDateTimeBehavior=convertToNull&tinyInt1isBit=false&rewriteBatchedStatements=true].
2019-12-12 17:04:19.226 [0-0-0-writer] WARN CommonRdbmsWriter$Task - 回滚此次写入, 采用每次写入一行方式提交. 因为:Duplicate entry '1' for key 'PRIMARY'
2019-12-12 17:04:19.243 [0-0-0-writer] ERROR StdoutPluginCollector -
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '1' for key 'PRIMARY'
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.8.0_232]
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[na:1.8.0_232]
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:1.8.0_232]
at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ~[na:1.8.0_232]
at com.mysql.jdbc.Util.handleNewInstance(Util.java:377) ~[mysql-connector-java-5.1.34.jar:5.1.34]
at com.mysql.jdbc.Util.getInstance(Util.java:360) ~[mysql-connector-java-5.1.34.jar:5.1.34]
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:971) ~[mysql-connector-java-5.1.34.jar:5.1.34]
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3887) ~[mysql-connector-java-5.1.34.jar:5.1.34]
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3823) ~[mysql-connector-java-5.1.34.jar:5.1.34]
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2435) ~[mysql-connector-java-5.1.34.jar:5.1.34]
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2582) ~[mysql-connector-java-5.1.34.jar:5.1.34]
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2530) ~[mysql-connector-java-5.1.34.jar:5.1.34]
at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1907) ~[mysql-connector-java-5.1.34.jar:5.1.34]
at com.mysql.jdbc.PreparedStatement.execute(PreparedStatement.java:1199) ~[mysql-connector-java-5.1.34.jar:5.1.34]
at com.alibaba.datax.plugin.rdbms.writer.CommonRdbmsWriter$Task.doOneInsert(CommonRdbmsWriter.java:382) [plugin-rdbms-util-0.0.1-SNAPSHOT.jar:na]
at com.alibaba.datax.plugin.rdbms.writer.CommonRdbmsWriter$Task.doBatchInsert(CommonRdbmsWriter.java:362) [plugin-rdbms-util-0.0.1-SNAPSHOT.jar:na]
at com.alibaba.datax.plugin.rdbms.writer.CommonRdbmsWriter$Task.startWriteWithConnection(CommonRdbmsWriter.java:297) [plugin-rdbms-util-0.0.1-SNAPSHOT.jar:na]
at com.alibaba.datax.plugin.rdbms.writer.CommonRdbmsWriter$Task.startWrite(CommonRdbmsWriter.java:319) [plugin-rdbms-util-0.0.1-SNAPSHOT.jar:na]
at com.alibaba.datax.plugin.writer.mysqlwriter.MysqlWriter$Task.startWrite(MysqlWriter.java:78) [mysqlwriter-0.0.1-SNAPSHOT.jar:na]
at com.alibaba.datax.core.taskgroup.runner.WriterRunner.run(WriterRunner.java:56) [datax-core-0.0.1-SNAPSHOT.jar:na]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_232]
2019-12-12 17:04:19.246 [0-0-0-writer] ERROR StdoutPluginCollector - 脏数据:
{"exception":"Duplicate entry '1' for key 'PRIMARY'","record":[{"byteSize":1,"index":0,"rawData":1,"type":"LONG"},{"byteSize":3,"index":1,"rawData":"cad","type":"STRING"}],"type":"writer"}
2019-12-12 17:04:19.257 [0-0-0-writer] ERROR StdoutPluginCollector - 脏数据:
{"exception":"Duplicate entry '8' for key 'PRIMARY'","record":[{"byteSize":1,"index":0,"rawData":8,"type":"LONG"},{"byteSize":3,"index":1,"rawData":"半同步","type":"STRING"}],"type":"writer"}
2019-12-12 17:04:19.263 [0-0-0-writer] ERROR StdoutPluginCollector - 脏数据:
{"exception":"Duplicate entry '9' for key 'PRIMARY'","record":[{"byteSize":1,"index":0,"rawData":9,"type":"LONG"},{"byteSize":4,"index":1,"rawData":"影响性能","type":"STRING"}],"type":"writer"}
2019-12-12 17:04:19.267 [0-0-0-writer] ERROR StdoutPluginCollector - 脏数据:
{"exception":"Duplicate entry '10' for key 'PRIMARY'","record":[{"byteSize":2,"index":0,"rawData":10,"type":"LONG"},{"byteSize":8,"index":1,"rawData":"数据量小没啥区别","type":"STRING"}],"type":"writer"}
2019-12-12 17:04:19.270 [0-0-0-writer] ERROR StdoutPluginCollector - 脏数据:
{"exception":"Duplicate entry '11' for key 'PRIMARY'","record":[{"byteSize":2,"index":0,"rawData":11,"type":"LONG"},{"byteSize":4,"index":1,"rawData":"雨女无瓜","type":"STRING"}],"type":"writer"}
2019-12-12 17:04:19.285 [0-0-0-writer] ERROR StdoutPluginCollector - 脏数据:
{"exception":"Duplicate entry '12' for key 'PRIMARY'","record":[{"byteSize":2,"index":0,"rawData":12,"type":"LONG"},{"byteSize":3,"index":1,"rawData":"马冬梅","type":"STRING"}],"type":"writer"}
2019-12-12 17:04:19.289 [0-0-0-writer] ERROR StdoutPluginCollector - 脏数据:
{"exception":"Duplicate entry '13' for key 'PRIMARY'","record":[{"byteSize":2,"index":0,"rawData":13,"type":"LONG"},{"byteSize":4,"index":1,"rawData":"马什么梅","type":"STRING"}],"type":"writer"}
2019-12-12 17:04:19.294 [0-0-0-writer] ERROR StdoutPluginCollector - 脏数据:
{"exception":"Duplicate entry '14' for key 'PRIMARY'","record":[{"byteSize":2,"index":0,"rawData":14,"type":"LONG"},{"byteSize":4,"index":1,"rawData":"马冬什么","type":"STRING"}],"type":"writer"}
2019-12-12 17:04:19.297 [0-0-0-writer] ERROR StdoutPluginCollector - 脏数据:
{"exception":"Duplicate entry '15' for key 'PRIMARY'","record":[{"byteSize":2,"index":0,"rawData":15,"type":"LONG"},{"byteSize":4,"index":1,"rawData":"什么冬梅","type":"STRING"}],"type":"writer"}
2019-12-12 17:04:19.523 [taskGroup-0] INFO TaskGroupContainer - taskGroup[0] taskId[0] is successed, used[508]ms
2019-12-12 17:04:19.524 [taskGroup-0] INFO TaskGroupContainer - taskGroup[0] completed it's tasks.
2019-12-12 17:04:29.015 [job-0] INFO StandAloneJobContainerCommunicator - Total 11 records, 60 bytes | Speed 6B/s, 1 records/s | Error 10 records, 56 bytes | All Task WaitWriterTime 0.000s | All Task WaitReaderTime 0.000s | Percentage 100.00%
2019-12-12 17:04:29.016 [job-0] INFO AbstractScheduler - Scheduler accomplished all tasks.
2019-12-12 17:04:29.016 [job-0] INFO JobContainer - DataX Writer.Job [mysqlwriter] do post work.
2019-12-12 17:04:29.016 [job-0] INFO JobContainer - DataX Reader.Job [mysqlreader] do post work.
2019-12-12 17:04:29.016 [job-0] INFO JobContainer - DataX jobId [0] completed successfully.
2019-12-12 17:04:29.017 [job-0] INFO HookInvoker - No hook invoked, because base dir not exists or is a file: /opt/DataX/target/datax/datax/hook
2019-12-12 17:04:29.018 [job-0] INFO JobContainer -
[total cpu info] =>
averageCpu                     | maxDeltaCpu                   | minDeltaCpu                    
-1.00%                         | -1.00%                         | -1.00%
                       

[total gc info] =>
NAME                 | totalGCCount       | maxDeltaGCCount   | minDeltaGCCount   | totalGCTime       | maxDeltaGCTime     | minDeltaGCTime    
PS MarkSweep         | 0                 | 0                 | 0                 | 0.000s             | 0.000s             | 0.000s            
PS Scavenge         | 0                 | 0                 | 0                 | 0.000s             | 0.000s             | 0.000s            

2019-12-12 17:04:29.018 [job-0] INFO JobContainer - PerfTrace not enable!
2019-12-12 17:04:29.019 [job-0] INFO StandAloneJobContainerCommunicator - Total 11 records, 60 bytes | Speed 6B/s, 1 records/s | Error 10 records, 56 bytes | All Task WaitWriterTime 0.000s | All Task WaitReaderTime 0.000s | Percentage 100.00%
2019-12-12 17:04:29.020 [job-0] INFO JobContainer -
任务启动时刻                   : 2019-12-12 17:04:18
任务结束时刻                   : 2019-12-12 17:04:29
任务总计耗时                   :                 10s
任务平均流量                   :               6B/s
记录写入速度                   :             1rec/s
读出记录总数                   :                  11
读写失败总数                   :                  10

从库结果:

在这里插入图片描述

再次尝试: 但是之前错误的没有改正

在这里插入图片描述

相较于mysql自带的主从复制功能,这里阿里的DataX同步“主从库”不会因为数据不一致而直接同步不成功,IO线程挂掉,而是选择忽略,接着同步其他数据。蛮像Noteability软件的。

5.结合crontabs,使DataX可以配置定时任务

1.确保安装crontabs
yum install crontabs
# crontabs-1.11-6.20121102git.el7.noarch 已安装并且是最新版本

常用命令:

service crond start     # 启服务
service crond stop # 停服务
service crond restart # 重启服务
service crond reload # 重载配置
service crond status # 查看服务状态
crontab -l # 查看当前用户的定时任务
2.如何创建crontab定时任务

我们在/opt/DataX/target/datax/datax/job下创建定时任务文件,因为是crontabs+datax,就叫做crondatax吧: vim crondatax

30,31,32,33  19 * * *  python /opt/DataX/target/datax/datax/bin/datax.py /opt/DataX/target/datax/datax/job/mysql2mysql.json  >>/opt/DataX/target/datax/datax/job/log.`date +\%Y\%m\%d\%H\%M\%S`  2>&1

格式说明:参照下图,解释下上面的测试例子:19时的30,31,32,33分分别做一次这个任务python /opt/DataX/target/datax/datax/bin/datax.py /opt/DataX/target/datax/datax/job/mysql2mysql.json,然后将每次执行的日志记录下,下图的log.xxxx就是,内容不赘述,都成功了。 填写说明:再新建一个任务,就在crondatax文件换一行写入即可。

在这里插入图片描述

在这里插入图片描述

跑任务,看下有没有:

在这里插入图片描述

跑起來了,这主库加的一条:

在这里插入图片描述

还未刷新navicat:

在这里插入图片描述

刷新,数据已经同步进来了,【我手速太慢,截图】:

在这里插入图片描述

第三章、datax面试

一、DataX介绍

 DataX 是阿里巴巴集团内被广泛使用的离线数据同步工具/平台,实现包括 MySQL、SQL Server、Oracle、PostgreSQL、HDFS、Hive、HBase、OTS、ODPS 等各种异构数据源之间高效的数据同步功能。 DataX本身作为数据同步框架,将不同数据源的同步抽象为从源头数据源读取数据的Reader插件,以及向目标端写入数据的Writer插件,理论上DataX框架可以支持任意数据源类型的数据同步工作。同时DataX插件体系作为一套生态系统, 每接入一套新数据源该新加入的数据源即可实现和现有的数据源互通。

 

二、Job&Task概念

 在DataX的逻辑模型中包括job、task两个维度,通过将job进行task拆分,然后将task合并到taskGroup进行运行。

  • job实例运行在jobContainer容器中,它是所有任务的master,负责初始化、拆分、调度、运行、回收、监控和汇报,但它并不做实际的数据同步操作。

  • Job: Job是DataX用以描述从一个源头到一个目的端的同步作业,是DataX数据同步的最小业务单元。比如:从一张mysql的表同步到odps的一个表的特定分区。

  • Task: Task是为最大化而把Job拆分得到的最小执行单元。比如:读一张有1024个分表的mysql分库分表的Job,拆分成1024个读Task,用若干个并发执行。

  • TaskGroup: 描述的是一组Task集合。在同一个TaskGroupContainer执行下的Task集合称之为TaskGroup。

  • JobContainer: Job执行器,负责Job全局拆分、调度、前置语句和后置语句等工作的工作单元。类似Yarn中的JobTracker。

  • TaskGroupContainer: TaskGroup执行器,负责执行一组Task的工作单元,类似Yarn中的TaskTracker。

  • 简而言之, Job拆分成Task,在分别在框架提供的容器中执行,插件只需要实现Job和Task两部分逻辑。

 

三、启动过程

img

说明:

  • 上图中,黄色表示Job部分的执行阶段,蓝色表示Task部分的执行阶段,绿色表示框架执行阶段。

 

 

img

说明:

  • reader和writer的自定义插件内部需要实现job和task的接口即可

 

四、DataX开启Debug

 阅读源码的最好方法是debug整个项目工程,在如何调试DataX项目的过程中还是花费了一些精力在里面的,现在一并共享出来供有兴趣的程序员一并研究。 整个debug过程需要按照下列步骤进行:

  • 1、github上下载DataX的源码并通过以下命令进行编译,github官网有编译命令,如果遇到依赖包无法下载可以省去部分writer或reader插件,不影响debug。

 

(1)、下载DataX源码:

$ git clone git@github.com:alibaba/DataX.git
(2)、通过maven打包:

$ cd {DataX_source_code_home}
$ mvn -U clean package assembly:assembly -Dmaven.test.skip=true
打包成功,日志显示如下:

[INFO] BUILD SUCCESS
[INFO] -----------------------------------------------------------------
[INFO] Total time: 08:12 min
[INFO] Finished at: 2015-12-13T16:26:48+08:00
[INFO] Final Memory: 133M/960M
[INFO] -----------------------------------------------------------------
打包成功后的DataX包位于 {DataX_source_code_home}/target/datax/datax/ ,结构如下:

$ cd {DataX_source_code_home}
$ ls ./target/datax/datax/
bin     conf        job     lib     log     log_perf    plugin      script      tmp
  • 2、由于DataX是通过python脚本进行启动的,所以在python脚本中把启动参数打印出来,核心在于print startCommand这句,继而我们就能够获取启动命令参数了。

 

if __name__ == "__main__":
   printCopyright()
   parser = getOptionParser()
   options, args = parser.parse_args(sys.argv[1:])
   if options.reader is not None and options.writer is not None:
       generateJobConfigTemplate(options.reader,options.writer)
       sys.exit(RET_STATE['OK'])
   if len(args) != 1:
       parser.print_help()
       sys.exit(RET_STATE['FAIL'])

   startCommand = buildStartCommand(options, args)
   print startCommand

   child_process = subprocess.Popen(startCommand, shell=True)
   register_signal()
  (stdout, stderr) = child_process.communicate()

   sys.exit(child_process.returncode)
  • 3、获取启动DataX的启动命令

 

java -server  -Xms1g -Xmx1g -XX:+HeapDumpOnOutOfMemoryError 
-XX:HeapDumpPath=//Users/lebron374/Documents/github/DataX/target/datax/datax/log
-Dloglevel=info -Dfile.encoding=UTF-8
-Dlogback.statusListenerClass=ch.qos.logback.core.status.NopStatusListener
-Djava.security.egd=file:///dev/urandom -Ddatax.home=//Users/lebron374/Documents/github/DataX/target/datax/datax
-Dlogback.configurationFile=//Users/lebron374/Documents/github/DataX/target/datax/datax/conf/logback.xml
-classpath //Users/lebron374/Documents/github/DataX/target/datax/datax/lib/*:.
-Dlog.file.name=s_datax_job_job_json
com.alibaba.datax.core.Engine

-mode standalone -jobid -1
-job //Users/lebron374/Documents/github/DataX/target/datax/datax/job/job.json
  • 4、配置Idea启动脚本

img

 

以下配置在VM options当中
-server  -Xms1g -Xmx1g -XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=//Users/lebron374/Documents/github/DataX/target/datax/datax/log
-Dloglevel=info -Dfile.encoding=UTF-8
-Dlogback.statusListenerClass=ch.qos.logback.core.status.NopStatusListener
-Djava.security.egd=file:///dev/urandom -Ddatax.home=//Users/lebron374/Documents/github/DataX/target/datax/datax
-Dlogback.configurationFile=//Users/lebron374/Documents/github/DataX/target/datax/datax/conf/logback.xml
-classpath //Users/lebron374/Documents/github/DataX/target/datax/datax/lib/*:.
-Dlog.file.name=s_datax_job_job_json
com.alibaba.datax.core.Engine

以下配置在Program arguments当中
-mode standalone -jobid -1
-job //Users/lebron374/Documents/github/DataX/target/datax/datax/job/job.json

 

五、启动步骤解析

  • 1、解析配置,包括job.json、core.json、plugin.json三个配置

  • 2、设置jobId到configuration当中

  • 3、启动Engine,通过Engine.start()进入启动程序

  • 4、设置RUNTIME_MODE奥configuration当中

  • 5、通过JobContainer的start()方法启动

  • 6、依次执行job的preHandler()、init()、prepare()、split()、schedule()、- post()、postHandle()等方法。

  • 7、init()方法涉及到根据configuration来初始化reader和writer插件,这里涉及到jar包热加载以及调用插件init()操作方法,同时设置reader和writer的configuration信息

  • 8、prepare()方法涉及到初始化reader和writer插件的初始化,通过调用插件的prepare()方法实现,每个插件都有自己的jarLoader,通过集成URLClassloader实现而来

  • 9、split()方法通过adjustChannelNumber()方法调整channel个数,同时执行reader和writer最细粒度的切分,需要注意的是,writer的切分结果要参照reader的切分结果,达到切分后数目相等,才能满足1:1的通道模型

  • 10、channel的计数主要是根据byte和record的限速来实现的,在split()的函数中第一步就是计算channel的大小

  • 11、split()方法reader插件会根据channel的值进行拆分,但是有些reader插件可能不会参考channel的值,writer插件会完全根据reader的插件1:1进行返回

  • 12、split()方法内部的mergeReaderAndWriterTaskConfigs()负责合并reader、writer、以及transformer三者关系,生成task的配置,并且重写job.content的配置

  • 13、schedule()方法根据split()拆分生成的task配置分配生成taskGroup对象,根据task的数量和单个taskGroup支持的task数量进行配置,两者相除就可以得出taskGroup的数量

  • 14、schdule()内部通过AbstractScheduler的schedule()执行,继续执行startAllTaskGroup()方法创建所有的TaskGroupContainer组织相关的task,TaskGroupContainerRunner负责运行TaskGroupContainer执行分配的task。

  • 15、taskGroupContainerExecutorService启动固定的线程池用以执行TaskGroupContainerRunner对象,TaskGroupContainerRunner的run()方法调用taskGroupContainer.start()方法,针对每个channel创建一个TaskExecutor,通过taskExecutor.doStart()启动任务

 

六、启动过程源码分析

1、入口main函数

 

public class Engine {

   public static void main(String[] args) throws Exception {
       int exitCode = 0;
       try {
           Engine.entry(args);
      } catch (Throwable e) {
           System.exit(exitCode);
      }
  }

   public static void entry(final String[] args) throws Throwable {

       // 省略相关参数的解析代码
       
       // 获取job的配置路径信息
       String jobPath = cl.getOptionValue("job");

       // 如果用户没有明确指定jobid, 则 datax.py 会指定 jobid 默认值为-1
       String jobIdString = cl.getOptionValue("jobid");
       RUNTIME_MODE = cl.getOptionValue("mode");

       // 解析配置信息
       Configuration configuration = ConfigParser.parse(jobPath);

       // 省略相关代码
       boolean isStandAloneMode = "standalone".equalsIgnoreCase(RUNTIME_MODE);
       configuration.set(CoreConstant.DATAX_CORE_CONTAINER_JOB_ID, jobId);

       // 根据配置启动参数
       Engine engine = new Engine();
       engine.start(configuration);
  }
}

说明: main函数主要做两件事情,分别是:

  • 1、解析job相关配置生成configuration。

  • 2、依据配置启动Engine。

 

2、configuration解析过程

 

public final class ConfigParser {
   private static final Logger LOG = LoggerFactory.getLogger(ConfigParser.class);
   /**
    * 指定Job配置路径,ConfigParser会解析Job、Plugin、Core全部信息,并以Configuration返回
    */
   public static Configuration parse(final String jobPath) {
       // 加载任务的指定的配置文件,这个配置是有固定的json的固定模板格式的
       Configuration configuration = ConfigParser.parseJobConfig(jobPath);

       // 合并conf/core.json的配置文件
       configuration.merge(
               ConfigParser.parseCoreConfig(CoreConstant.DATAX_CONF_PATH),
               false);

       // todo config优化,只捕获需要的plugin
       // 固定的节点路径 job.content[0].reader.name
       String readerPluginName = configuration.getString(
               CoreConstant.DATAX_JOB_CONTENT_READER_NAME);
       // 固定的节点路径 job.content[0].writer.name
       String writerPluginName = configuration.getString(
               CoreConstant.DATAX_JOB_CONTENT_WRITER_NAME);

       // 固定的节点路径 job.preHandler.pluginName
       String preHandlerName = configuration.getString(
               CoreConstant.DATAX_JOB_PREHANDLER_PLUGINNAME);

       // 固定的节点路径 job.postHandler.pluginName
       String postHandlerName = configuration.getString(
               CoreConstant.DATAX_JOB_POSTHANDLER_PLUGINNAME);

       // 添加读写插件的列表待加载
       Set<String> pluginList = new HashSet<String>();
       pluginList.add(readerPluginName);
       pluginList.add(writerPluginName);

       if(StringUtils.isNotEmpty(preHandlerName)) {
           pluginList.add(preHandlerName);
      }
       if(StringUtils.isNotEmpty(postHandlerName)) {
           pluginList.add(postHandlerName);
      }
       try {
           // parsePluginConfig(new ArrayList<String>(pluginList))加载指定的插件的配置信息,并且和全局的配置文件进行合并
           configuration.merge(parsePluginConfig(new ArrayList<String>(pluginList)), false);
      }catch (Exception e){
      }

       // configuration整合了三方的配置,包括 任务配置、core核心配置、指定插件的配置。
       return configuration;
  }


   // 在指定的reader和writer目录获取指定的插件并解析其配置
   public static Configuration parsePluginConfig(List<String> wantPluginNames) {
       // 创建一个空的配置信息对象
       Configuration configuration = Configuration.newDefault();

       Set<String> replicaCheckPluginSet = new HashSet<String>();
       int complete = 0;
       // 所有的reader在/plugin/reader目录,遍历获取所有reader的目录
       // 获取待加载插件的配资信息,并合并到上面创建的空配置对象
       // //Users/lebron374/Documents/github/DataX/target/datax/datax/plugin/reader
       for (final String each : ConfigParser
              .getDirAsList(CoreConstant.DATAX_PLUGIN_READER_HOME)) {

           // 解析单个reader目录,eachReaderConfig保存的是key是plugin.reader.pluginname,value是对应的plugin.json内容
           Configuration eachReaderConfig = ConfigParser.parseOnePluginConfig(each, "reader", replicaCheckPluginSet, wantPluginNames);
           if(eachReaderConfig!=null) {
               // 采用覆盖式的合并
               configuration.merge(eachReaderConfig, true);
               complete += 1;
          }
      }

       // //Users/lebron374/Documents/github/DataX/target/datax/datax/plugin/writer
       for (final String each : ConfigParser
              .getDirAsList(CoreConstant.DATAX_PLUGIN_WRITER_HOME)) {
           Configuration eachWriterConfig = ConfigParser.parseOnePluginConfig(each, "writer", replicaCheckPluginSet, wantPluginNames);
           if(eachWriterConfig!=null) {
               configuration.merge(eachWriterConfig, true);
               complete += 1;
          }
      }

       if (wantPluginNames != null && wantPluginNames.size() > 0 && wantPluginNames.size() != complete) {
           throw DataXException.asDataXException(FrameworkErrorCode.PLUGIN_INIT_ERROR, "插件加载失败,未完成指定插件加载:" + wantPluginNames);
      }

       return configuration;
  }
}

说明: configuration解析包括三部分的配置解析合并解析结果并返回,分别是:

  • 1、解析job的配置信息,由启动参数指定job.json文件。

  • 2、解析DataX自带配置信息,由默认指定的core.json文件。

  • 3、解析读写插件配置信息,由job.json指定的reader和writer插件信息

 

3、configuration配置信息

job.json的configuration

 

{
   "job": {
       "setting": {
           "speed": {
               "byte":10485760,
               "record":1000
          },
           "errorLimit": {
               "record": 0,
               "percentage": 0.02
          }
      },
       "content": [
          {
               "reader": {
                   "name": "streamreader",
                   "parameter": {
                       "column" : [
                          {
                               "value": "DataX",
                               "type": "string"
                          },
                          {
                               "value": 19890604,
                               "type": "long"
                          },
                          {
                               "value": "1989-06-04 00:00:00",
                               "type": "date"
                          },
                          {
                               "value": true,
                               "type": "bool"
                          },
                          {
                               "value": "test",
                               "type": "bytes"
                          }
                      ],
                       "sliceRecordCount": 100000
                  }
              },
               "writer": {
                   "name": "streamwriter",
                   "parameter": {
                       "print": false,
                       "encoding": "UTF-8"
                  }
              }
          }
      ]
  }
}

 

core.json的configuration

 

{
   "entry": {
       "jvm": "-Xms1G -Xmx1G",
       "environment": {}
  },
   "common": {
       "column": {
           "datetimeFormat": "yyyy-MM-dd HH:mm:ss",
           "timeFormat": "HH:mm:ss",
           "dateFormat": "yyyy-MM-dd",
           "extraFormats":["yyyyMMdd"],
           "timeZone": "GMT+8",
           "encoding": "utf-8"
      }
  },
   "core": {
       "dataXServer": {
           "address": "http://localhost:7001/api",
           "timeout": 10000,
           "reportDataxLog": false,
           "reportPerfLog": false
      },
       "transport": {
           "channel": {
               "class": "com.alibaba.datax.core.transport.channel.memory.MemoryChannel",
               "speed": {
                   "byte": 100,
                   "record": 10
              },
               "flowControlInterval": 20,
               "capacity": 512,
               "byteCapacity": 67108864
          },
           "exchanger": {
               "class": "com.alibaba.datax.core.plugin.BufferedRecordExchanger",
               "bufferSize": 32
          }
      },
       "container": {
           "job": {
               "reportInterval": 10000
          },
           "taskGroup": {
               "channel": 5
          },
           "trace": {
               "enable": "false"
          }

      },
       "statistics": {
           "collector": {
               "plugin": {
                   "taskClass": "com.alibaba.datax.core.statistics.plugin.task.StdoutPluginCollector",
                   "maxDirtyNumber": 10
              }
          }
      }
  }
}

 

plugin.json的configuration

 

{
   "name": "streamreader",
   "class": "com.alibaba.datax.plugin.reader.streamreader.StreamReader",
   "description": {
       "useScene": "only for developer test.",
       "mechanism": "use datax framework to transport data from stream.",
       "warn": "Never use it in your real job."
  },
   "developer": "alibaba"
}


{
   "name": "streamwriter",
   "class": "com.alibaba.datax.plugin.writer.streamwriter.StreamWriter",
   "description": {
       "useScene": "only for developer test.",
       "mechanism": "use datax framework to transport data to stream.",
       "warn": "Never use it in your real job."
  },
   "developer": "alibaba"
}

 

合并后的configuration

 

{
   "common": {
       "column": {
           "dateFormat": "yyyy-MM-dd",
           "datetimeFormat": "yyyy-MM-dd HH:mm:ss",
           "encoding": "utf-8",
           "extraFormats": ["yyyyMMdd"],
           "timeFormat": "HH:mm:ss",
           "timeZone": "GMT+8"
      }
  },
   "core": {
       "container": {
           "job": {
               "id": -1,
               "reportInterval": 10000
          },
           "taskGroup": {
               "channel": 5
          },
           "trace": {
               "enable": "false"
          }
      },
       "dataXServer": {
           "address": "http://localhost:7001/api",
           "reportDataxLog": false,
           "reportPerfLog": false,
           "timeout": 10000
      },
       "statistics": {
           "collector": {
               "plugin": {
                   "maxDirtyNumber": 10,
                   "taskClass": "com.alibaba.datax.core.statistics.plugin.task.StdoutPluginCollector"
              }
          }
      },
       "transport": {
           "channel": {
               "byteCapacity": 67108864,
               "capacity": 512,
               "class": "com.alibaba.datax.core.transport.channel.memory.MemoryChannel",
               "flowControlInterval": 20,
               "speed": {
                   "byte": -1,
                   "record": -1
              }
          },
           "exchanger": {
               "bufferSize": 32,
               "class": "com.alibaba.datax.core.plugin.BufferedRecordExchanger"
          }
      }
  },
   "entry": {
       "jvm": "-Xms1G -Xmx1G"
  },
   "job": {
       "content": [{
           "reader": {
               "name": "streamreader",
               "parameter": {
                   "column": [{
                       "type": "string",
                       "value": "DataX"
                  }, {
                       "type": "long",
                       "value": 19890604
                  }, {
                       "type": "date",
                       "value": "1989-06-04 00:00:00"
                  }, {
                       "type": "bool",
                       "value": true
                  }, {
                       "type": "bytes",
                       "value": "test"
                  }],
                   "sliceRecordCount": 100000
              }
          },
           "writer": {
               "name": "streamwriter",
               "parameter": {
                   "encoding": "UTF-8",
                   "print": false
              }
          }
      }],
       "setting": {
           "errorLimit": {
               "percentage": 0.02,
               "record": 0
          },
           "speed": {
               "byte": 10485760
          }
      }
  },
   "plugin": {
       "reader": {
           "streamreader": {
               "class": "com.alibaba.datax.plugin.reader.streamreader.StreamReader",
               "description": {
                   "mechanism": "use datax framework to transport data from stream.",
                   "useScene": "only for developer test.",
                   "warn": "Never use it in your real job."
              },
               "developer": "alibaba",
               "name": "streamreader",
               "path": "//Users/lebron374/Documents/github/DataX/target/datax/datax/plugin/reader/streamreader"
          }
      },
       "writer": {
           "streamwriter": {
               "class": "com.alibaba.datax.plugin.writer.streamwriter.StreamWriter",
               "description": {
                   "mechanism": "use datax framework to transport data to stream.",
                   "useScene": "only for developer test.",
                   "warn": "Never use it in your real job."
              },
               "developer": "alibaba",
               "name": "streamwriter",
               "path": "//Users/lebron374/Documents/github/DataX/target/datax/datax/plugin/writer/streamwriter"
          }
      }
  }
}

 

4、Engine的start过程

 

public class Engine {
   private static final Logger LOG = LoggerFactory.getLogger(Engine.class);

   private static String RUNTIME_MODE;

   /* check job model (job/task) first */
   public void start(Configuration allConf) {

       // 省略相关代码

       boolean isJob = !("taskGroup".equalsIgnoreCase(allConf
              .getString(CoreConstant.DATAX_CORE_CONTAINER_MODEL)));
       AbstractContainer container;
       if (isJob) {
           allConf.set(CoreConstant.DATAX_CORE_CONTAINER_JOB_MODE, RUNTIME_MODE);
           // 核心点在于JobContainer的对象
           container = new JobContainer(allConf);
           instanceId = allConf.getLong(
                   CoreConstant.DATAX_CORE_CONTAINER_JOB_ID, 0);

      }

       Configuration jobInfoConfig = allConf.getConfiguration(CoreConstant.DATAX_JOB_JOBINFO);

       // 核心容器的启动
       container.start();

  }

说明: start过程中做了两件事:

  • 1、创建JobContainer对象

  • 2、启动JobContainer对象

 

5、JobContainer的启动过程

 

public class JobContainer extends AbstractContainer {
   /**
    * jobContainer主要负责的工作全部在start()里面,包括init、prepare、split、scheduler、
    * post以及destroy和statistics
    */
   @Override
   public void start() {
       try {
           isDryRun = configuration.getBool(CoreConstant.DATAX_JOB_SETTING_DRYRUN, false);
           if(isDryRun) {
               // 省略相关代码
          } else {

               //拷贝一份新的配置,保证线程安全
               userConf = configuration.clone();

               // 执行preHandle()操作
               LOG.debug("jobContainer starts to do preHandle ...");
               this.preHandle();

               // 执行reader、transform、writer等初始化
               LOG.debug("jobContainer starts to do init ...");
               this.init();

               // 执行plugin的prepare
               LOG.info("jobContainer starts to do prepare ...");
               this.prepare();

               // 执行任务切分
               LOG.info("jobContainer starts to do split ...");
               this.totalStage = this.split();

               // 执行任务调度
               LOG.info("jobContainer starts to do schedule ...");
               this.schedule();

               // 执行后置操作
               LOG.debug("jobContainer starts to do post ...");
               this.post();

               // 执行postHandle操作
               LOG.debug("jobContainer starts to do postHandle ...");
               this.postHandle();

               LOG.info("DataX jobId [{}] completed successfully.", this.jobId);

               this.invokeHooks();
          }
      } catch (Throwable e) {
           // 省略相关代码
      } finally {
            // 省略相关代码
      }
  }
}

说明: JobContainer的start方法会执行一系列job相关的操作,如下:

  • 1、执行job的preHandle()操作,暂时不关注。

  • 2、执行job的init()操作,需重点关注。

  • 3、执行job的prepare()操作,暂时不关注。

  • 4、执行job的split()操作,需重点关注。

  • 5、执行job的schedule()操作,需重点关注。

  • 6、执行job的post()和postHandle()操作,暂时不关注。

 

6、Job的init过程

 

public class JobContainer extends AbstractContainer {
   private void init() {
       this.jobId = this.configuration.getLong(
               CoreConstant.DATAX_CORE_CONTAINER_JOB_ID, -1);

       if (this.jobId < 0) {
           LOG.info("Set jobId = 0");
           this.jobId = 0;
           this.configuration.set(CoreConstant.DATAX_CORE_CONTAINER_JOB_ID,
                   this.jobId);
      }

       Thread.currentThread().setName("job-" + this.jobId);

       // 初始化
       JobPluginCollector jobPluginCollector = new DefaultJobPluginCollector(
               this.getContainerCommunicator());

       //必须先Reader ,后Writer
       this.jobReader = this.initJobReader(jobPluginCollector);
       this.jobWriter = this.initJobWriter(jobPluginCollector);
  }


   private Reader.Job initJobReader(
           JobPluginCollector jobPluginCollector) {

       // 获取插件名字
       this.readerPluginName = this.configuration.getString(
               CoreConstant.DATAX_JOB_CONTENT_READER_NAME);

       classLoaderSwapper.setCurrentThreadClassLoader(LoadUtil.getJarLoader(
               PluginType.READER, this.readerPluginName));

       Reader.Job jobReader = (Reader.Job) LoadUtil.loadJobPlugin(
               PluginType.READER, this.readerPluginName);

       // 设置reader的jobConfig
       jobReader.setPluginJobConf(this.configuration.getConfiguration(
               CoreConstant.DATAX_JOB_CONTENT_READER_PARAMETER));

       // 设置reader的readerConfig
       jobReader.setPeerPluginJobConf(this.configuration.getConfiguration(
               CoreConstant.DATAX_JOB_CONTENT_WRITER_PARAMETER));

       jobReader.setJobPluginCollector(jobPluginCollector);

       // 这里已经到每个插件具体的初始化操作
       jobReader.init();

       classLoaderSwapper.restoreCurrentThreadClassLoader();
       return jobReader;
  }


   private Writer.Job initJobWriter(
           JobPluginCollector jobPluginCollector) {
       this.writerPluginName = this.configuration.getString(
               CoreConstant.DATAX_JOB_CONTENT_WRITER_NAME);
       classLoaderSwapper.setCurrentThreadClassLoader(LoadUtil.getJarLoader(
               PluginType.WRITER, this.writerPluginName));

       Writer.Job jobWriter = (Writer.Job) LoadUtil.loadJobPlugin(
               PluginType.WRITER, this.writerPluginName);

       // 设置writer的jobConfig
       jobWriter.setPluginJobConf(this.configuration.getConfiguration(
               CoreConstant.DATAX_JOB_CONTENT_WRITER_PARAMETER));

       // 设置reader的readerConfig
       jobWriter.setPeerPluginJobConf(this.configuration.getConfiguration(
               CoreConstant.DATAX_JOB_CONTENT_READER_PARAMETER));

       jobWriter.setPeerPluginName(this.readerPluginName);
       jobWriter.setJobPluginCollector(jobPluginCollector);
       jobWriter.init();
       classLoaderSwapper.restoreCurrentThreadClassLoader();

       return jobWriter;
  }
}

说明: Job的init()过程主要做了两个事情,分别是:

  • 1、创建reader的job对象,通过URLClassLoader实现类加载。

  • 2、创建writer的job对象,通过URLClassLoader实现类加载。

 

7、job的split过程

 

public class JobContainer extends AbstractContainer {
   private int split() {
       this.adjustChannelNumber();

       if (this.needChannelNumber <= 0) {
           this.needChannelNumber = 1;
      }

       List<Configuration> readerTaskConfigs = this
              .doReaderSplit(this.needChannelNumber);
       int taskNumber = readerTaskConfigs.size();
       List<Configuration> writerTaskConfigs = this
              .doWriterSplit(taskNumber);

       List<Configuration> transformerList = this.configuration.getListConfiguration(CoreConstant.DATAX_JOB_CONTENT_TRANSFORMER);

       LOG.debug("transformer configuration: "+ JSON.toJSONString(transformerList));
       /**
        * 输入是reader和writer的parameter list,输出是content下面元素的list
        */
       List<Configuration> contentConfig = mergeReaderAndWriterTaskConfigs(
               readerTaskConfigs, writerTaskConfigs, transformerList);


       LOG.debug("contentConfig configuration: "+ JSON.toJSONString(contentConfig));

       this.configuration.set(CoreConstant.DATAX_JOB_CONTENT, contentConfig);

       return contentConfig.size();
  }


   private void adjustChannelNumber() {
       int needChannelNumberByByte = Integer.MAX_VALUE;
       int needChannelNumberByRecord = Integer.MAX_VALUE;

       boolean isByteLimit = (this.configuration.getInt(
               CoreConstant.DATAX_JOB_SETTING_SPEED_BYTE, 0) > 0);
       if (isByteLimit) {
           long globalLimitedByteSpeed = this.configuration.getInt(
                   CoreConstant.DATAX_JOB_SETTING_SPEED_BYTE, 10 * 1024 * 1024);
           Long channelLimitedByteSpeed = this.configuration
                  .getLong(CoreConstant.DATAX_CORE_TRANSPORT_CHANNEL_SPEED_BYTE);
           needChannelNumberByByte =
                  (int) (globalLimitedByteSpeed / channelLimitedByteSpeed);
           needChannelNumberByByte =
                   needChannelNumberByByte > 0 ? needChannelNumberByByte : 1;
           LOG.info("Job set Max-Byte-Speed to " + globalLimitedByteSpeed + " bytes.");
      }

       boolean isRecordLimit = (this.configuration.getInt(
               CoreConstant.DATAX_JOB_SETTING_SPEED_RECORD, 0)) > 0;
       if (isRecordLimit) {
           long globalLimitedRecordSpeed = this.configuration.getInt(
                   CoreConstant.DATAX_JOB_SETTING_SPEED_RECORD, 100000);

           Long channelLimitedRecordSpeed = this.configuration.getLong(
                   CoreConstant.DATAX_CORE_TRANSPORT_CHANNEL_SPEED_RECORD);

           needChannelNumberByRecord =
                  (int) (globalLimitedRecordSpeed / channelLimitedRecordSpeed);
           needChannelNumberByRecord =
                   needChannelNumberByRecord > 0 ? needChannelNumberByRecord : 1;
      }

       // 取较小值
       this.needChannelNumber = needChannelNumberByByte < needChannelNumberByRecord ?
               needChannelNumberByByte : needChannelNumberByRecord;

       boolean isChannelLimit = (this.configuration.getInt(
               CoreConstant.DATAX_JOB_SETTING_SPEED_CHANNEL, 0) > 0);
       if (isChannelLimit) {
           this.needChannelNumber = this.configuration.getInt(
                   CoreConstant.DATAX_JOB_SETTING_SPEED_CHANNEL);

           LOG.info("Job set Channel-Number to " + this.needChannelNumber
                   + " channels.");

           return;
      }

       throw DataXException.asDataXException(
               FrameworkErrorCode.CONFIG_ERROR,
               "Job运行速度必须设置");
  }
}

说明: DataX的job的split过程主要是根据限流配置计算channel的个数,进而计算task的个数,主要过程如下:

  • 1、adjustChannelNumber的过程根据按照字节限流和record限流计算channel的个数。

  • 2、reader的个数根据channel的个数进行计算。

  • 3、writer的个数根据reader的个数进行计算,writer和reader实现1:1绑定。

  • 4、通过mergeReaderAndWriterTaskConfigs()方法生成reader+writer的task的configuration,至此我们生成了task的配置信息。

 

8、Job的schedule过程

 

public class JobContainer extends AbstractContainer {

   private void schedule() {
       /**
        * 通过获取配置信息得到每个taskGroup需要运行哪些tasks任务
        */
       List<Configuration> taskGroupConfigs = JobAssignUtil.assignFairly(this.configuration,
               this.needChannelNumber, channelsPerTaskGroup);

       ExecuteMode executeMode = null;
       AbstractScheduler scheduler;
       try {
           executeMode = ExecuteMode.STANDALONE;
           scheduler = initStandaloneScheduler(this.configuration);

           //设置 executeMode
           for (Configuration taskGroupConfig : taskGroupConfigs) {
               taskGroupConfig.set(CoreConstant.DATAX_CORE_CONTAINER_JOB_MODE, executeMode.getValue());
          }

           // 开始调度所有的taskGroup
           scheduler.schedule(taskGroupConfigs);

      } catch (Exception e) {
           // 省略相关代码
      }
  }

}

说明: Job的schedule的过程主要做了两件事,分别是:

  • 1、将task拆分成taskGroup,生成List<Configuration> taskGroupConfigs。

  • 2、启动taskgroup的对象, scheduler.schedule(taskGroupConfigs)。

 

9、TaskGroup的schedule过程

 

public abstract class AbstractScheduler {
 
   public void schedule(List<Configuration> configurations) {
       
       int totalTasks = calculateTaskCount(configurations);
       
       // 启动所有的TaskGroup
       startAllTaskGroup(configurations);
       try {
           while (true) {
               // 省略相关代码
          }
      } catch (InterruptedException e) {
      }
  }
}



public abstract class ProcessInnerScheduler extends AbstractScheduler {

   private ExecutorService taskGroupContainerExecutorService;

   @Override
   public void startAllTaskGroup(List<Configuration> configurations) {

       //todo 根据taskGroup的数量启动固定的线程数
       this.taskGroupContainerExecutorService = Executors
              .newFixedThreadPool(configurations.size());

       //todo 每个TaskGroup启动一个TaskGroupContainerRunner
       for (Configuration taskGroupConfiguration : configurations) {
           //todo 创建TaskGroupContainerRunner并提交线程池运行
           TaskGroupContainerRunner taskGroupContainerRunner = newTaskGroupContainerRunner(taskGroupConfiguration);
           this.taskGroupContainerExecutorService.execute(taskGroupContainerRunner);
      }

       // 等待所有任务执行完后会关闭,执行该方法后不会再接收新任务
       this.taskGroupContainerExecutorService.shutdown();
  }
}



public class TaskGroupContainerRunner implements Runnable {

   private TaskGroupContainer taskGroupContainer;

   private State state;

   public TaskGroupContainerRunner(TaskGroupContainer taskGroup) {
       this.taskGroupContainer = taskGroup;
       this.state = State.SUCCEEDED;
  }

   @Override
   public void run() {
       try {
           Thread.currentThread().setName(
                   String.format("taskGroup-%d", this.taskGroupContainer.getTaskGroupId()));
           this.taskGroupContainer.start();
           this.state = State.SUCCEEDED;
      } catch (Throwable e) {
      }
  }
}

说明: TaskGroup的Schedule方法做的事情如下:

  • 1、为所有的TaskGroup创建TaskGroupContainerRunner。

  • 2、通过线程池提交TaskGroupContainerRunner任务,执行TaskGroupContainerRunner的run()方法。

  • 3、在run()方法内部执行this.taskGroupContainer.start()方法。

 

10、TaskGroupContainer的启动

 

public class TaskGroupContainer extends AbstractContainer {

   @Override
   public void start() {
       try {
            // 省略相关代码

           int taskCountInThisTaskGroup = taskConfigs.size();
           Map<Integer, Configuration> taskConfigMap = buildTaskConfigMap(taskConfigs); //taskId与task配置
           List<Configuration> taskQueue = buildRemainTasks(taskConfigs); //待运行task列表
           Map<Integer, TaskExecutor> taskFailedExecutorMap = new HashMap<Integer, TaskExecutor>(); //taskId与上次失败实例
           List<TaskExecutor> runTasks = new ArrayList<TaskExecutor>(channelNumber); //正在运行task
           Map<Integer, Long> taskStartTimeMap = new HashMap<Integer, Long>(); //任务开始时间

           while (true) {
               // 省略相关代码
               
               // 新增任务会在这里被启动
               Iterator<Configuration> iterator = taskQueue.iterator();
               while(iterator.hasNext() && runTasks.size() < channelNumber){
                   Configuration taskConfig = iterator.next();
                   Integer taskId = taskConfig.getInt(CoreConstant.TASK_ID);
                   int attemptCount = 1;
                   TaskExecutor lastExecutor = taskFailedExecutorMap.get(taskId);

                   // todo 需要新建任务的配置信息
                   Configuration taskConfigForRun = taskMaxRetryTimes > 1 ? taskConfig.clone() : taskConfig;

                   // todo taskExecutor应该就需要新建的任务
                   TaskExecutor taskExecutor = new TaskExecutor(taskConfigForRun, attemptCount);
                   taskStartTimeMap.put(taskId, System.currentTimeMillis());
                   taskExecutor.doStart();

                   iterator.remove();
                   runTasks.add(taskExecutor);
                 
              }
      } catch (Throwable e) {
      }finally {
      }
  }
}

说明: TaskGroupContainer的内部主要做的事情如下:

  • 1、根据TaskGroupContainer分配的Task任务列表,创建TaskExecutor对象。

  • 2、创建TaskExecutor对象,用以启动分配该TaskGroup的task。

  • 3、至此,已经成功的启动了Job当中的Task任务。

11、Task的启动

 

    class TaskExecutor {
       
       private Channel channel;
       private Thread readerThread;
       private Thread writerThread;
       private ReaderRunner readerRunner;
       private WriterRunner writerRunner;

       /**
        * 该处的taskCommunication在多处用到:
        * 1. channel
        * 2. readerRunner和writerRunner
        * 3. reader和writer的taskPluginCollector
        */
       public TaskExecutor(Configuration taskConf, int attemptCount) {
           // 获取该taskExecutor的配置
           this.taskConfig = taskConf;

           // 得到taskId
           this.taskId = this.taskConfig.getInt(CoreConstant.TASK_ID);
           this.attemptCount = attemptCount;

           /**
            * 由taskId得到该taskExecutor的Communication
            * 要传给readerRunner和writerRunner,同时要传给channel作统计用
            */
           this.channel = ClassUtil.instantiate(channelClazz,
                   Channel.class, configuration);
           // channel在这里生成,每个taskGroup生成一个channel,在generateRunner方法当中生成writer或reader并注入channel
           this.channel.setCommunication(this.taskCommunication);

           /**
            * 获取transformer的参数
            */

           List<TransformerExecution> transformerInfoExecs = TransformerUtil.buildTransformerInfo(taskConfig);

           /**
            * 生成writerThread
            */
           writerRunner = (WriterRunner) generateRunner(PluginType.WRITER);
           this.writerThread = new Thread(writerRunner,
                   String.format("%d-%d-%d-writer",
                           jobId, taskGroupId, this.taskId));
           //通过设置thread的contextClassLoader,即可实现同步和主程序不通的加载器
           this.writerThread.setContextClassLoader(LoadUtil.getJarLoader(
                   PluginType.WRITER, this.taskConfig.getString(
                           CoreConstant.JOB_WRITER_NAME)));

           /**
            * 生成readerThread
            */
           readerRunner = (ReaderRunner) generateRunner(PluginType.READER,transformerInfoExecs);
           this.readerThread = new Thread(readerRunner,
                   String.format("%d-%d-%d-reader",
                           jobId, taskGroupId, this.taskId));
           /**
            * 通过设置thread的contextClassLoader,即可实现同步和主程序不通的加载器
            */
           this.readerThread.setContextClassLoader(LoadUtil.getJarLoader(
                   PluginType.READER, this.taskConfig.getString(
                           CoreConstant.JOB_READER_NAME)));
      }

       public void doStart() {
           this.writerThread.start();
           this.readerThread.start();
      }
}

说明: TaskExecutor的启动过程主要做了以下事情:

  • 1、创建了reader和writer的线程任务,reader和writer公用一个channel。

  • 2、先启动writer线程后,再启动reader线程。

  • 3、至此,同步数据的Task任务已经启动了。

 

七、DataX的数据传输

 跟一般的生产者-消费者模式一样,Reader插件和Writer插件之间也是通过channel来实现数据的传输的。channel可以是内存的,也可能是持久化的,插件不必关心。插件通过RecordSender往channel写入数据,通过RecordReceiver从channel读取数据。

 channel中的一条数据为一个Record的对象,Record中可以放多个Column对象,这可以简单理解为数据库中的记录和列。

 

public class DefaultRecord implements Record {

   private static final int RECORD_AVERGAE_COLUMN_NUMBER = 16;

   private List<Column> columns;

   private int byteSize;

   // 首先是Record本身需要的内存
   private int memorySize = ClassSize.DefaultRecordHead;

   public DefaultRecord() {
       this.columns = new ArrayList<Column>(RECORD_AVERGAE_COLUMN_NUMBER);
  }

   @Override
   public void addColumn(Column column) {
       columns.add(column);
       incrByteSize(column);
  }

   @Override
   public Column getColumn(int i) {
       if (i < 0 || i >= columns.size()) {
           return null;
      }
       return columns.get(i);
  }

   @Override
   public void setColumn(int i, final Column column) {
       if (i < 0) {
           throw DataXException.asDataXException(FrameworkErrorCode.ARGUMENT_ERROR,
                   "不能给index小于0的column设置值");
      }

       if (i >= columns.size()) {
           expandCapacity(i + 1);
      }

       decrByteSize(getColumn(i));
       this.columns.set(i, column);
       incrByteSize(getColumn(i));
  }
}
 posted on 2020-05-12 15:56  大码王  阅读(3048)  评论(0编辑  收藏  举报
复制代码