美文网首页
Spring中任务调度

Spring中任务调度

作者: Snipers_onk | 来源:发表于2019-11-16 21:03 被阅读0次

    任务调度

    任务调度即在特定的时间点执行指定的操作。任务调度本身设计多线程并发,运行时间规则制定及解析,运行现场保持及恢复,线程池维护等。

    quartz是任务调度的成熟解决方案,功能强大使用简单。Spring提供了集成quartz的功能,也为JDK Timer 和 Excutor提供了支持。

    在Spring中提供了一系列FactoryBean,可以很轻松的创建任务调度的实例;Spring还提供了几个工具类,可以将某个具体的Bean的方法作为被调度的任务;Spring还提供了支持线程池的执行调度器,它提供了一个抽象层,屏蔽了Java 1.3、JAVA 1.4、JAVA 1.5及Java EE之间的差异。

    Quartz

    Quartz允许开发人员灵活的定义触发器的调度时间表,并可对触发器和任务进行关联。Quartz提供了调度运行环境的持久化机制,可以保存并恢复调度现场。Quartz提供了监听器,各种插件,线程池等功能。(以下代码基于Quartz 1.8.6)

    基础结构

    Quartz对任务调度进行了高度的抽象,提出了调度器,任务和触发器三个核心概念,并在org.quartz这个包中通过接口和类对核心概念进行描述。

    Job

    Job 是一个接口,内部只有一个方法。通过实现该接口定义需要执行的任务。JobExcutionContext提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中。

    public interface Job {
        void execute(JobExecutionContext context)
            throws JobExecutionException;
    }
    
    • StatefulJob

      Job的子接口,代表有状态的任务。该接口是个标签接口,让Quartz知道任务的类型,以便采取不同的执行方案。每个无状态的任务有自己的JobDataMap,所有有状态任务共享一个JobDataMap,StatefulJob每次任务执行对JobDataMap的更改会影响后续任务。所以有状态的StatefulJob不能并发执行,后续任务将阻塞等待直到本次任务执行完毕。除非必要,应避免StatefulJob的使用。

    JobDetail

    Quartz每次执行任务时,都根据接收的Job的实现类Class,通过反射创建一个Job实例,因此需要一个类对Job实现类和其他配置进行描述,如Job名称,描述,关联监听器等(类似于Spring中的BeanDefinition)。

    public interface JobDetail extends Serializable, Cloneable {
        public JobKey getKey();
        public String getDescription();
        public Class<? extends Job> getJobClass();
        public JobDataMap getJobDataMap();
        ...
    }
    
    JobDataMap

    JobDataMap中可以包含不限量的(序列化的)数据对象,在job实例执行的时候,可以使用其中的数据;JobDataMap是Java Map接口的一个实现,额外增加了一些便于存取基本类型的数据的方法。
    将job加入到scheduler之前,在构建JobDetail时,可以将数据放入JobDataMap。
    方式有两种

    直接在构建JobDetail时通过JobBuilder的usingJobData方法将数据放入JobDataMap中。方法有两种:直接添加数据或者构造JobDataMap

    //构造JobDataMap
    JobDataMap jobDataMap = new JobDataMap();
    jobDataMap.put("jobData2", "hello 2");
    
    JobDetail job = JobBuilder.newJob(SimpleJob.class)
        .withIdentity("helloJob","hello")
        .usingJobData("jobData1","hello 1") //添加数据
        .usingJobData(jobDataMap)
        .build();
    

    也可以在job类中,为JobDataMap中存储的数据的key增加set方法,那么Quartz的默认JobFactory实现在job被实例化的时候会自动调用这些set方法,这样就不需要在execute()方法中显式地从map中取数据了。

    public class SimpleJob implements Job {
    
        private String jobData1;
        private String jobData2;
    
        public void setJobData1(String jobData1) {
            this.jobData1 = jobData1;
        }
        public void setJobData2(String jobData2) {
            this.jobData2 = jobData2;
        }
    
        @Override
        public void execute(JobExecutionContext context) throws JobExecutionException {
    //        System.out.println(context.getJobDetail().getJobDataMap().get("jobData1"));
    //        System.out.println(context.getJobDetail().getJobDataMap().get("jobData2"));
            System.out.println(jobData1);
            System.out.println(jobData2);
            System.out.println("hello,quartz!"+ context.getJobDetail().getKey()+":::"+ context.getTrigger().getKey());
        }
    }
    
    Trigger

    Trigger接口描述触发Job执行的时间触发规则。主要由两个子类接口:仅需要触发一次或以固定时间间隔执行时,适合选择SimpleTriggerCronTrigger适合复杂的调度方案,通过Cron表达式定义时间规则。

    Calendar

    org.quartz.Calendarjava.util.Calendar不同,它是一些日历特定时间点的集合。一个Trigger可以和多个Calendar关联,以便排除或包含某些时间点。例如每周一上午9点执行任务,如果遇到法定节假日不执行。针对不同的时间段类型,Quartz提供了不同的实现类,如AnnualCalendar,HolidayCalendar,MonthlyCalendar等。

    Scheduler

    代表一个Quartz独立运行的容器,Trigger和JobDetail可以注册到Scheduler中,二者在Scheduler中各自拥有组和名称,组成了key。key是Scheduler查找定位容器中某一对象的依据,所以必须唯一(Trigger和JobDetail的key可以相同,因为类型不一样,处在不同的集合中)。

    Scheduler定义了多个接口方法,允许外部通过组及名称对容器中的Trigger和JobDetail进行访问控制。

    Scheduler可以将Trigger绑定到某一个JobDetail,这样当Trigger触发时,对应Job会被执行,一个Job可以对应多个Trigger,但是一个Trigger只能对应一个Job。

    Scheduler实例通过SchedulerFactory创建,Scheduler拥有一个SchedulerContext,SchedulerContext内部维护一个Map,以键值对形式保存上下文信息。job和Trigger都可以访问SchedulerContext内的信息。

    Scheduler的生命周期

    Scheduler的生命期, 从SchedulerFactory创建它时开始,到Scheduler调用shutdown()方法时结束;Scheduler被创建后,可以增加、删除和列举Job和Trigger,以及执行其它与调度相关的操作(如暂停Trigger)。但是,Scheduler只有在调用start()方法后,才会真正地触发trigger(即执行job)

    JobBuilder

    用于定义/构建JobDetail实例,用于定义作业的实例。

    ThreadPool

    Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程来提高运行效率。

    SimpleTrigger

    Demo
    public class SimpleJob implements Job {
        @Override
        public void execute(JobExecutionContext context) throws JobExecutionException {
            System.out.println("hello,quartz!"+ context.getJobDetail().getKey()+":::"+ context.getTrigger().getKey());
        }
    }
    
    public static void main(String[] args) throws Exception{
        //构建SchedulerFactory实例
        SchedulerFactory schedFact = new StdSchedulerFactory();
    
        //获取Scheduler实例
        Scheduler scheduler = schedFact.getScheduler();
    
        //构建JobDetail实例
        JobDetail job = JobBuilder.newJob(SimpleJob.class)
            .withIdentity("helloJob","hello")
            .build();
    
        //构建Trigger实例
        SimpleTrigger trigger = TriggerBuilder.newTrigger()
            .withIdentity("helloTrigger","hello")
            .startNow()         .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())
            .build();
    
        //将JobDetail实例和Trigger实例加入到调度容器
        scheduler.scheduleJob(job,trigger);
    
        //启动容器
        scheduler.start();
    
    }
    
    Misfire策略
    • MISFIRE_INSTRUCTION_FIRE_NOW

      设置方法:withMisfireHandlingInstructionFireNow

      ——以当前时间为触发频率立即触发执行
      ——执行至EndTIme的剩余周期次数
      ——以调度或恢复调度的时刻为基准的周期频率,EndTIme根据剩余次数和当前时间计算得到
      ——调整后的EndTIme会略大于根据StartTime计算的到的EndTIme值

    • MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY

      设置方法:withMisfireHandlingInstructionIgnoreMisfires
      含义:
      ——以错过的第一个频率时间立刻开始执行
      ——重做错过的所有频率周期
      ——当下一次触发频率发生时间大于当前时间以后,按照Interval的依次执行剩下的频率
      ——共执行RepeatCount+1次

    • MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_CO

      设置方法:withMisfireHandlingInstructionNowWithExistingCount
      含义:
      ——以当前时间为触发频率立即触发执行
      ——执行至EndTIme的剩余周期次数
      ——以调度或恢复调度的时刻为基准的周期频率,EndTIme根据剩余次数和当前时间计算得到
      ——调整后的EndTIme会略大于根据StartTime计算的到的EndTIme值

    • MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT

      设置方法:withMisfireHandlingInstructionNowWithRemainingCount
      含义:
      ——以当前时间为触发频率立即触发执行
      ——执行至EndTIme的剩余周期次数
      ——以调度或恢复调度的时刻为基准的周期频率,EndTIme根据剩余次数和当前时间计算得到
      ——调整后的EndTIme会略大于根据StartTime计算的到的EndTIme值

    • MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT

      设置方法:withMisfireHandlingInstructionNextWithRemainingCount
      含义:
      ——不触发立即执行
      ——等待下次触发频率周期时刻,执行至EndTIme的剩余周期次数
      ——以StartTime为基准计算周期频率,并得到EndTIme
      ——即使中间出现pause,resume以后保持EndTIme时间不变

    • MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT

      设置方法:withMisfireHandlingInstructionNextWithExistingCount
      含义:
      ——此指令导致trigger忘记原始设置的StartTime和repeat-count
      ——触发器的repeat-count将被设置为剩余的次数
      ——这样会导致后面无法获得原始设定的StartTime和repeat-count值

    • 默认策略

      SimpleScheduleBuilder中misfireInstruction的默认值是MISFIRE_INSTRUCTION_SMART_POLICY,这是所有Trigger默认的MisFire策略,这个策略会根据Trigger的状态和类型来自动调节MisFire策略。
      查看源码可以看到,若设置为默认策略,则按照以下规则来选择MisFire策略

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

    CronTrigger

    CronTrigger能提供比SimpleTrigger更具有实际意义的调度方案,调度规则基于Cron表达式。

    Demo
    TriggerBuilder.newTrigger()
        .withSchedule(CronScheduleBuilder.cronSchedule("0 0/30 * * * ? "))
        .forJob("jobA", "groupA")
        .build();
    
    Misfire策略
    • MISFIRE_INSTRUCTION_DO_NOTHING

      设置方法:withMisfireHandlingInstructionDoNothing
      含义:
      ——不触发立即执行
      ——等待下次Cron触发频率到达时刻开始按照Cron频率依次执行

    • MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY

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

    • MISFIRE_INSTRUCTION_FIRE_ONCE_NOW

      设置方法:withMisfireHandlingInstructionFireAndProceed
      含义:
      ——以当前时间为触发频率立刻触发一次执行
      ——然后按照Cron频率依次执行

    • 默认策略

      在SimpleTrigger中已经提到所有trigger的默认Misfire策略都是MISFIRE_INSTRUCTION_SMART_POLICY,SimpleTrigger会根据tirgger的状态来调整具体的Misfire策略,而CronTrigger的默认Misfire策略会被CronTrigger解释为MISFIRE_INSTRUCTION_FIRE_NOW,具体可以参照CronTrigger实现类的源码

    Cron表达式

    Cron([krɑn]):代表100万年,是英文中最大的时间单位。

    Cron表达式对特殊字符的大小写不敏感。

    时间 强制 允许值 允许的特殊字符
    Seconds yes 0-59 ,_*/
    Minutes yes 0-59 ,_*/
    Hours yes 0-23 ,_*/
    Day Of month yes 1-31 ,_*?/LW
    Month yes 1-12 or JAX-DEC ,_*/
    Day of Week yes 1-7 or SUN-SAT ,_*?/L#
    Year no empty,1970-2099 ,_*/
    • * :可用在所有字段中,表示对应时间的每一时刻
    • ?:在日期和星期中使用,通常表示无意义的值
    • -:表达一个范围,在小时中10-12表示10点到12点,即10,11,12
    • ,:表示一个列表值
    • /:x/y表示一个等步长序列,x为起始值,y为增量步长值。如在分钟中0/15,表示0,15,30,45秒;也可以使用 */15 ,等同于 0/15
    • L:再日期和星期中使用,代表"Last"。在日期中,代表最后一天,在星期中,代表星期六,等同于7(西方国家认为星期六是一周的最后一天)。例如6L在星期中代表最后一个星期五。
    • W:只能出现在日期中,是对前导日期的修饰,表示离该日期最近的工作日。如15W,如果15日是周天,则匹配16号周一,如果15号是周六,则匹配14号周五。匹配不能跨月,只能指定单一时间。
    • LW:在日期中使用,表示当月最后一天
    • #:只能在星期中使用,表示当月某个工作日。6#7表示第7个星期五(6代表星期五,#7代表第7个),如果当月没有第七个星期5,则不触发。
    • C:只能在日期和星期中使用,代表“Calendar”。意思计划所有关联的日期,如果日期没有被关联,则相当于日期中的所有日期。

    Spring中使用Quartz

    在Spring中使用Quartz比较简单,导入相关依赖,进行配置即可。(后续补充)

    <bean name="quartzScheduler"
          class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="dataSource">
            <ref bean="myDataSource" /> //在这里指定数据源,配置中会失效
        </property>
        <!-- 指定Spring容器 -->
        <property name="applicationContextSchedulerContextKey" value="applicationContextKey" />
        <property name="configLocation" value="classpath:quartz.properties" />
    </bean>
    
    #==============================================================
    #Configure Main Scheduler Properties
    #==============================================================
    org.quartz.scheduler.instanceName = quartzScheduler
    org.quartz.scheduler.instanceId = AUTO
    
    #==============================================================
    #Configure JobStore
    #==============================================================
    org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
    org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
    org.quartz.jobStore.tablePrefix = QRTZ_
    org.quartz.jobStore.isClustered = true
    org.quartz.jobStore.clusterCheckinInterval = 20000
    #org.quartz.jobStore.dataSource = myDS
    
    #==============================================================
    #Configure DataSource
    #==============================================================
    #org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver
    #org.quartz.dataSource.myDS.URL =jdbc:mysql://localhost:3306/quartz_db?useUnicode=true&characterEncoding=UTF-8
    #org.quartz.dataSource.myDS.user = root
    #org.quartz.dataSource.myDS.password = 123456
    #org.quartz.dataSource.myDS.maxConnections = 30
    
    #==============================================================
    #Configure ThreadPool
    #==============================================================
    org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
    org.quartz.threadPool.threadCount = 100
    org.quartz.threadPool.threadPriority = 5
    org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
    
    

    相关文章

      网友评论

          本文标题:Spring中任务调度

          本文链接:https://www.haomeiwen.com/subject/spkuictx.html