Azkaban将需要操作的信息打包成zip文件发送给Server端,Server对用户的信息进行存储。用户在Web UI 或者通过HTTP Client发送操作请求后,Server会根据用户定义的*.job文件(KV 匹配),执行zip包中的Jar文件。
源码的执行过程:
1.从Web页面提交工作流程:
Method.GET
/executor?projectId=33&project=testSpark&ajax=executeFlow&flow=test1&disabled=%5B%5D&failureEmailsOverride=false&successEmailsOverride=false&failureAction=finishCurrent&failureEmails=&successEmails=¬ifyFailureFirst=false¬ifyFailureLast=false&concurrentOption=ignore
用户提交任务后,发送任务的详情到服务器中,Azkaban客户端会对任务以及用户的信息进行校验,封装后首先将执行的信息(任务,时间,用户等)存入数据库中(表active_executing_flows),之后执行dispatch方法,对需要执行的任务流进行调度。
在dispatch方法中,首先会更新executions_flows表,然后将操作的语句发送到指定的ip和端口进行执行。
2.服务器接收到了请求:如果是执行操作那么接收到的action的type为execute。接着服务器会从数据库中获取相应的工作流flow,服务器将flow封装成FlowRunner。
FlowRunner的属性
ExecutorService |
线程池对象 |
ExecId |
从数据库中获取相应的flow |
numJobThreads |
默认10个线程 |
JobTypeManager |
定义Job的插件,有以下几种插件
|
Set |
将有向无环图中的node抽象成一个JobRunner进行运行 |
其中任务的执行是使用一个递归操作runReadyJob(),循环操作其中的node,也就是每个JobRunner。
JobRunner的主要属性:
Job |
执行任务的父类接口。 |
JobtypeManager |
根据输入的type类型返回此节点需要执行的任务类型 |
JobId |
唯一标识符 |
配置文件,Job的路径,监控FlowWatch..... |
其中会根据需要操作的Flow来定义Job的type。返回相应的类型。例如MR 返回的是JavaProcessJob。
也就是说:每一个节点,是通过新建一个进程去运行。在这个进程中会执行多条command,通过process.run(),运行用户定义的job。
PS.每条command都需要重新建立一个process。
在Oozie中,用户需要准备以下文件:
Job.properties |
Job文件存储HDFS,ResourceManager的配置 |
Workflow.xml |
配置每个节点之间的依赖关系 |
Lib |
存放着指定运行jar的关联包 |
.jar |
运行的jar包 |
用户需要将这些文件放置在一个文件夹下,然后上传至HDFS中。在客户端或者终端中发送请求去执行。
源码执行流程:
使用控制行操作:
1.首先调用:org.apache.oozie.cli.OozieCLI。首先根据不同的command类型调用不同的发送请求,例如使用MRCommand
在这个方法中会生成一个Client去Submit指定的Properties(根据Client和Command生成)。提交的对象是HTTPJobSubmit。调用该对象中的call方法和Server进行通信。最终返回一个jobId。METHOD.POST
2.服务器端:首先调用相应的Servlet,调用提交作业方法,生成一个DAG图(DAGEngine,然后所有的操作都是基于DAG来实现的)。
A.如果我们在提交一个作业时生成了jobType那么,此时会选定不同的提交类型(类似于一个工厂模式),返回指定的信息。
B.首先它会调用SubmitXCommand.call()方法,将job的信息加入数据库中并且返回一个jobId。
C.之后执行start(jobId)的方法,调用Xcommand.call()方法,生意Instrument对任务进行监控,在这个方法中会调用一个SignalXCommand.execute()方法。
在Oozie的后端中会维护一个异步队列,在上述的execute中会根据job中的每一个action的类型,去生成相应的Command加入异步队列中。类型如下:
skipAction |
SignXcommand |
startAction |
ActionStartXCommand |
ForkAction |
ActionStartXCommand 和上面的jobType不同 |
类似还有killActionXCommand,workflowNotifyActionXCommand等
PS如果是MR 或者 Spark 映射ActionStartXCommand 类型。
D.在后端异步队列CallableQueueService中。(在这个方法中使用Instrument对Java进行进行监控)。会调用这些XCommand的execute方法,不同的类型会实例化不同的executor,例如MR 和 Spark都会实例化JavaActionExecutor(同时还有SubWorkflowActionExecutor执行提交任务)。
E.在上述对象的execute方法中会根据配置生成JobClient,来获取正在运行的Running Job的信息以及提交Job SubmitJob,返回一个jobId。如果获取正在运行的runningJob在这个对象中还有job.trackerUrl也就是任务的日志。可以供以后展示。
测试用例提交流程:
看测试用例提交Hadoop作业中,首先对连接进行验证,然后每次提交会生成一个JobClient,该Oozie作为一个Client给Hadoop服务器发送操作job的请求。
其中操作hive hadoop spark 作业均是JavaActionExecutor,该执行器中会调用submitLauncher提交Hadoop作业。
Azkaban的工作流运行是依靠操作进程来提交不同的命令的,它操作任务成功和失败的信息在于进程的相应,但是这并不能有效的管理任务的成功与失败。
Oozie 执行MR 任务是依靠Hadoop的Jar包,以Server作为Client发送请求至集群进行操作。在此之前需要将任务所依赖执行的jar包上传至HDFS中才可执行。
通过了解Oozie和Azkaban的执行过程,个人任务使用Oozie作为底层的流程引擎比较合适,因为通过JobClient可以有效的监控正在执行的任务,获取任务的信息,如果使用Azkaban则只能获取进程执行的详情。
在Oozie中每个工作流有不同的状态,具体如下:
PERP |
工作流已经被定义但是没有执行 |
RUNNING |
当一个工作流开始执行。它不会达到结束的状态只会出错结束或者挂起 |
SUSPENDED |
工作流给挂起状态从RUNNING状态过来 |
SUCCESSED |
工作流到达END节点 |
KILLED |
工作流处于RUNNING或SUSPENDED状态被杀死 |
FAILED |
工作流遇到错误停止 |
工作流节点有以下几种类型:
控制流节点:控制工作流开始和结束以及控制执行的路径
Start |
<start to="[NODE-NAME]" /> 第一个执行的节点 |
End |
<end name="[NODE-NAME]" /> 执行到该节点任务成功,一个工作流只能有一个end |
Kill |
<kill name="[NODE-NAME]"> <message>[MESSAGE-TO-LOG]message> kill> 被杀死节点的名称和备注,达到该节点时,任务状态为KILLED |
Decision |
<decision name="[NODE-NAME]"> <switch> <case to="[NODE_NAME]">[PREDICATE]case> <default to="[NODE_NAME]" /> switch> decision> 工作流执行到此处时会根据条件进行判断,满足条件的路径将被执行 |
Fork |
<fork name="[FORK-NODE-NAME]"> <path start="[NODE-NAME]" />... fork> 多个并发路径 |
Join |
<join name="[JOIN-NODE-NAME]" to="[NODE-NAME]" /> Fork的多条路径会在Join处汇合,只有所有路径都到了,才会执行join. |
动作类型节点:能够触发一个计算任务或者处理任务执行的节点。该类节点有以下的基本特性:
1.异步:Oozie会启动一个异步队列来执行某个工作流job,并通过回调机制以及轮询来获取任务的执行状态.
2.节点要么成功要么失败。
3.一个任务如果在某个节点失败了,那么Oozie提供一套恢复运行的策略,如果是状态转移失败,那么自动运行,否则需手动运行。
动作类节点主要有以下几大类:
MR |
<action name="[NODE-NAME]"> <map-reduce>...启动一个MRJOB的执行,并且可以配置其中的其他任务,如streaming,pipes,file,archive |
Hive |
<hive xmlns="uri:oozie:hive-action:0.2"> <script>[HIVE-SCRIPT]script> <param>[PARAM-VALUE]param> 执行hive查询sql |
Sqoop |
<sqoop xmlns="uri:oozie:sqoop-action:0.2"> |
Pig |
启动脚本实现Job |
Fs |
<fs> <delete path='[PATH]' /> <mkdir path='[PATH]' /> <move source='[SOURCE-PATH]' target='[TARGET-PATH]' /> .... fs> 操作HDFS |
Java |
在Oozie中Java是有main方法执行的程序,他在服务器中以MR Job进行执行,这个Job只有一个Map程序,需要执行 namenode,jobTracker以及JVM和传输给主函数的参数 |
Sub-workflow |
子流程动作,主流程执行过程中,遇到子流程点执行时,会一直等到子流程执行完后才跳转到下一个要执行的节点。 |
Shell |
<shell xmlns="uri:oozie:shell-action:0.2"> <exec>[SHELL-COMMAND]exec> <argument>[ARGUMENT-VALUE]argument> 执行shell语句 |
可以根据hue中的方法进行新建,重写hue中的editor/workflow/new方法,不过得将python转java。
参考oozie中的提交作业的流程,看下操作的主要对象的属性信息:
WorkflowJobBean
startTimestamp |
开始时间 |
endTimestamp |
结束时间 |
app_path |
jar包位置 |
Conf |
配置文件的信息BLOB二进制大文件 |
Actions |
List |
等等。。 |
这个是一个任务的基本属性,主要包含了一堆actions节点和conf配置文件。在提交代码的过程中,以MR 作业为例:
首先,在提交的过程中,会将用户的任务信息封装成一个workFlowJob以及workflowInstance(job的状态,执行路径等)并判断job的行为状态。
然后,对这个Job中的每个action进行遍历,判断action属于哪种类型,然后放入后端的异步队列中。
异步队列会执行其中每一个action,执行时生成一个executor,这个执行器在操作的过程中会根据每一个action的xml文件生成org.apache.hadoop.conf.configuration actionConfig对象,循环遍历每个action的节点xml映射去填充这个对象的属性。
最终根据actionConf生成一个jobClient,发送用户的请求。
在Oozie中对spark作业的执行有其自定义的一套执行器----sparkActionExecutor,这个执行器继承了JavaActionExecutor。
在这个执行器中,主要作用是定义好spark作业的配置信息以及在生成Client的时候定义的Configuration actionConfig对象的初始化。
也就是说,spark对象会根据不同的配置初始化相应的JobClient用于发送spark任务jar包,其具体的流程和HadoopActionExecutor相似,都是调用JavaActionExecutor的execute()方法。
Azkaban的底层是将命令封装成一个进程进行执行,在这个过程中我们可以自定义相关命令。发送jar包进行执行。
如果从操作的角度上来说,那么Azkaban直接上传jar包然后执行,其过程更为简易,并且用户操作相对于Oozie来说更为简单,困难在于,不能直接将所需要操作的shell语句编写入口提供给用户。需要根据WEB UI的返回值,生成操作命令。
Oozie的配置相对于复杂,但是它已经提供了一套相对于比较完整的WEB 页面接口以及HUE中配置workflow.xml的代码。困难点在于将用户编写的操作流程以xml形式形象的展示出来。
如果任务正在运行的过程中,那么当前这个任务会被存储在数据库中,并且状态标记为RUNNING。当任务在执行的过程中,如果不出错且不出现挂起的状态,则任务状态不会变化。
当任务操作结束后(无论错误还是成功执行完成),Oozie会操作回调接口,具体操作流程如下:
1.生成CompletedActionXCommand,封装当前action的信息。
2.在这个对象的execute方法中,如果当前action的状态为PREP,则将继续轮询,会将轮询的命令加入执行的异步队列中,并设置相应的延时执行。
3.如果任务正处于RUNNING中,那么会在异步队列中加入ActionCheckXCommand对象,在这其中例如使用MR,则会生成JavaActionExecutor 类型的执行器。
4.执行这个执行器中的check方法。根据jobId生成jobClient获取HADOOP中正在运行的RUNNING JOB 。
5.如果job.isComplete(),会判断任务是否结束。结束是否运行成功,有相应接口。(判断成功与否包括org.apache.hadoop.mapred.Counters)代码位于:
/oozie/action/hadoop/LauncherMapperHelper/isMainSucessful。成功返回SUCCESSED,失败FAILED
6.如果任务未结束,则任务设置为RUNNING。
最终每次jobClient查询结束需要close()。Oozie会将每次运行的状态信息存储于数据库中。
当Azkaban在提交任务之后会在Client运行一个Process,不断的向Server发送查询请求。发送的请求:
/executor?execid=55&ajax=fetchexecflowupdate&lastUpdateTime=1470735951842
在Server中Azkaban会维护一个ConcurrentHashMap存储着执行的flow。这个hashmap是放在内存中的。由于Azkaban操作的颗粒度是进程,进程的执行成功或者失败都会影响这个hashmap。
但是进程的执行结果无法直接反应任务是否执行成功。
综上述的几点对比Oozie以及Azkaban,个人觉得选择Oozie作为流程引擎的选型比较好,理由如下:
1.Oozie是基于Hadoop系统进行操作,而Azkaban是基于命令行进行操作。使用hadoop提供的第三方包JobClient比直接在底层跑shell命令开发成本小,可能遇到的坑也少(一个是基于平台,一个是基于系统)。
2.Oozie的操作是放在Hadoop中,而Azkaban的运行是服务器运行shell命令。为保证服务器的稳定,使用Oozie靠谱点。
3.Ooize提供查询任务执行状态,Azkaban查询的是进程执行的结果,如果某进程执行的shell命令出错,其进程仍展示位成功,混淆了任务输出。
4.Oozie将任务执行的状态持久化到数据库中,Azkaban将任务的状态存储在服务器内存中,如果掉电,则Azkaban会丢失任务信息。
5.Ooize中定义的action类型更为丰富,而Azkaban中的依赖较为简单,当面对复杂的逻辑时Oozie执行的比较顺畅(网上说的,但是没有实践的数据。。。)。
以Oozie作为流程引擎的难点:
1.定义workflow.xml的过程,需要保证有效的完成用户的逻辑且运行的过程中job不出错。
2.部署有点麻烦。
3.学习的成本会略高。