1.datax介绍
DataX 是阿里云 DataWorks数据集成 的开源版本,在阿里巴巴集团内被广泛使用的离线数据同步工具/平台。DataX 实现了包括 MySQL、Oracle、OceanBase、SqlServer、Postgre、HDFS、Hive、ADS、HBase、TableStore(OTS)、MaxCompute(ODPS)、Hologres、DRDS 等各种异构数据源之间高效的数据同步功能。
2.datax框架介绍
DataX本身作为离线数据同步框架,采用Framework + plugin架构构建。将数据源读取和写入抽象成为Reader/Writer插件,纳入到整个同步框架中。
- Reader:Reader为数据采集模块,负责采集数据源的数据,将数据发送给Framework。
- Writer: Writer为数据写入模块,负责不断向Framework取数据,并将数据写入到目的端。
- Framework:Framework用于连接reader和writer,作为两者的数据传输通道,并处理缓冲,流控,并发,数据转换等核心技术问题。
3.datax插件体系
经过几年积累,DataX目前已经有了比较全面的插件体系,主流的RDBMS数据库、NOSQL、大数据计算系统都已经接入。DataX目前支持数据如下:
类型 | 数据源 | Reader(读) | Writer(写) | 文档 |
RDBMS 关系型数据库 | MySQL | √ | √ | 读、写 |
RDBMS 关系型数据库 | Oracle | √ | √ | 读、写 |
RDBMS 关系型数据库 | OceanBase | √ | √ | 读、写 |
待补充
4.datax核心架构
DataX 3.0 开源版本支持单机多线程模式完成同步作业运行,本小节按一个DataX作业生命周期的时序图,从整体架构设计非常简要说明DataX各个模块相互关系。
4.1datax模块介绍
- 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
5.datax核心源码分析
5.1JobContainer核心源码分析
JobContainer实现了AbstractContainer的start方法。
方法如下,我们现在主要分析下schedule方法。
- 生成scheduler实例
这里的scheduler实现类是StandAloneScheduler。
- 调用schedule方法
- 分析startAllTaskGroup方法
① 首先创建固定数量的线程池
② 遍历任务组配置
③ 创建TaskGroupContainerRunner对象实例
点开TaskGroupContainerRunner可以看到,runner实现了Runnable接口,run方法实际调用的是TaskGroupContainer的start方法
④ 调用TaskGroupContainer.start方法
5.2TaskGroupContainer核心源码分析
这里我们不对每一行代码进行分析,主要分析下start方法。
前面的逻辑主要是一些初始化参数的配置。
我们重点分析这几行代码
① 创建并初始化TaskExecutor
TaskExecutor是一个完整的执行器,包括reader和writer以及传输通道channel。
② 启动器创建逻辑分析
③ 调用doStart方法
从源码可得知,doStart本质上就是启动了writerTread和readerThread线程。writerTread的Runnable是WriterRunner,readThread的Runnable是ReaderRunner。
5.3.ReaderRunner核心源码分析
ReaderRunner实现了Runnable接口,我们重点关注run方法即可。ReaderRunner的构造方法只有一个参数AbstractTaskPlugin,而AbstractTaskPlugin的实例是通过LoadUtil类加载器创建的,如下图:
LoadUtil.loadPluginClass底层其实就是通过反射技术获取plugin的实例,有兴趣的同学可以追踪下LoadUtil.getJarLoader的源码,我这里就不多做介绍了,下面我们重点分析run方法。
在开始具体分析之前,我们先看下下面的截图,可以很清晰的发现,核心方法就是startRead,事实上确实如此,基本上所有的reader插件最复杂的部分就是startRead方法的实现。话不多说,我们看下startReader的具体实现是什么样的,下面我以MysqlReader读取插件作为范例,给大家揭开datax读取插件的神秘面纱。
5.4 MysqlReader核心源码分析
先来一张图:
Datax给我们提供了一个模板,所有的读取插件都要实现Job、Task两个内部类。而MysqlReader继承Reader并实现了Job和Task两个内部类,如下图:
本质上,MysqlReader其实是调用了CommonRdbmsReader的方法。它没干啥事,有兴趣的同学可以看下其他的read插件,其实可以发现PostgresReader、OracleReader等关系型数据库都是一样的,都是调用的模板方法。
我们继续往下挖/(ㄒoㄒ)/~~
① Job内部类的实现
实现了init()、preCheck()、split(int adviceNumber)、post()、destory()方法。
- init方法解读
commonRdbmsReaderJob.init方法做了什么?我们点进去看下:
- preCheck方法解读
- post方法解读
- destroy方法解读
- split方法解读
主要是做切分工作,有兴趣的同学可以看下doSplit具体的实现。
② Task内部类的实现
Task内部的实现,主要实现了init和startRead方法,我们只介绍startRead方法。
- startRead方法解读
需要注意的是,每次读取到的数据,都会调用recordSender.sendToWriter方法。Record对象类型是通过core.transport.record.class属性控制的。
RecordSender的实现类一共有三个,分别是RecordExchanger、BufferedRecordExchanger、BufferedRecordTransformerExchanger。
A.RecordExchanger
在这里可以做校验或者转换。调用的是TransformerExchanger抽象类的doTransformer方法。然后调用channel.push方法推送至writer,从以上代码可以得出,channel是reader和writer之间数据沟通的桥梁。Channel的实现类只有一个MemoryChannel,底层使用的是ArrayBlockingQueue有界阻塞队列。
B.BufferedRecordExchanger
从字面意思上看,BufferedRecordExchanger是一个可以缓存的Record交互类。先判断缓存区是否满了,如果满了会调用flush方法,将所有的数据推送至channel,并清空缓存,重置下标和缓存大小。不管怎么操作,数据最终还是会通过channel流向writer插件。
C.BufferedRecordTransformerExchanger
BufferedRecordTransformerExchanger和BufferedRecordExchanger一样。
5.5.MysqlWriter核心源码分析
先来一张图:
可以看到,Writer模板和Reader模板比较类似,这里我们着重看下startWrite的实现。
① startWrite方法解读
A.调用DBUtil,根据数据库类型、用户、密码、连接URL获取连接。
B.处理session,这里有兴趣可以看到,我们着重介绍下面的方法。
C.startWriteWithConnection方法一个有三个参数,分别是RecordReceiver接收器、taskPluginCollector数据收集器,Connection数据库连接。
跟踪getFromReader方法:
其实就是从channel中获取数据,底层就是调用了阻塞队列的take方法。
跟踪doBatchInsert方法: