DataX 源码导读

源码调试

VM Options: 
-Ddatax.home=D:\workspaces\DataX\target\datax\datax
Args Options: 
-mode standalone -jobid -1 -job D:\datax-jobs\stream2stream.json
Main Lancher:
com.alibaba.datax.core.Engine
  • mysql2stream.json
{
  "job": {
    "content": [
      {
        "reader": {
          "name": "streamreader",
          "parameter": {
            "sliceRecordCount": 10,
            "column": [
              {
                "type": "long",
                "value": "10"
              },
              {
                "type": "string",
                "value": "hello,你好,世界-DataX"
              }
            ]
          }
        },
        "writer": {
          "name": "streamwriter",
          "parameter": {
            "encoding": "UTF-8",
            "print": true
          }
        }
      }
    ],
    "setting": {
      "speed": {
        "channel": 5
       }
    }
  }
}

DataX执行总体步骤

  • preHandle()
  • init() 需重点关注
  • prepare()
  • split() 需重点关注
  • schedule() 需重点关注
  • post()
  • postHandle()
  • invokeHooks()

init() 初始化过程

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

1、创建reader的job对象。

2、创建writer的job对象。

通过 组件类型【read/write/transformer】、组件名称、ContainerType【Job/task】三要素来确定唯一组件,通过ClassLoader 获取对应Class

e.g mysqlreader 可以找到路径为 d a t a x . h o m e / p l u g i n s / m y s q l r e a d e r / 并 加 载 J o b L o a d e r c l a s s p a t h , 从 中 查 找 名 为 c o m . a l i b a b a . d a t a x . p l u g i n . r e a d e r . m y s q l r e a d e r . M y s q l R e a d e r {datax.home}/plugins/mysqlreader/ 并加载 JobLoader classpath, 从中查找名为 com.alibaba.datax.plugin.reader.mysqlreader.MysqlReader datax.home/plugins/mysqlreader/JobLoaderclasspath,com.alibaba.datax.plugin.reader.mysqlreader.MysqlReaderJob 的 类, 并调用 getInstance() 方法进行实例的创建。init() 方法进行初始化

split() 任务切分

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的配置信息。

任务切分的主要目的是得到关键结果 totalStage

执行reader和writer最细粒度的切分,需要注意的是,writer的切分结果要参照reader的切分结果,达到切分后数目相等,才能满足1:1的通道模型,所以这里可以将reader和writer的配置整合到一起,然后,为避免顺序给读写端带来长尾影响,将整合的结果shuffler掉

  1. 计算所需通道数

com.alibaba.datax.core.job.JobContainer#adjustChannelNumber()

关键参数

属性配置参数默认值作用备注
globalLimitedByteSpeedjob.setting.speed.byte10 * 1024 * 1024总bps值
globalLimitedRecordSpeedjob.setting.speed.record100000总tps值
channelLimitedByteSpeedcore.transport.channel.speed.byte单个channel的bps值
channelLimitedRecordSpeedcore.transport.channel.speed.record单个channel的tps值
needChannelNumberjob.setting.speed.channel在以上四个参数的计算结果结果大于 231-1 时生效
/* com.alibaba.datax.core.job.JobContainer#adjustChannelNumber */
// 总bps限速 除以 单个channel的bps值 后取整
needChannelNumberByByte = (int) (globalLimitedByteSpeed / channelLimitedByteSpeed);
needChannelNumberByByte = needChannelNumberByByte > 0 ? needChannelNumberByByte : 1;
    
needChannelNumberByRecord = (int) (globalLimitedRecordSpeed / channelLimitedRecordSpeed);
needChannelNumberByRecord = needChannelNumberByRecord > 0 ? needChannelNumberByRecord : 1;

 // 取较小值
needChannelNumber = needChannelNumberByByte < needChannelNumberByRecord ?
    				needChannelNumberByByte : needChannelNumberByRecord;
  1. 进行Reader/Writer分割

    List<Configuration> com.alibaba.datax.core.job.JobContainer#doReaderSplit(int needChannelNumber);
    List<Configuration> com.alibaba.datax.core.job.JobContainer#doWriterSplit(int readerTaskNumber)
    

主要是通过调用加载的组件的split()方法进行各自的分割

与读分割操作不同的是,写分割的分割因子是 读分割其分割后的配置数量

List<Configuration> readerTaskConfigs = this.doReaderSplit(this.needChannelNumber);
int taskNumber = readerTaskConfigs.size();
List<Configuration> writerTaskConfigs = this.doWriterSplit(taskNumber);
  1. 合并Reader / Writer / transformer 分割后得到的 Configuration集合
List<Configuration> mergeReaderAndWriterTaskConfigs(
            List<Configuration> readerTasksConfigs,
            List<Configuration> writerTasksConfigs,
            List<Configuration> transformerConfigs) {
    //...
}

代码完成的事情主要是:

  • 校验 Reader 分割后配置集合 与 Writer 分割后配置集合是否数量相同
  • 合并配置
  • 返回合并后配置长度作为 totalStage

关键合并参数:

配置参数备注
reader.nameReader名称
reader.parameterReader配置信息
writer.nameWriter名称
writer.parameterWriter配置信息
transformerTransformer配置信息

schedule()

schedule首先完成的工作是把上一步reader和writer split的结果整合到具体taskGroupContainer中, 同时不同的执行模式调用不同的调度策略,将所有任务调度起来

关键参数

属性配置参数默认值作用备注
channelsPerTaskGroupcore.container.taskGroup.channel5
taskNumberjob.content配置的content个数
readerResourceMarkreader.parameter.loadBalanceResourceMarkaFakeResourceMarkForLoadBalance资源名称第一个Job Configuration 内的配置
writerResourceMarkwriter.parameter.loadBalanceResourceMark资源名称第一个Job Configuration 内的配置
  1. 任务分组

法:com.alibaba.datax.core.container.util.JobAssignUtil#assignFairly(Configuration configuration, int channelNumber, int channelsPerTaskGroup)

公平的分配 task 到对应的 taskGroup 中。

公平体现在:会考虑 task 中对资源负载作的 load 标识进行更均衡的作业分配操作。

// 该任务分组数量计算 
int taskGroupNumber = (int) Math.ceil(1.0 * needChannelNumber / channelsPerTaskGroup);
  • 根据task 配置,获取到:资源名称 --> taskId(List) 的 map 映射关系, 返回根据资源名称拆分数量较多的集合
/**
 * 根据task 配置,获取到:
 * 资源名称 --> taskId(List) 的 map 映射关系
 */
private static LinkedHashMap<String, List<Integer>> parseAndGetResourceMarkAndTaskIdMap(List<Configuration> contentConfig) {
  // ...
   if (readerResourceMarkAndTaskIdMap.size() >= writerResourceMarkAndTaskIdMap.size()) {
       // 采用 reader 对资源做的标记进行 shuffle
       return readerResourceMarkAndTaskIdMap;
   } else {
       // 采用 writer 对资源做的标记进行 shuffle
       return writerResourceMarkAndTaskIdMap;
   }
}
  • 分配任务
/**
 * /**
 * 需要实现的效果通过例子来说是:
 * <pre>
 * a 库上有表:0, 1, 2
 * a 库上有表:3, 4
 * c 库上有表:5, 6, 7
 *
 * 如果有 4个 taskGroup
 * 则 assign 后的结果为:
 * taskGroup-0: 0,  4,
 * taskGroup-1: 3,  6,
 * taskGroup-2: 5,  2,
 * taskGroup-3: 1,  7
 *
 * </pre>
 *
 * 任务分配
 * @param resourceMarkAndTaskIdMap 根据资源名称拆分数量较多的集合
 * @param jobConfiguration 任务配置
 * @param taskGroupNumber  任务分组数量
 * @return
 */
private static List<Configuration> doAssign(LinkedHashMap<String, List<Integer>> resourceMarkAndTaskIdMap, Configuration jobConfiguration, int taskGroupNumber) {
  // ...
}
  • 任务调度

初始化调度器 (com.alibaba.datax.core.job.scheduler.processinner.StandAloneScheduler)

方法:com.alibaba.datax.core.job.scheduler.AbstractScheduler#schedule

主要做的事情:

​ - 构建计数器 ErrorRecordChecker

​ - 给 taskGroupContainer 的 Communication 注册(监听者模式,可能用于信息上报等操作)

​ - 计算任务数

​ - 执行组任务(com.alibaba.datax.core.job.scheduler.processinner.ProcessInnerScheduler#startAllTaskGroup(List configurations))

​ - 任务上报/异常监控

  • checkLimit() 检查任务执行情况

通过之前注册的 Communication,进行异常检查

检查最终结果是否超出阈值,如果阈值设定小于1,则表示百分数阈值,大于1表示条数阈值。

post()

注意:此方法每个 Task 都会执行一次。
最佳实践:如果 Task 中有需要进行数据同步之后的后续处理,可以在此处完成。

postHandle()

从源代码看出,这个操作主要是将 DefaultJobPluginCollector 对象注入到了插件当中,后进行了 postHandler 操作,应用场景暂无。

invokeHooks() 调用外部hook

主要原理是 JavaSPI

扩展路径: ${datax.home}/hook

参考文档

  • DataX的执行流程分析 https://www.jianshu.com/p/b10fbdee7e56

  • 一看你就懂,超详细java中的ClassLoader详解 https://blog.csdn.net/briblue/article/details/54973413

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值