Quartz定时调度

一、利用quartz实现定时调度

1、Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。这里我介绍quartz的两种方式。我这里搭建的框架是采用springboot、spring-data-jpa、mysql、quartz的方式来实现。

二、Quartz特点

1.强大的调度功能

Spring默认的调度框架,Quartz可以灵活的与Spring集成,实现灵活的可配置的调度功能,提供调度运行环境的持久化机制,可以保存调度线程,即使系统因故障关闭,任务调度数据并不会丢失,Timer不会做到。

2.灵活的运用方式

可灵活的定义触发器的调度事件表,并可以对触发器和任务关联映射,提供了组建式监听器,各种插件,线程池等功能,支持调度数据多种存储方式

3.分布式和集群能力

三、回顾Quartz三个核心概念

Scheduler:任务调度器,所有的任务都是从这里开始
定时调度核心配置代码:

@Configuration  //配置文件注解
public class QuartzScheduler {
    // 任务调度
    @Autowired  //注入scheduler
    private Scheduler scheduler;

    /**
     * 开始执行所有任务
     * @throws SchedulerException
     */
    public void startJob() throws SchedulerException {
        //下面的方法就是用来启动这个任务的
        startJob1(scheduler);  //任务1
        startJob2(scheduler); //任务2 ...有多个任务可自行添加
        scheduler.start();  //开启定时调度
    }

    /**
     * 获取Job信息
     * @param name  //任务名字  自定义
     * @param group  //任务分组  自定义
     * @return
     * @throws SchedulerException
     */
    public String getJobInfo(String name, String group) throws SchedulerException {
        TriggerKey triggerKey = new TriggerKey(name, group);
        
        CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        
        return String.format("time:%s,state:%s", cronTrigger.getCronExpression(),
                scheduler.getTriggerState(triggerKey).name());
    }

    /**
     * 修改某个任务的执行时间
     * @param name
     * @param group
     * @param time
     * @return
     * @throws SchedulerException
     */
    public boolean modifyJob(String name, String group, String time) throws SchedulerException {
        Date date = null;
        TriggerKey triggerKey = new TriggerKey(name, group);
        CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        String oldTime = cronTrigger.getCronExpression();
        if (!oldTime.equalsIgnoreCase(time)) {
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(time);
            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(name, group)
                    .withSchedule(cronScheduleBuilder).build();
            date = scheduler.rescheduleJob(triggerKey, trigger);
        }
        return date != null;
    }

    /**
     * 暂停所有任务
     * @throws SchedulerException
     */
    public void pauseAllJob() throws SchedulerException {
        scheduler.pauseAll();
    }

    /**
     * 暂停某个任务
     * @param name
     * @param group
     * @throws SchedulerException
     */
    public void pauseJob(String name, String group) throws SchedulerException {
        JobKey jobKey = new JobKey(name, group);
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        if (jobDetail == null)
            return;
        scheduler.pauseJob(jobKey);
    }

    /**
     * 恢复所有任务
     * @throws SchedulerException
     */
    public void resumeAllJob() throws SchedulerException {
        scheduler.resumeAll();
    }

    /**
     * 恢复某个任务
     * @param name
     * @param group
     * @throws SchedulerException
     */
    public void resumeJob(String name, String group) throws SchedulerException {
        JobKey jobKey = new JobKey(name, group);
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        if (jobDetail == null)
            return;
        scheduler.resumeJob(jobKey);
    }

    /**
     * 删除某个任务
     * @param name
     * @param group
     * @throws SchedulerException
     */
    public void deleteJob(String name, String group) throws SchedulerException {
        JobKey jobKey = new JobKey(name, group);
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        if (jobDetail == null)
            return;
        scheduler.deleteJob(jobKey);
    }

    /**
     * 要启动的任务1
     * @param scheduler
     * @throws SchedulerException
     */
    private void startJob1(Scheduler scheduler) throws SchedulerException {
        // 通过JobBuilder构建JobDetail实例,JobDetail规定只能是实现Job接口的实例
        // JobDetail 是具体Job实例
        //开发的时候你要改变的地方 也只有下面的class ---SchedulerQuartzJob1.class
        JobDetail jobDetail = JobBuilder.newJob(SchedulerQuartzJob1.class).withIdentity("cache1", "cacheGroup1").build();
        // 基于表达式构建触发器
        //下面的表达式很重要
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/10 * * * * ?");
        // CronTrigger表达式触发器 继承于Trigger
        // TriggerBuilder 用于构建触发器实例
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("cache1", "cacheGroup1")
                .withSchedule(cronScheduleBuilder).build();
        scheduler.scheduleJob(jobDetail, cronTrigger);
    }
    /**
     * 要启动的任务1
     * @param scheduler
     * @throws SchedulerException
     */
    private void startJob2(Scheduler scheduler) throws SchedulerException {
        // 通过JobBuilder构建JobDetail实例,JobDetail规定只能是实现Job接口的实例
        // JobDetail 是具体Job实例
        //开发的时候你要改变的地方 也只有下面的class ---SchedulerQuartzJob1.class
        JobDetail jobDetail = JobBuilder.newJob(SchedulerQuartzJob2.class).withIdentity("order", "orderGrouop").build();
        // 基于表达式构建触发器
        //下面的表达式很重要
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/10 * * * * ?");
        // CronTrigger表达式触发器 继承于Trigger
        // TriggerBuilder 用于构建触发器实例
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("order", "orderGrouop")
                .withSchedule(cronScheduleBuilder).build();
        scheduler.scheduleJob(jobDetail, cronTrigger);
    }

}

Trigger:触发器,定义任务执行的方式、间隔。
JobDetail & Job : 定义任务具体执行的逻辑。

四、Scheduler

scheduler 是quartz的核心所在,所有的任务都是通过scheduler开始。

scheduler是一个接口类,所有的具体实现类都是通过SchedulerFactory工厂类实现,但是SchedulerFactory有两个具体的实现类,如图:
在这里插入图片描述
1.StdSchedulerFactory:默认值加载是当前工作目录下的”quartz.properties”属性文件。如果加载失败,会去加载org/quartz包下的”quartz.properties”属性文件。一般使用这个实现类就能满足我们的要求。

2.DirectSchedulerFactory:这个我也没用过QAQ,听说是为那些想绝对控制 Scheduler 实例是如何生产出的人所设计的。

Trigger
惊奇的发现trigger采用的也是buidler模式-。

withIdentity() 给触发器一些属性 比如名字,组名。
startNow() 立刻启动
withSchedule(ScheduleBuilder schedBuilder) 以某种触发器触发。
usingJobData(String dataKey, Boolean value) 给具体job传递参数。

举个创建Trigger的例子:

Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1)
                .repeatForever()).build();

Trigger的重点内容就是在withSchedule这个方法,从参数开始:查看SchedulerBuilder,这个是个抽象类,一共有4种具体实现方法,如图:

SimpleScheduleBuilder
最简单的触发器,表示从某一时刻开始,以一定的时间间隔执行任务。
属性:
repeatInterval 重复间隔。
repeatCount 重复次数。

比如:现在开始,以后每一个小时执行一次。

Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInHours(1)
                .repeatForever()).build();

DailyTimeIntervalScheduleBuilder
每一天的某一个时间段内,以一定的时间间隔执行任务,可以指定具体的某一天(星期一、星期二、星期三。。)
属性:
intervalUnit 重复间隔(秒、分钟、小时。。。)。
daysOfWeek 具体的星期。 默认 周一到周日
startTimeOfDay 每天开始时间 默认 0.0
endTimeOfDay 每天结束时间,默认 23.59.59
repeatCount 重复次数。 默认是-1 不限次数
interval 每次执行间隔

比如每周一到周四早上9点开始,晚上16点结束,每次执行间隔1 小时。

需要 导入静态方法:import static org.quartz.DailyTimeIntervalScheduleBuilder.dailyTimeIntervalSchedule;

Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1")
                //加入 scheduler之后立刻执行
                .startNow()
                //定时 ,每个1秒钟执行一次
                .withSchedule(dailyTimeIntervalSchedule()
                        .startingDailyAt(TimeOfDay.hourAndMinuteOfDay(9, 0)) //第天9:00开始
                        .endingDailyAt(TimeOfDay.hourAndMinuteOfDay(16, 0)) //16:00 结束
                        .onDaysOfTheWeek(MONDAY,TUESDAY,WEDNESDAY,THURSDAY) //周一至周五执行
                        .withIntervalInHours(1) //每间隔1小时执行一次
                        ).build();

CalendarIntervalScheduleBuilder
和SimpleScheduleBuilder类似,都是表示从某一时刻开始,以一定时间间隔执行任务。但是SimpleScheduleBuilder无法指定一些特殊情况,比如每个月执行一次,每周执行一次、每一年执行一次
属性:
interval 执行间隔
intervalUnit 执行间隔的单位(秒,分钟,小时,天,月,年,星期)

  Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1")
                //加入 scheduler之后立刻执行
                .startNow()
                //定时 ,每个1秒钟执行一次
                .withSchedule(calendarIntervalSchedule()
                        .withIntervalInWeeks(1) //每周执行一次
                        ).build();

接下来是最刁的也是最常用的最自由的CronScheduleBuilder

CronScheduleBuilder
以上几个例子都可以使用cron表达式来表示。
属性:
cron表达式。

Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1")
                //加入 scheduler之后立刻执行
                .startNow()
                //定时 ,每个1秒钟执行一次
                .withSchedule(cronSchedule("0 0/2 8-17 * * ?") // 每天8:00-17:00,每隔2分钟执行一次
                        ).build();

这没什么 最主要解释写cron表达式,可以自行google。

JobDetail & Job
jobdetail 就是对job的定义,而job是具体执行的逻辑内容。
具体的执行的逻辑需要实现 job类,并实现execute方法。
这里为什么需要有个JobDetai来作为job的定义,为什么不直接使用job?
解释:如果使用jobdetail来定义,那么每次调度都会创建一个new job实例,这样带来的好处就是任务并发执行的时候,互不干扰,不会对临界资源造成影响。

项目中出现的问题:
cron表达式怎么写?
答:这个问题,有点尴尬,本来quartz框架用起来就很简单,重点都是在于cron表达式如何写。所以大家还是多google,这里给个地址,我当时看的一篇专栏。
https://zhuanlan.zhihu.com/p/35629505
如何禁止并发执行?
答:项目中出现了一种情况,本来job执行时间只需要10s,但是由于数据库量增大之后,执行时间变成了60s,而我设置的间隔时间是30s,这样就会出现上次任务还没执行完成,下次任务就开始执行了。所以,在这种情况下,我们要禁止quartz的并发操作。

2种方式:

spring中将job的concurrent属性设置为false。默认是true 如下:

job类上加上注解@DisallowConcurrentExecution。

@DisallowConcurrentExecution
public class HelloQuartz implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) {
        JobDetail detail = jobExecutionContext.getJobDetail();
        String name = detail.getJobDataMap().getString("name");
        System.out.println("my job name is  " + name + " at " + new Date());

    }
}

注意:@DisallowConcurrentExecution是对JobDetail实例生效,如果一个job类被不同的jobdetail引用,这样是可以并发执行。

总结:
Quartz是一个纯java开发的开源框架,对于java出身的程序员来讲,不管是api还是文档相对还是很友好的,而且使用起来也很方便。其实quartz最主要的几个接口就是 scheduler·、job、jobdetai、Trigger、jobBuilder。其中最主要的还是Trigger,再深一点,最主要的还是cron表达式。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值