DataX--源码浅读(作业的执行流程)

本文详细阐述了DataX作业的执行过程,包括Job对象的初始化、全局与局部准备、任务切分、调度分配以及读写操作的具体步骤。DataX在处理不同读写插件时的差异性和通道模型的实现,如channel数量的计算与分配,以及reader和writer的启动、读写数据和后置工作的执行。此外,还介绍了如何通过命令行启动DataX作业以及内部的调度执行机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

DataX的执行顺序


init:Job对象初始化工作,获取与job有关的配置参数等

prepare:全局准备工作,比如清空目标表,清空hdfs目标文件

split:拆分Task

schedule:负责任务的调度分配。

init:task对象的初始化,获取与task相关的配置参数

prepare:局部的准备工作

startRead:从数据源读数据,写到RecordSender中。RecordSender再将数据写入连接Reader和Writer的缓存队列。

channel:Reader和Writer的缓存队列就由其来维护,是Reader和Writer进行数据传输的通道。

startWrite:从RecordReceiver中读取数据。RecordReceiver的数据读取自连接Reader和Writer的缓存队列。

post:局部的后置工作。

destory:task对象的销毁。

post:数据写完后的完善工作

destory:job对象的销毁

DataX一个普通作业的执行流程

一个作业由读写作业组成,不同的读写插件,在作业初始化、作业准备、作业的任务切分、作业的post和作业的销毁等的一些具体细节上是不同的。 这里没有具体到哪个读写插件的流程逻辑,只是大体上的一个流程。

  1. 命令行执行datax.py脚本,getOptionParser()将配置参数保存起来,然后将参数项和值解析后,交给buildStartCommand生成startCommand,其中一条ENGINE_COMMAND引擎启动指令,就是开启datax引擎的指令。subprocess.Popen()方法将指令通过shell执行。

  2. 通过command,运行Engine.main(), 调用自身的entry方法,将参数set到配置对象中;

  3. Engine.start()依据配置对象生成JobContainer对象,最后调用JobContainer.start()方法,去完成Job的整个工作流程;

  4. 先执行JobContainer.init(),完成Job对象初始化工作,即reader和writer的初始化。先调用initJobReader方法,在其内部调用相关读插件的init()对reader进行初始化;reader初始化完成之后,用相同的方式初始化writer;

  5. 接着执行JobContainer.prepare(),全局准备工作,先执行prepareJobReader(),在其内部调用读插件的prepare(),对读进行准备。相同的,调用写插件的prepare(),对写进行准备;

  6. 然后执行JobContainer.split(),在其中调用adjustChannelNumber,adjustChannelNumber计算Byte和Record的两者限制下的不同channel数量,并取其最小值作为channel数量。如果,Byte和Record都未设置,便启用配置参数中配置的channel数量;都未设置,则为1;但是,有些读插件会根据自身的判断,设置channel数,不会启用前面的方式来获取channel数量。

  7. 紧接着在JobContainer.split()方法内调用不同Reader的split()方法,并把channel数量作为adviceNumber切片建议数,传递给split();

  8. Reader.split(adviceNumber)会依据 不同策略(不同读插件可能不同) 切分出readerTask的数量,大都会切分出大于等于channel的任务数;

  9. JobContainer.split()再依据readertask数量调用doWriterSplit,进行writertask的切分,达到切分后两者数目相等,满足1:1的通道模型;

  10. 切分完成后,由JobContainer.schedule()来调度。先获取每个taskGroup的channel上限数量,由core.container.taskGroup.channel参数指定,如果未指定,则默认为5,再获取needChannelNumber;然后由assignFairly()函数分配任务给taskGroup,分配的方式为轮询

  11. 分配完成后,初始化一个scheduler。JobContainer.schedule()中调用scheduler.schedule()方法,依据任务组设置获取各种参数,调用startAllTaskGroup()方法,生成线程池;然后循环调用线程池的execute方法,执行任务,即执行TaskGroupContainerRunner.run()

  12. 由taskGroupContainer.start()方法启动线程,在其中先获取一些配置参数,开启一个循环,在其中创建TaskExecutor对象,即线程执行器对象。TaskExecutor对象构建时会生成writerThread和readerThread两个线程,在生成每个线程的前一步分别创建recordSender(用来写数据到channel的缓存队列)和recordReceiver(用来读channel的缓存队列)。然后通过taskExecutor.doStart()去执行reader线程和writer线程。

  13. 先启动writer线程,紧接着启动reader线程,两个线程分别执行他们的init()方法,进行task对象的初始化,获取与task相关的配置参数;

  14. 接着执行他们各自的prepare()方法,一般都是设置参数等的操作,不同插件可能不同;

  15. 准备就绪后,调用taskReader.startRead(recordSender)去读取源数据,然后依据数据条数循环通过transportOneRecord()方法生成record,再在transportOneRecord()方法内由recordSender.sendToWriter(record)方法将record写到缓冲区列表内。当列表到达刷写时机,调用flush()方法,将缓冲区内record刷写道channel的queue中。

  16. 再来看读数据的线程,prepare()方法后,执行taskWriter.startWrite(RecordReceiver),不同插件的的实现大致为连接到写目的地,开启一个循环,调用recordReceiver的getFromReader()方法,该方法首先是排查队列是否为空。如果为空,然后调用receive()方法将数据批量写入 writer 的缓冲区,pull数据的过程中会上锁,pull结束后会解锁。循环直到获取的record为null时结束;

  17. 写完数据后,读写两个线程执行post,进行局部的后置工作。一般地,读的post大都为空,即没有后置工作;而写的post会涉及到rename的工作;

  18. 读写任务执行post之后,执行destory,将task对象的销毁;

  19. task阶段完成,执行Job的post,进行数据写完后的完善工作,例如,关闭连接,移除配置参数。在post内先执行postJobWriter(),调用对writer作业的post()方法进行写的完善工作;再执行postJobReader(),在内部调用相关readerJob的post(),对读进行善后;

  20. 最后,执行Job的destory,先执行写作业的destory,再执行读作业的destory,将job对象销毁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

loftiest

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值