Quartz:任务调度实现原理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/fly910905/article/details/84664100

什么是Quartz

Quartz是一个完全由Java编写的开源作业调度框架,为在Java应用程序中进行作业调度提供了简单却强大的机制。

Quartz允许开发人员根据时间间隔来调度作业。

它实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。简单地创建一个org.quarz.Job接口的Java类。

Quartz的特点

作为一个优秀的开源调度框架,Quartz 具有以下特点:

① 强大的调度功能,例如支持丰富多样的调度方法,可以满足各种常规及特殊需求;

② 灵活的应用方式,例如支持任务和调度的多种组合方式,支持调度数据的多种存储方式;

③ 分布式和集群能力,Terracotta 收购后在原来功能基础上作了进一步提升。

④ Quartz 很容易与 Spring 集成实现灵活可配置的调度功能。

 

Quartz专用词汇说明

下面是本文中用到的一些专用词汇,在此声明:

scheduler任务调度器

trigger触发器,用于定义任务调度时间规则

job任务,即被调度的任务

misfire错过的,指本来应该被执行但实际没有被执行的任务调度

 

Quartz任务调度基本实现原理:

Quartz任务调度的核心元素为:Scheduler——任务调度器、Trigger——触发器、Job——任务。

其中trigger和job是任务调度的元数据,scheduler是实际执行调度的控制器。

Trigger

  • 是用于定义调度时间的元素,即按照什么时间规则去执行任务。
  • Quartz中主要提供了四种类型的trigger:SimpleTrigger,CronTirgger,DateIntervalTrigger,和NthIncludedDayTrigger。
  • 这四种trigger可以满足企业应用中的绝大部分需求。

Job

  • 用于表示被调度的任务。
  • 主要有两种类型的job:无状态的(stateless)和有状态的(stateful)
  • 对于同一个trigger来说,有状态的job不能被并行执行,只有上一次触发的任务被执行完之后,才能触发下一次执行。
  • Job主要有两种属性:volatility和durability,其中volatility表示任务是否被持久化到数据库存储,而durability表示在没有trigger关联的时候任务是否被保留。两者都是在值为true的时候任务被持久化或保留。
  • 一个job可以被多个trigger关联,但是一个trigger只能关联一个job

Scheduler

  • scheduler由scheduler工厂创建:DirectSchedulerFactory或者StdSchedulerFactory。
  • StdSchedulerFactory使用较多,因为DirectSchedulerFactory使用起来不够方便,需要作许多详细的手工编码设置。
  • Scheduler主要有三种:RemoteMBeanScheduler,RemoteScheduler和StdScheduler。StdScheduler 为最常用。

Quartz 线程视图

  在Quartz中,有两类线程,Scheduler调度线程和任务执行线程,其中任务执行线程通常使用一个线程池维护一组线程。

Scheduler调度线程主要有两个:执行常规调度的线程,和执行misfiredtrigger的线程。

常规调度线程:轮询存储的所有trigger,如果有需要触发的trigger,即到达了下一次触发的时间,则从任务执行线程池获取一个空闲线程,执行与该trigger关联的任务。

Misfire线程:是扫描所有的trigger,查看是否有misfiredtrigger,如果有的话根据misfire的策略分别处理(fire now OR wait for the next fire)。

Quartz Job数据存储

Quartz中的trigger和job需要存储下来才能被使用。

Quartz中有两种存储方式:RAMJobStore,JobStoreSupport。

RAMJobStore是将trigger和job存储在内存中,而JobStoreSupport是基于jdbc将trigger和job存储到数据库中。

RAMJobStore的存取速度非常快,但是由于其在系统被停止后所有的数据都会丢失,所以在集群应用中,必须使用JobStoreSupport。

任务调度器调度的时序:

在这里将几个重要的类调用的过程以序列图的形式展现出来,上半部分展现的是启动过程,下半部分展现的是任务调度的过程。

步骤1.用户首先需要生成一个调度器工厂SchedulerFactory,可以用下面的方式实现自己的定制化:

Properties properties=new Properties();    
properties.put("org.quartz.threadPool.class","org.quartz.simpl.SimpleThreadPool");
properties.put("org.quartz.threadPool.threadCount","10");
SchedulerFactory sf=new StdSchedulerFactory(properties);

步骤2.然后通过getScheduler()方法从调度器工厂里得到调度器实例,首先查找有没有这样的调度器,没有的话,就生成一个,有的话直接返回。所以得到的一般是单例,即默认的调度器。

步骤3.Scheduler有一个QuartzSchedulerThread(Thread的子类)属性,在scheduler实例化的时候,实例化了一个对象,并用ThreadExecutor启动该线程对象。该线程就是调度线程,主要任务就是不停的从JobStore中获取即将被触发的触发器(默认30s调度一次)。在这个时候调度线程虽然启动,但是处于pause状态。

步骤4.接下来是任务调度的部分:

Scheduler scheduler=sf.getScheduler();
scheduler.addJobListener(new TaskListener());
scheduler.scheduleJob(jobDetail, simpleTrigger);
scheduler.start();

client通过scheduleJob()方法将任务和触发器存储在JobStore中,通过start()方法将QuartzSchedulerThread的pause状态设为false,通知调度线程执行任务,此后调度线程不停的从JobStore中去取即将触发的任务。

任务执行的时序:

上半部分展现的是任务执行之前准备工作的时序,下半部分展现的是任务执行的时序。

步骤1.调度线程首先去线程池中获取可用的线程,如果没有的话,就阻塞。

步骤2.从JobStore(从存储介质中获取触发器,存储介质可以是内存也可以是数据库)获取(接下来30s内的)触发器,然后等待该触发器触发。

步骤3.调度线程创建一个JobRunShell(就是一个Runnable),然后从线程池中调用线程执行该任务。

接下来就是任务执行的时序:

步骤4.获取trigger、JobDetail以及生成Job实例,然后执行job的execute接口函数。

持久化的任务的执行时序:

以上就是Quartz的基本工作流程。

参考来源:https://www.cnblogs.com/zhangchengzhangtuo/p/5705672.html

猜你喜欢

转载自blog.csdn.net/fly910905/article/details/84664100