quartz基本使用-灵析社区

开着皮卡写代码

前言

众所周知,Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,在众多的中小项目中有着广泛的使用(本人所在的项目也有部分使用),Quartz的一个特点就是,引用简单,能和既有的框架做快速的整合,不管是基于spring的项目还是springboot的项目,甚至是简单的web项目,都可以快速的引入,加上学习成本低,对于大多数小伙伴来说,是个不错的选择

Quartz基本概念

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

Quartz 可以与 J2EE 与 J2SE 应用程序相结合也可以单独使用

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

Quartz 实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联

Quartz 核心概念

在正式开始编写代码之前,有必要了解一些关于Quartz的几个核心概念,有助于我们对于代码的编写和理解

Job 表示一个工作,要执行的具体内容。此接口中只有一个方法,如下:

void execute(JobExecutionContext context)

JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略

Trigger 代表一个调度参数的配置,什么时候去调

Scheduler 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了

Quartz的运行环境

Quartz 可以运行嵌入在另一个独立式应用程序

Quartz 可以在应用程序服务器(或 servlet 容器)内被实例化,并且参与 XA 事务

Quartz 可以作为一个独立的程序运行(其自己的 Java 虚拟机内),可以通过 RMI 使用

Quartz 可以被实例化,作为独立的项目集群(负载平衡和故障转移功能),用于作业的执行

下面让我们正式开始编码吧

1、引入quartz依赖

	<dependencies>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!--quartz相关依赖-->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.1</version>
        </dependency>

        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>2.3.1</version>
        </dependency>

    </dependencies>

按照上面的核心概念中提到的,其实我们定义一个job在quartz中非常简单,大概2个步骤,第一一个实现Job接口的类,第二需要一个类,用于组装job,JobDetail,Trigger,然后利用调度器Scheduler将他们整合在一起就可以了,里面的细节就是如何运用API的过程,那么先看第一个例子吧

简单job类

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.time.LocalTime;

public class MyJob implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("我正在执行任务:" + LocalTime.now());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class Test1 {
    
    public static void main(String[] args) throws SchedulerException {
        //定义Scheduler对象
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.start();

        //定义JobDetail对象
        JobDetail jobDetail = JobBuilder.newJob(MyJob.class).withIdentity("jodDetail1", "group1").build();

        //定义Trigger对象
        SimpleTrigger trigger = TriggerBuilder.newTrigger()
                .startNow()
                .withSchedule(
                        SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())
                .build();
        //使用scheduler将JobDetail和Trigger进行组装
        scheduler.scheduleJob(jobDetail,trigger);
        try {
            Thread.sleep(60000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        scheduler.shutdown();
    }

}

关于这段代码有几处需要简单说明下

JobDetail -> withIdentity ,为每一个job指定一个唯一的身份标识,最好带有一定的业务含义

Trigger -> withSchedule ,在这个模块中,定义运行的job的详细参数,比如schedule类型,有simpleSchedule简单类型,还有cronSchedule类型,withIntervalInSeconds任务执行间隔时间,这里面的API比较多,定义的时间策略也很多,有兴趣的同学可以点出来瞧瞧

本段代码的意思就是间隔5秒执行一次job

下面运行下这段代码,看下效果

job参数传递

试想有这么一种场景,每次执行job时,需要从jobDetail中给job传递一些参数,以便执行时使用该怎么做呢?

在jobDetail的对象构造过程中,提供了usingJobData的方法,里面有多种类型可供选择,最常见的像key,value类似map的结构

比如我们在jobDetail中传递一个name=jike的值,简单改造后如下:

public static void main(String[] args) throws SchedulerException {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.start();

        JobDetail jobDetail = JobBuilder.newJob(MyJob2.class)
                .withIdentity("jodDetail1", "group1")
                .usingJobData("name","jike")
                .build();
        SimpleTrigger trigger = TriggerBuilder.newTrigger()
                .startNow()
                .withSchedule(
                        SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())
                .build();
        scheduler.scheduleJob(jobDetail,trigger);
        try {
            Thread.sleep(60000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        scheduler.shutdown();
    }


参数放进去了以后,job中怎么接收呢?quartz提供了大概3种方式进行参数的传递,

第一种,属性的set/get方法

public class MyJob2 implements Job {

    @Getter@Setter
    private String name;

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        //JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
        //System.out.println("name is :" + jobDataMap.getString("name"));
        System.out.println("name is :" + name);
    }
}

运行上面的代码

第二种,从excute的context中获取

public class MyJob2 implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
        System.out.println("name is :" + jobDataMap.getString("name"));
    }
}

还有一种场景,就是我希望这一次job执行完毕之后,参数也随着job的执行不断的改变,这时就需要@PersistJobDataAfterExecution 这个注解排上用场了,看下面的这段代码,第一次在jobDetail中向job传递了一个count的参数,后面我们希望每次job执行完毕后count数值递增

public class Test3 {

    public static void main(String[] args) throws SchedulerException {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.start();
        int count = new Random().nextInt(10);
        JobDetail jobDetail = JobBuilder.newJob(MyJob3.class)
                .withIdentity("jodDetail1", "group1")
                .usingJobData("count",count)
                .build();
        SimpleTrigger trigger = TriggerBuilder.newTrigger()
                .startNow()
                .withSchedule(
                        SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())
                .build();
        scheduler.scheduleJob(jobDetail,trigger);
        try {
            Thread.sleep(60000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        scheduler.shutdown();
    }
}
@PersistJobDataAfterExecution
public class MyJob3 implements Job {

    @Setter@Getter
    private int count;

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        count++;
        context.getJobDetail().getJobDataMap().put("count",count);
        LocalTime now = LocalTime.now();
        System.out.println("current time is :"  + now.toString() + ",the cout is:" + count);
    }
}

简单来说,@PersistJobDataAfterExecution这个注解具有存储参数的功能

trigger优先级

在使用trigger的时候,有这么一个问题不容忽略,就是当项目中有多个地方的job配置了同一时间点触发,但我们知道,quartz的执行是需要额外占用系统资源的,也就是每次job的执行需要系统提供新的线程来执行,往往在开发过程中,为了不至于让job的运行占用过多的线程资源,我们将quartz的线程开销数设置为一个固定的值,设置也比较简单,只需要在resource目录下,提供一个quartz.properties的文件进行简单的配置即可,job在启动的时候,会去读取这个配置文件中的内容

org.quartz.scheduler.instanceName=myScheduler
#配置quartz的线程总数,这里配置为1,放大问题出现的可能性
org.quartz.threadPool.threadCount=1
org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore

org.quartz.jobStore.misfire.Threshold=1000

这样以来,在上面的场景下,多个任务要在同一时间点触而且我们希望某个job的业务先执行怎么办呢?

quartz在trigger的配置中提供了优先级的API,现在假如有两个job,均在10秒之后触发,那么我们可以通过设置withPriority的值来达到我们的目的(注意:前提是线程资源不够的情况下,优先级才会生效)

public class Test4 {

    public static void main(String[] args) throws SchedulerException {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.start();

        JobDetail jobDetail1 = JobBuilder.newJob(MyJob4.class).withIdentity("jodDetail1", "group1").build();
        JobDetail jobDetail2 = JobBuilder.newJob(MyJob4.class).withIdentity("jobDetail2", "group2").build();

        Date date = DateBuilder.futureDate(10, DateBuilder.IntervalUnit.SECOND);

        SimpleTrigger trigger1 = TriggerBuilder.newTrigger()
               // .startNow()
                .startAt(date)
                .withPriority(2)
                .usingJobData("msg","trigger1触发")
                .withSchedule(
                        SimpleScheduleBuilder
                                .simpleSchedule()
                                .withIntervalInSeconds(5)
                                .repeatForever())
                .build();

        SimpleTrigger trigger2 = TriggerBuilder.newTrigger()
                //.startNow()
                .startAt(date)
                .withPriority(9)
                .usingJobData("msg","trigger2触发")
                .withSchedule(
                        SimpleScheduleBuilder
                                .simpleSchedule()
                                .withIntervalInSeconds(5)
                                .repeatForever())
                .build();

        scheduler.scheduleJob(jobDetail1,trigger1);
        scheduler.scheduleJob(jobDetail2,trigger2);
        try {
            Thread.sleep(60000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        scheduler.shutdown();
    }

}
public class MyJob4 implements Job {

    @Setter@Getter
    private String msg;

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("我正在执行任务:" + LocalTime.now() +",msg is:" + msg);
    }

}

简单改造一下代码之后,运行上面的程序,通过控制台打印结果可以发现具有更高优先级的trigger的任务,在资源有限的情况下会更先执行,

cronSchedule

相比simpleSchedule,在项目开发中cronSchedule的使用可能更多,cronSchedule的方式支持丰富的cron表达式,即我们熟悉的基于各种时间类型的定时任务,比如每隔5秒执行,就可以使用我们熟悉的 “0/5 * * * * ?”

使用CronTrigger,可以指定具体的时间表,例如“每周五中午”或“每个工作日和上午9:30”,甚至“每周一至周五上午9:00至10点之间每5分钟”和1月份的星期五“。

在CronScheduleBuilder对象的API中,提供了几种常用的和cron相关的方法,比如最常用的支持cron时间表达式的字符串格式的方法

请参考如下示例代码:

public class Test6 {

    public static void main(String[] args) throws SchedulerException {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.start();

        JobDetail jobDetail = JobBuilder.newJob(MyJob6.class).withIdentity("jodDetail1", "group1").build();

        CronTrigger cronTrigger = TriggerBuilder.newTrigger()
                .startNow()
                .usingJobData("msg", "trigger1触发")
                .withSchedule(
                        CronScheduleBuilder.cronSchedule("0/5 * * * * ?")
                )
                .build();
        scheduler.scheduleJob(jobDetail, cronTrigger);
        try {
            Thread.sleep(60000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        scheduler.shutdown();
    }

}

public class MyJob6 implements Job {

    @Setter@Getter
    private String msg;

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("我正在执行任务:" + LocalTime.now() +",msg is:" + msg);
    }

}

运行效果如下:

misfire机制

在quartz中,有这么一种场景不能忽视,设想当你的任务间隔执行时间是5秒,但实际上,在job执行业务逻辑过程中却执行了9秒甚至更长的时间,那么对于quartz来说,这两次间隔的任务该怎么处理呢?因为人家原本希望的是每5秒一次的,现在你的业务执行时间打破了5秒的机制,要怎么处理呢?

在quartz中,针对cron类型的trigger,提供了下面3种处理方式,分别是:

withMisfireHandlingInstructionDoNothing 不触发立即执行,等待下次Cron触发频率到达时刻开始按照Cron频率依次执行

withMisfireHandlingInstructionIgnoreMisfires 以错过的第一个频率时间立刻开始执行,做错过的所有频率周期后,当下一次触发频率发生时间大于当前时间后,再按照正常的Cron频率依次执行

withMisfireHandlingInstructionFireAndProceed 以当前时间为触发频率立刻触发一次执行,后按照Cron频率依次执行

大概是什么意思呢?我们先用程序简单模拟下效果吧,比如使用第一种来看一下实际效果怎样的?withMisfireHandlingInstructionDoNothing

public class Test7 {

    public static void main(String[] args) throws SchedulerException {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.start();

        JobDetail jobDetail1 = JobBuilder.newJob(Job7.class).withIdentity("jodDetail1", "group1").build();
        CronTrigger cronTrigger = TriggerBuilder.newTrigger()
                .startNow()
                .usingJobData("msg", "trigger1触发")
                .withPriority(2)
                .withSchedule(
                        CronScheduleBuilder.cronSchedule("0/5 * * * * ?")
                        .withMisfireHandlingInstructionDoNothing()
                )
                .build();

        scheduler.scheduleJob(jobDetail1, cronTrigger);
        try {
            Thread.sleep(600000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        scheduler.shutdown();
    }
}
public class Job7 implements Job {

    @Setter
    @Getter
    private String msg;

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("我正在执行任务:" + LocalTime.now() +",msg is:" + msg);
        try {
            Thread.sleep(7000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行程序,对照上述关于这几种misFire的解释,能够想象出下面的效果吗?

阅读量:617

点赞量:0

收藏量:0