Quartz的分布式任务调度应用

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zhongwumao/article/details/81077503

目录

 

Quartz 基本概念及原理

Quartz Scheduler 开源框架

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

线程视图

基本的开发流程及简单实例

quartz企业级开发中的常见应用的一些问题及常见的解决方案


Quartz 基本概念及原理

Quartz Scheduler 开源框架

Quartz 是 OpenSymphony 开源组织在任务调度领域的一个开源项目,完全基于 Java 实现。该项目于 2009 年被 Terracotta 收购,目前是 Terracotta 旗下的一个项目。读者可以到 http://www.quartz-scheduler.org/站点下载 Quartz 的发布版本及其源代码。笔者在产品开发中使用的是版本 1.8.4,因此本文内容基于该版本。本文不仅介绍如何应用 Quartz 进行开发,也对其内部实现原理作一定讲解。

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

  1. 强大的调度功能,例如支持丰富多样的调度方法,可以满足各种常规及特殊需求;
  2. 灵活的应用方式,例如支持任务和调度的多种组合方式,支持调度数据的多种存储方式;
  3. 分布式和集群能力,Terracotta 收购后在原来功能基础上作了进一步提升。本文暂不讨论该部分内容

另外,作为 Spring 默认的调度框架,Quartz 很容易与 Spring 集成实现灵活可配置的调度功能。

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

scheduler:

任务调度器

trigger:

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

job:

任务,即被调度的任务

misfire:

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

 

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

核心元素

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

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

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

在 Quartz 中, scheduler 由 scheduler 工厂创建:DirectSchedulerFactory 或者 StdSchedulerFactory。 第二种工厂 StdSchedulerFactory 使用较多,因为 DirectSchedulerFactory 使用起来不够方便,需要作许多详细的手工编码设置。 Scheduler 主要有三种:RemoteMBeanScheduler, RemoteScheduler 和 StdScheduler。本文以最常用的 StdScheduler 为例讲解。这也是笔者在项目中所使用的 scheduler 类。

Quartz 核心元素之间的关系如下图所示:

线程视图

Scheduler 调度线程主要有两个: 执行常规调度的线程,和执行 misfired trigger 的线程。常规调度线程轮询存储的所有 trigger,如果有需要触发的 trigger,即到达了下一次触发的时间,则从任务执行线程池获取一个空闲线程,执行与该 trigger 关联的任务。Misfire 线程是扫描所有的 trigger,查看是否有 misfired trigger,如果有的话根据 misfire 的策略分别处理。下图描述了这两个线程的基本流程:

 

基本的开发流程及简单实例

下载相应的开发包

http://www.quartz-scheduler.org/downloads/

简单实例

  • 准备数据库和Quartz用的数据表

   新建数据库,并通过建表语句生成

建表语句 建表结构

 

  • 定义任务实现类
实现无状态的Job接口
public class SampleStoreJob implements Job{
    private Logger logger = Logger.getLogger(SampleStoreJob.class);


    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        //job任务实现类参数传递
        JobDetail detail = jobExecutionContext.getJobDetail();
        Integer taskId = detail.getJobDataMap().getIntValue("taskId");
        logger.info("SampleStoreJob===========execute()");
   
    }
}
  • 实现主程序
通过cron实现每隔两秒钟执行一次的任务调度
public class SampleStoreQuartz {
    public void run() throws Exception{
        //使用SchedulerFactory创建一个Scheduler
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();
        scheduler.clear(); //测试用,避免因为调度存在报错,可以在job未delete的情况下删掉看下效果

        //定义一个具体的Job
        JobDetail jobDetail = JobBuilder.newJob(SampleStoreJob.class)
                .withIdentity("sampleStoreJob", "sampleJobGroup")
                .build();
        //定义一个具体的Trigger
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/2 * * * * ?");//具体的执行时间定义
        CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity("sampleStoreTrigger1", "sampleTriggerGroup1").withSchedule(scheduleBuilder).build();
        //将Job和Trigger绑定至Scheduler
        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();//启动运行

        Thread.sleep(10*1000);//情节需要,10秒钟
        //定义一个JobKey,用来做删除Job测试
     //   JobKey jobKey = JobKey.jobKey("sampleStoreJob", "sampleJobGroup");
       // scheduler.deleteJob(jobKey);

      //  scheduler.shutdown();//关闭Scheduler
    }

    public static void main(String[] args) throws Exception{
        SampleStoreQuartz sampleStoreQuartz = new SampleStoreQuartz();
        sampleStoreQuartz.run();
    }

 

  • 核心代码
暂停任务 Scheduler scheduler = schedulerFactoryBean.getScheduler(); JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup()); scheduler.pauseJob(jobKey);
恢复任务 Scheduler scheduler = schedulerFactoryBean.getScheduler(); JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup()); scheduler.resumeJob(jobKey);
删除任务 Scheduler scheduler = schedulerFactoryBean.getScheduler(); JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup()); scheduler.deleteJob(jobKey);
立即运行任务 Scheduler scheduler = schedulerFactoryBean.getScheduler(); JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup()); scheduler.triggerJob(jobKey);

 

  • 运行程序,查看数据库和运行结果
表名 表记录
qrtz_triggers
qrtz_job_details

 

 

quartz企业级开发中的常见应用的一些问题及常见的解决方案

 

使用有状态的StatefulJob还是无状态的Job 在quartz中,Job是一个接口,企业应用需要实现这个接口,以定义自己的任务,基本来说,任务分为有状态和无状态两种,实现Job接口的任务缺省为无状态的,Quartz中海油一个应用接口StatefulJob,是有状态的,有状态的, 当任务未执行完,是不能并发执行的,比如若有些任务不能并发执行,可以考虑有状态的接口,并配合misfiredjob
如何设置Quartz的线程池和并发任务 Quartz自带了一个线程池的实现:SimpleThreadPool,并提供了默认的配置参数,不如线程数等,并不是线程数,越多越好,可以根据自己的业务灵活调整
如何处理Misfired任务

1)系统因为某些原因被重启。在系统关闭到重新启动之间的一段时间里,可能有些任务会

被 misfire;

2)Trigger 被暂停(suspend)的一段时间里,有些任务可能会被 misfire;

3)线程池中所有线程都被占用,导致任务无法被触发执行,造成 misfire;

4)有状态任务在下次触发时间到达时,上次执行还没有结束;

为了处理 misfired job,Quartz 中为 trigger 定义了处理策略,主要有下面两种:

MISFIRE_INSTRUCTION_FIRE_ONCE_NOW:针对 misfired job 马上执行一次;

MISFIRE_INSTRUCTION_DO_NOTHING:忽略 misfired job,等待下次触发;

建议读者在应用开发中,将该设置作为可配置选项,使得用户可以在使用过程中,针对已经添加的 tirgger 动态配置该选项。

 

 

 

展开阅读全文

没有更多推荐了,返回首页