Quartz简介
作为一个优秀的开源调度框架,Quartz 具有以下特点:
强大的调度功能,支持立即调度、定时调度、周期调度、并发调度;
灵活的应用方式,支持job间通过listener实现依赖调度,可以方便的进行调度组合,支持调度数据的多种存储方式;
分布式和集群能力;
作为 Spring 默认的调度框架,Quartz 很容易与 Spring 集成实现灵活可配置的调度功能。
需求
对于工作流需要满足以下需求:
支持任务按照顺序进行调度(这是工作流的基本需求)
存在多任务并发调度的情况
存在某一个任务等待多个上游任务都结束才启动调度的情况
任务失败后,依赖该任务的下游结点要停止运行
Quartz的主要组件
Quartz的主要组件如下图所示,任务调度三个主要的类是 Scheduler、Trigger、Job。
Scheduler 是执行调度的控制器。
Trigger 是用于定义调度时间的元素,我们项目没有定时调度的需求,所有调度都选用理解触发就可以了。
Job 表示被调度的任务,Job和Trigger成对传递给Scheduler,当Trigger的条件满足时,它对应的Job就会被Scheduler触发。
Trigger/Job的组合不能实现顺序调度,实现顺序调度需要用到JobListener,JobListener对指定Job进行监听,如上图所示,JobLisener可以捕捉到三个任务触发点.
我们需要的是在Job已执行完成这个触发点,把下一个Job启动起来。
也有TriggerLisener/SchedulerLisener,触发点和Trigger、Scheduler相关,和我们的需求关系不大,暂忽略。
实现
为每个算法任务创建一个Job,任务失败不能启动后续任务,所以在job运行失败的情况下,需要把启动Job的JobLisener删除掉。
public class HelloJob implements Job {
private String JobName;
public HelloJob(String name) {
JobName = name;
}
public void execute(JobExecutionContext context) throws JobExecutionException {
/* 获取传递参数 */
JobDataMap jobDataMap = context.getMergedJobDataMap();
/* 从jobDataMap中获取下游JobLisener名称 */
/* 执行spark mlib 作业 */
if (/* 执行失败 */){
/* 删除依赖本任务的JobLisener */
context.getScheduler().getListenerManager().removeJobListener("next_job_lisener");
}
/* 当前任务结果写入数据库 */
}
}
基于所有的依赖关系,创建JobLisener,并将JobLisener与它依赖的Job绑定,在JobLisener中将下一步的Job启动起来。
public class HelloJobListener implements JobListener {
private String lisenerName;
private JobDetail nextJob;
HelloJobListener(String name, JobDetail job){
lisenerName = name;
nextJob = job;
}
public String getName() {
return lisenerName;
}
public void jobWasExecuted(JobExecutionContext inContext,JobExecutionException inException) {
/* 创建Trigger */
Trigger trigger = newTrigger()
.withIdentity(lisenerName)
.startNow()
.build();
inContext.getScheduler().scheduleJob(nextJob, trigger);
try {
/* 拉起下一个Job */
inContext.getScheduler().scheduleJob(nextJob, trigger);
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
当前任务依赖多个上游Job时,试验了AndMatcher,这个方法是对多个条件进行判断的接口,不能进行多上游依赖判断。
需要自己在JobLisener中实现多个依赖是否完成的检查。JobLisener需要知道其它依赖的完成情况,并且在自己完成后更新自己的状态。
所有Job、JobLisener的关系配置好以后,调用scheduler.start()就可以启动整个调度。
后续主线程的任务就是检查工作流是否已经完成。每个任务结点在任务完成后,会将当前任务结点的的运行结果写入数据库或缓存。
主线程依据上下游依赖关系去数据库中定时检查数据的结果,当所有分支都运行完成或运行失败后,得出算法的总体结果。
为提高更新效率,上一轮检查过后,已经完成的任务记录已查阅标记,下一轮检查从未查阅结点开始检查。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。