zoukankan      html  css  js  c++  java
  • Quartz总结

    前言

    最近项目中有使用到Quartz,得空便总结总结,顺便记录一下这种设计模式,毕竟“好记性不如烂笔头”。

    搭建

    pom文件:

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

    quartz.properties配置:

    org.quartz.scheduler.instanceName = MyScheduler
    org.quartz.threadPool.threadCount = 3
    org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

    简单的示例

    EnumCheckJobType,任务类型

    public enum EnumCheckJobType {
    
        ONE,TWO
        
    }

    SchedulerTask,任务的接口

    public interface SchedulerTask {
    
        void run();
    
    }

    OneScheduler,一个具体的任务

    public class OneScheduler implements SchedulerTask{
    
        @Override
        public void run() {
            //do something
        }
    }

    TwoScheduler,另一个具体的任务

    ublic class TwoScheduler implements SchedulerTask{
    
        @Override
        public void run() {
            //do something
        }
    }

    MyJob,执行任务(必须实现Job接口)

    public class MyJob implements Job {
    
        @Override
        public void execute(JobExecutionContext jobExecutionContext) {
            
            SchedulerTask task = (SchedulerTask) jobExecutionContext.getMergedJobDataMap().get("task");
            task.run();     
        }  
    }

    MyScheduler,调度器(什么时候执行什么任务,指的是MyJob的execute方法)

    public class MyScheduler {
    
        /**
         * 注册调度任务
         *
         * @param scheduler
         * @param jobType
         * @param cronExpress cron 表达式
         */
        public void scheduleJob(Scheduler scheduler, EnumCheckJobType jobType, String cronExpress) {
            try {
                scheduleJob(scheduler, jobType, CronScheduleBuilder.cronSchedule(cronExpress));
            } catch (SchedulerException e) {
    
            }
        }
    
    
        /**
         * 注册调度任务
         *
         * @param scheduler
         * @param jobType
         * @param interval  间隔时间
         */
        public void scheduleJob(Scheduler scheduler, EnumCheckJobType jobType, int interval) {
            try {
                scheduleJob(scheduler, jobType, SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInSeconds(interval)
                        .repeatForever()
                        .withMisfireHandlingInstructionNowWithExistingCount());//misfire策略
            } catch (SchedulerException e) {
    
            }
        }
    
        /**
         * 注册调度任务
         *
         * @param scheduler
         * @param jobType
         * @param builder
         */
        public <T extends Trigger> void scheduleJob(Scheduler scheduler, EnumCheckJobType jobType, ScheduleBuilder<T> builder) throws SchedulerException {
    
            SchedulerTask task = newTask(jobType);
            if (task == null) {
                throw new SchedulerException();
            }
    
            JobDetail job = JobBuilder.newJob(MyJob.class)
                    .withIdentity(getJobName(jobType), getJobGroupName(jobType))
                    .build();
    
            job.getJobDataMap().put("task", task);
    
            // 第二天的零点开始执行
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(getJobTriggerName(jobType), getJobGroupName(jobType))
                    .forJob(job.getKey())
                    .withSchedule(builder)
                    .startAt(DateBuilder.tomorrowAt(0, 0, 0))
                    .build();
    
            scheduler.scheduleJob(job, trigger);
    
        }
    
    
        private String getJobName(EnumCheckJobType checkType) {
            return checkType.name();
        }
    
    
    
        private String getJobGroupName(EnumCheckJobType checkType) {
            return "group-" + checkType.name();
        }
    
    
        private String getJobTriggerName(EnumCheckJobType jobType) {
            return jobType.name();
        }
    
    
        private SchedulerTask newTask(EnumCheckJobType jobType) {
            switch (jobType) {
                case ONE:
                    return applicationContext.getBean
                            (OneScheduler.class);
                case TWO:
                    return applicationContext.getBean
                            (TwoScheduler.class);
                default:
                    return null;
            }
        }
    }

    HelloQuartz,主函数

    public class HelloQuartz {
        public static void main(String[] args) throws SchedulerException {
            MyScheduler myScheduler = new MyScheduler();
            try {
                Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
                myScheduler.scheduleJob(scheduler, EnumCheckJobType.ONE, "0 0 0 * * ?");//每天的凌晨0点执行
                myScheduler.scheduleJob(scheduler, EnumCheckJobType.TWO, 60);//间隔60S执行一次
           scheduler.start();//启动调度任务 } catch (SchedulerException e) { } } }

    Quartz的3个基本要素

    • Scheduler:调度器。所有的调度都是由它控制。
    • Trigger: 触发器。决定什么时候来执行任务。
    • JobDetail & Job: JobDetail 定义的是任务数据,而真正的执行逻辑是在Job中。使用JobDetail + Job而不是Job,这是因为任务是有可能并发执行,如果Scheduler直接使用Job,就会存在对同一个Job实例并发访问的问题。而JobDetail & Job 方式,sheduler每次执行,都会根据JobDetail创建一个新的Job实例,这样就可以规避并发访问的问题。

    name和group

    JobDetail和Trigger都有name和group。

    name是它们在这个sheduler里面的唯一标识。如果我们要更新一个JobDetail定义,只需要设置一个name相同的JobDetail实例即可。

    group是一个组织单元,sheduler会提供一些对整组操作的API,比如 scheduler.resumeJobs()。

    misfire(错失触发)策略

    当系统由于某种原因(未启动或是没有可用线程)在预定时刻没有启动任务,之后当系统可以调度该任务时(系统启动或是取得了可用线程),会首先检查当前时刻与预定时刻的差值,如果小于等于misfireThreshold值(该参数缺省为60秒),则不认为发生Misfire,并立刻启动该任务,一切正常进行。如果大于misfireThreshold值,则认为发生了misfire,此时的行为由trigger的Misfire Instructions来决定。而不同类型的trigger的缺省Misfire Instructions是不同的。

    对于典型的SimpleTrigger: 

    缺省Misfire策略为Trigger.MISFIRE_INSTRUCTION_SMART_POLICY ,其他策略如下:

    SimpleScheduleBuilder ssb = SimpleScheduleBuilder.simpleSchedule();
    ssb.withMisfireHandlingInstructionFireNow();//1
    ssb.withMisfireHandlingInstructionIgnoreMisfires();//2
    ssb.withMisfireHandlingInstructionNextWithExistingCount();//3
    ssb.withMisfireHandlingInstructionNextWithRemainingCount();//4
    ssb.withMisfireHandlingInstructionNowWithExistingCount();//5
    ssb.withMisfireHandlingInstructionNowWithRemainingCount();//6
    
    //1
    withMisfireHandlingInstructionFireNow  ---> SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW (misfireInstruction == 1)
    ——以当前时间为触发频率立即触发执行
    ——执行至FinalTIme的剩余周期次数
    ——以调度或恢复调度的时刻为基准的周期频率,FinalTime根据剩余次数和当前时间计算得到
    ——调整后的FinalTime会略大于根据starttime计算的到的FinalTime值
    
    //2
    withMisfireHandlingInstructionIgnoreMisfires ---> Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY(misfireInstruction == -1)
    —以错过的第一个频率时间立刻开始执行
    ——重做错过的所有频率周期
    ——当下一次触发频率发生时间大于当前时间以后,按照Interval的依次执行剩下的频率
    ——共执行RepeatCount+1次
    
    //3
    withMisfireHandlingInstructionNextWithExistingCount ---> SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT(misfireInstruction == 5)
    ——不触发立即执行
    ——等待下次触发频率周期时刻,执行至FinalTime的剩余周期次数
    ——以startTime为基准计算周期频率,并得到FinalTime
    ——即使中间出现pause,resume以后保持FinalTime时间不变
    
    //4
    withMisfireHandlingInstructionNextWithRemainingCount ---> SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT(misfireInstruction == 4)
    ——不触发立即执行
    ——等待下次触发频率周期时刻,执行至FinalTime的剩余周期次数
    ——以startTime为基准计算周期频率,并得到FinalTime
    ——即使中间出现pause,resume以后保持FinalTime时间不变
    
    //5
    withMisfireHandlingInstructionNowWithExistingCount ---> SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT(misfireInstruction == 2)
    ——以当前时间为触发频率立即触发执行
    ——执行至FinalTIme的剩余周期次数
    ——以调度或恢复调度的时刻为基准的周期频率,FinalTime根据剩余次数和当前时间计算得到
    ——调整后的FinalTime会略大于根据starttime计算的到的FinalTime值
    
    //6
    withMisfireHandlingInstructionNowWithRemainingCount ---> SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT(misfireInstruction == 3)
    ——以当前时间为触发频率立即触发执行
    ——执行至FinalTIme的剩余周期次数
    ——以调度或恢复调度的时刻为基准的周期频率,FinalTime根据剩余次数和当前时间计算得到
    ——调整后的FinalTime会略大于根据starttime计算的到的FinalTime值

    总结:
    1、带有_NOW字样的策略,就是立即执行;反之,带有_NEXT的策略,则会等到下一个触发周期才会执行。
    2、带有WITH_EXISTING_REPEAT_COUNT字样的,则是确保周期总数不变,用周期总数-已执行数作为剩余周期数,因此FinalTime会适当延后;
    例如,repeatCount为3次(总计4次),已执行1次,错过2次,则后续仍会执行4-1=3次。
    3、带有WITH_REMAINING_REPEAT_COUNT则是按原定计划执行,FinalTime不变,已错过的忽略。
    例如,repeatCount为3次(总计4次),已执行1次,错过2次,则后续会执行4-1-2=1次。

    基于在创建SimpleTrigger时选择的MISFIRE_INSTRUCTION_XXX更新SimpleTrigger的状态。 如果失火指令设置为MISFIRE_INSTRUCTION_SMART_POLICY,则将使用以下方案:

    • 如果重复计数为0,则指令将解释为MISFIRE_INSTRUCTION_FIRE_NOW。
    • 如果重复计数为REPEAT_INDEFINITELY(repeatForever),则指令将解释为MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT。 警告:如果触发器具有非空的结束时间,则使用MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT可能会导致触发器在失火时间范围内到达结束时,不会再次触发。
    • 如果重复计数大于0,则指令将解释为MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT。

    对于典型的CronTrigger: 

    缺省Misfire策略为Trigger.MISFIRE_INSTRUCTION_SMART_POLICY ,其他策略如下:

    CronScheduleBuilder csb = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");
    csb.withMisfireHandlingInstructionDoNothing();
    csb.withMisfireHandlingInstructionFireAndProceed();
    csb.withMisfireHandlingInstructionIgnoreMisfires();
    
    withMisfireHandlingInstructionDoNothing ---> CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING(misfireInstruction = 2)
    ——不触发立即执行
    ——等待下次Cron触发频率到达时刻开始按照Cron频率依次执行
    
    withMisfireHandlingInstructionFireAndProceed ---> CronTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW(misfireInstruction = 1)
    ——以当前时间为触发频率立刻触发一次执行
    ——然后按照Cron频率依次执行
    
    withMisfireHandlingInstructionIgnoreMisfires ---> Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY(misfireInstruction = -1)
    ——以错过的第一个频率时间立刻开始执行
    ——重做错过的所有频率周期后
    ——当下一次触发频率发生时间大于当前时间后,再按照正常的Cron频率依次执行

    根据创建CronTrigger时选择的MISFIRE_INSTRUCTION_XXX更新CronTrigger的状态。 如果失火指令设置为MISFIRE_INSTRUCTION_SMART_POLICY,则将使用以下方案:

    • 指令将解释为MISFIRE_INSTRUCTION_FIRE_ONCE_NOW

    参考

    这里有一篇博客总结的非常好,记录并分享下

    http://www.cnblogs.com/drift-ice/p/3817269.html

                                         

     

  • 相关阅读:
    Springboot中使用Interceptor(拦截器)
    八大排序之冒泡排序
    八大排序之快速排序
    mysql 用户的增删改与授权
    基于Java8开发接口时,处理Java8中的日期
    Springboot中Filter的使用
    正则校验日期,不考虑闰年和闰月
    正则校验时间,24小时制
    记一下mybatis中foreach循环遇到的一个小问题
    sqlserver中一条语句执行查询与更新
  • 原文地址:https://www.cnblogs.com/irain/p/7458223.html
Copyright © 2011-2022 走看看