zoukankan      html  css  js  c++  java
  • JAVA定时任务系列(三、定时任务框架Quartz)

    接上文......

    五、定时任务框架Quartz

    (1) 介绍Quartz

    Quartz框架是Java领域最著名的开源任务调度工具,也是目前事实上的定时任务标准,几乎全部的开源定时任务框架都是基于Quartz核心调度构建而成。

    (2) Quartz 框架的特点

    (2.1) Quartz 优点

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

    强大的调度功能,例如支持丰富多样的调度方法,可以满足各种常规及特殊需求;

    灵活的应用方式,例如支持任务和调度的多种组合方式,支持调度数据的多种存储方式;

    分布式和集群能力,Terracotta 收购后在原来功能基础上作了进一步提升。本文暂不讨论该部分内容

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

    (2.1) Quartz 缺点

    不适合大量的短任务,不适合过多节点部署

    需要把任务信息持久化到业务数据表,和业务有耦合;

    调度逻辑和执行逻辑并存于同一个项目中,在机器性能固定的情况下,业务和调度之间不可避免地会相互影响;

    quartz集群模式下,是通过数据库独占锁来唯一获取任务,任务执行并没有实现完善的负载均衡机制;

    (3) Quartz 核心组件

    核心组件图和架构:

    关键概念:
    (1) Scheduler任务调度器,是执行任务调度的控制器。本质上是一个计划调度容器,注册了全部Trigger和对应的JobDetail, 使用线程池作为任务运行的基础组件,提高任务执行效率。

    (2) Trigger触发器,用于定义任务调度的时间规则,告诉任务调度器什么时候触发任务,其中CronTrigger是基于cron表达式构建的功能强大的触发器。

    (3) Calendar日历特定时间点的集合。一个trigger可以包含多个Calendar,可用于排除或包含某些时间点

    (4) JobDetail:是一个可执行的工作,用来描述Job实现类及其它相关的静态信息,如Job的名称、监听器等相关信息。

    (5) Job任务执行接口,只有一个execute方法,用于执行真正的业务逻辑

    (6) JobStore任务存储方式,主要有RAMJobStoreJDBCJobStoreRAMJobStore是存储在JVM的内存中,有丢失和数量受限的风险,JDBCJobStore是将任务信息持久化到数据库中,支持集群。

    (3) Quartz 框架使用推荐

    (1)业务使用要满足动态修改和重启不丢失, 一般需要使用数据库进行保存。

    Quartz本身支持JDBCJobStore,但是其配置的数据表比较多,官方推荐配置可参照官方文档,超过10张表,业务使用比较重。

    在使用的时候只需要存在基本trigger配置和对应任务以及相关执行日志的表即可满足绝大部分需求。

    (2)组件化

    quartz动态任务配置信息持久化到数据库,将数据操作包装成基本jar包,供项目之间使用,引用项目只需要引入jar包依赖和配置对应的数据表,使用时就可以对Quartz配置透明。

    (3)扩展

    集群模式

    通过故障转移和负载均衡实现了任务的高可用性,通过数据库的锁机制来确保任务执行的唯一性,但是集群特性仅仅只是用来HA,节点数量的增加并不会提升单个任务的执行效率,不能实现水平扩展。

    Quartz插件*

    可以对特定需要进行扩展,比如增加触发器和任务执行日志,任务依赖串行处理场景,可参考quartz插件——实现任务之间的串行调度

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

    (4.1) 核心元素

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

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

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

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

    (4.2) Quartz 核心元素关系图

    (4.3) Quartz 线程视图和调度流程图

    在 Quartz 中,有两类线程,Scheduler 调度线程和任务执行线程,其中任务执行线程通常使用一个线程池维护一组线程。

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

    (4.4) 数据存储

    Quartz 中的triggerjob 需要存储下来才能被使用。Quartz 中有两种存储方式:RAMJobStore, JobStoreSupport,其中 RAMJobStore 是将triggerjob 存储在内存中,而 JobStoreSupport 是基于 jdbctriggerjob 存储到数据库中。RAMJobStore 的存取速度非常快,但是由于其在系统被停止后所有的数据都会丢失,所以在通常应用中,都是使用 JobStoreSupport

    Quartz 中,JobStoreSupport 使用一个驱动代理来操作 triggerjob 的数据存储:StdJDBCDelegateStdJDBCDelegate 实现了大部分基于标准 JDBC 的功能接口,但是对于各种数据库来说,需要根据其具体实现的特点做某些特殊处理,因此各种数据库需要扩展 StdJDBCDelegate 以实现这些特殊处理。 Quartz 已经自带了一些数据库的扩展实现,可以直接使用,如下图所示:

    作为嵌入式数据库的代表,Derby 近来非常流行。如果使用 Derby 数据库,可以使用上图中的 CloudscapeDelegate 作为 triggerjob 数据存储的代理类。

    (5)Quartz 开发过程中的应用

    (5.1) 如何使用不同类型的 Trigger

    前面我们提到Quartz 中四种类型的 Trigger:SimpleTriggerCronTirggerDateIntervalTrigger, 和 NthIncludedDayTrigger

    • SimpleTrigger : 一般用于实现每隔一定时间执行任务,以及重复多少次,如每 2 小时执行一次,重复执行 5 次
      SimpleTrigger 内部实现机制是通过计算间隔时间来计算下次的执行时间,这就导致其不适合调度定时的任务。例如我们想每天的 1:00AM 执行任务,如果使用 SimpleTrigger 的话间隔时间就是一天。注意这里就会有一个问题,即当有 misfired 的任务并且恢复执行时,该执行时间是随机的(取决于何时执行 misfired 的任务,例如某天的 3:00PM)。这会导致之后每天的执行时间都会变成 3:00PM,而不是我们原来期望的 1:00AM。

    • CronTirgger 类似于 LINUX 上的任务调度命令crontab即利用一个包含 7 个字段的表达式来表示时间调度方式。例如,"0 15 10 * * ? *" 表示每天的 10:15AM 执行任务。对于涉及到星期和月份的调度,CronTirgger 是最适合的,甚至某些情况下是唯一选择。例如,"0 10 14 ? 3 WED" 表示三月份的每个星期三的下午 14:10PM 执行任务。读者可以在具体用到该 trigger 时再详细了解每个字段的含义。

    • DateIntervalTriggerQuartz 1.7 之后的版本加入的,其最适合调度类似每 N(1, 2, 3...)小时,每 N 天,每 N 周等的任务。虽然 SimpleTrigger 也能实现类似的任务,但是 DateIntervalTrigger 不会受到我们上面说到的 misfired 任务的影响。另外,DateIntervalTrigger 也不会受到 DST(Daylight Saving Time, 即中国的夏令时)调整的影响。笔者就曾经因为该原因将项目中的 SimpleTrigger 改为了 DateIntervalTrigger,因为如果使用 SimpleTrigger,本来设定的调度时间就会由于 DST 的调整而提前或延迟一个小时,而 DateIntervalTrigger 不会受此影响。

    • NthIncludedDayTrigger 的用途比较简单明确,即用于每隔一个周期的第几天调度任务,例如,每个月的第 3 天执行指定的任务。

    除了上面提到的 4 种 TriggerQuartz 中还定义了一个 Calendar 类(注意,是 org.quartz.Calendar)。这个 Calendar Trigger 一起使用,但是它们的作用相反,它是用于排除任务不被执行的情况。例如,按照 Trigger 的规则在 10 月 1 号需要执行任务,但是 Calendar 指定了 10 月 1 号是节日(国庆),所以任务在这一天将不会被执行。通常来说,Calendar 用于排除节假日的任务调度,从而使任务只在工作日执行。

    (5.2) 使用有状态(StatefulJob)还是无状态的任务(Job)

    Quartz 中,Job 是一个接口,企业应用需要实现这个接口以定义自己的任务。基本来说,任务分为有状态和无状态两种。实现 Job 接口的任务缺省为无状态的。Quartz 中还有另外一个接口 StatefulJob实现 StatefulJob 接口的任务为有状态的,上一节的简单实例中,我们定义的 SampleJob 就是实现了 StatefulJob 接口的有状态任务。下图列出了 Quartz 中 Job 接口的定义以及一些自带的实现类:

    无状态任务一般指可以并发的任务,即任务之间是独立的,不会互相干扰。例如我们定义一个 trigger,每 2 分钟执行一次,但是某些情况下一个任务可能需要 3 分钟才能执行完,这样,在上一个任务还处在执行状态时,下一次触发时间已经到了。对于无状态任务,只要触发时间到了就会被执行,因为几个相同任务可以并发执行。但是对有状态任务来说,是不能并发执行的,同一时间只能有一个任务在执行。

    在笔者项目中,某些任务需要对数据库中的数据进行增删改处理。这些任务不能并发执行,否则会造成数据混乱。因此我们使用 StatefulJob 接口。现在回到上面的例子,任务每 2 分钟执行一次,若某次任务执行了 5 分钟才完成,Quartz 会怎么处理呢?按照 trigger 的规则,第 2 分钟和第 4 分钟分别会有一次预定的触发执行,但是由于是有状态任务,因此实际不会被触发。在第 5 分钟第一次任务执行完毕时,Quartz 会把第 2 和第 4 分钟的两次触发作为 misfired job进行处理。对于 misfired jobQuartz 会查看其 misfire 策略是如何设定的,如果是立刻执行,则会马上启动一次执行,如果是等待下次执行,则会忽略错过的任务,而等待下次(即第 6 分钟)触发执行。

    (5.3) 如何设置 Quartz 的线程池和并发任务

    Quartz 中自带了一个线程池的实现:SimpleThreadPool。类如其名,这只是线程池的一个简单实现,没有提供动态自发调整等高级特性。Quartz 提供了一个配置参数:org.quartz.threadPool.threadCount可以在初始化时设定线程池的线程数量,但是一次设定后不能再修改。假定这个数目是 10,则在并发任务达到 10 个以后,再有触发的任务就无法被执行了,只能等待有空闲线程的时候才能得到执行。因此有些 trigger 就可能被 misfire。但是必须指出一点,这个初始线程数并不是越大越好。当并发线程太多时,系统整体性能反而会下降,因为系统把很多时间花在了线程调度上。根据一般经验,这个值在 10 -- 50 比较合适。

    对于一些注重性能的线程池来说,会根据实际线程使用情况进行动态调整,例如初始线程数,最大线程数,空闲线程数等。读者在应用中,如果有更好的线程池,则可以在配置文件中通过下面参数替换

    SimpleThreadPool:org.quartz.threadPool.class = myapp.GreatThreadPool
    

    (5.4) 如何处理 Misfired 任务

    Quartz 应用中,misfired job 是经常遇到的情况。一般来说,下面这些原因可能造成 misfired job

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

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

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

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

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

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

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

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

    (5.5) 如何保留已经结束的 Trigger

    Quartz中,一个tirgger在最后一次触发完成之后,会被自动删除。Quartz 默认不会保留已经结束的 trigger,如下面 Quartz 源代码所示:

    但是在实际应用中,有些用户需要保留以前的 trigger,作为历史记录,或者作为以后创建其他 trigger 的依据。如何保留结束的 trigger 呢?

    一个办法是应用开发者自己维护一份数据备份记录,并且与 Quartz 原表的记录保持一定的同步。这个办法实际操作起来比较繁琐,而且容易出错,不推荐使用。

    另外一个办法是通过修改并重新编译Quartz trigger 类,修改其默认的行为。我们以 org.quartz.SimpleTrigger 为例,修改上面代码中 if (!mayFireAgain()) 部分的代码如下:

    另外我们需要在 SimpleTrigger 中定义一个新的类属性:needRetain,如下所示:

    在定义自己的 trigger 时,设置该属性,就可以选择是否在 trigger 结束时删除 trigger。如下代码所示:

    有人可能会考虑通过定义一个新的类,然后继承 org.quartz.SimpleTrigger 类并覆盖 executionComplete( ) 方法来实现。但是这种方法是行不通的,因为 Quartz 内部在处理时会根据 trigger 的类型重新生成 SimpleTrigger 类的实例,而不是使用我们自己定义的类创建的实例。这一点应该是 Quartz 的一个小小的不足之处,因为它把扩展 trigger 的能力堵死了。好在Quartz是开源的,我们可以根据需要进行修改。

    (6)Quartz 框架,简单应用

    (6.1) Quartz 应用场景

    • 餐厅系统会在每周四晚上的22点自动审核并生成报表
    • 人事系统会在每天早晨8点给有待办的人员自动发送Email提醒

    (6.2) 简单使用(重复执行)

    (6.2.1) 引入依赖
    <dependency>
    	<groupId>org.quartz-scheduler</groupId>
    	<artifactId>quartz</artifactId>
    	<version>2.3.0</version>
    </dependency>
    
    (6.2.2) 创建HelloJob实现Job接口
    
    public class HelloJob implements Job{
    	private static final SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    	@Override
    	public void execute(JobExecutionContext context) throws JobExecutionException {
    		Date now = new Date();
    		String currentTime = sdf.format(now);
    		System.out.println("执行时间为:"+currentTime);
    	}
    }
    
    (6.2.3) 创建HelloScheduler触发任务
    public class HelloScheduler {
    	public static void main(String[] args) throws SchedulerException {
    		//创建jobDetail绑定HelloJob
    		JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
    						.withIdentity("myJob","myGroup").build();
    		//创建触发器trigger每个2秒执行一次,一直执行
    		Trigger trigger = TriggerBuilder.newTrigger().withIdentity("mtTrigger", "myGroup").startNow()
    						.withSchedule(SimpleScheduleBuilder.simpleSchedule()
    						.withIntervalInSeconds(2).repeatForever()).build();
    		//创建调度者工厂
    		SchedulerFactory schedulerFactory = new StdSchedulerFactory();
    		//创建调度者
    		Scheduler scheduler = schedulerFactory.getScheduler();
    		//启动调度器
    		scheduler.start();
    		//设置调度任务
    		scheduler.scheduleJob(jobDetail, trigger);
    	}
    }
    

    执行结果:

    执行时间为:2019-04-15 23:45:41
    23:45:43.388 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'myGroup.myJob', class=com.xuxu.quartz.HelloJob
    23:45:43.388 [DefaultQuartzScheduler_Worker-9] DEBUG org.quartz.core.JobRunShell - Calling execute on job myGroup.myJob
    23:45:43.388 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
    执行时间为:2019-04-15 23:45:43
    23:45:45.387 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'myGroup.myJob', class=com.xuxu.quartz.HelloJob
    23:45:45.387 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
    23:45:45.391 [DefaultQuartzScheduler_Worker-10] DEBUG org.quartz.core.JobRunShell - Calling execute on job myGroup.myJob
    执行时间为:2019-04-15 23:45:45
    

    (6.3) 定时执行使用cron表达式确定时间

    (6.3.1) 引入依赖
    <dependency>
    	<groupId>org.quartz-scheduler</groupId>
    	<artifactId>quartz</artifactId>
    	<version>2.3.0</version>
    </dependency>
    
    (6.3.2)创建HelloJob实现job接口,任务执行时输出时间
    public class HelloJob implements Job{
    	private final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    	@Override
    	public void execute(JobExecutionContext arg0) throws JobExecutionException {
    		Date now = new Date();
    		String currentDate = sdf.format(now);
    		System.out.println("现在时间是:"+currentDate+":开始执行任务生成表格,或者发送邮件");
    	}
    }
    
    (6.2.3) 创建触发类CronScheduler
    public class CronScheduler {
        public static void main(String[] args) throws Exception {
            JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
                    .withIdentity("myJob").build();
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("cronTrigger")
                    //cron表达式 这里定义的是  在每天下午2点到下午2:59期间的每1分钟触发
                    .withSchedule(CronScheduleBuilder.cronSchedule("0 * 14 * * ?"))
                    .build();
            SchedulerFactory factory = new StdSchedulerFactory();
            //创建调度器
            Scheduler scheduler = factory.getScheduler();
            //启动调度器
            scheduler.start();
            //jobDetail和trigger加入调度
            scheduler.scheduleJob(jobDetail, trigger);
        }
    }
    

    这里通过cron表达式确定时间规则
    一般我们会使用cron生成器
    执行结果如下:

    现在时间是:2019-04-16 09:21:00:开始执行任务生成表格,或者发送邮件
    

    (7) Quartz的三大API

    (7.1) Job

    JobDetail & Job & JobDataMap
    JobDetail任务的定义,而Job是任务的执行逻辑。在JobDetail里会引用一个Job Class定义。每一个JobDetail都会有一个JobDataMapJobDataMap本质就是一个Map的扩展类,只是提供了一些更便捷的方法,比如getString()之类的。

    public class CronScheduler {
    	public static void main(String[] args) throws Exception {
    		JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
    				//添加jobname,jobgroup
    				.withIdentity("myJob","myGroup")
    				//jobDataMap信息
    				.usingJobData("message","this is a message")
    				.build();
    		Trigger trigger = TriggerBuilder.newTrigger()
    				.withIdentity("cronTrigger")
    				//cron表达式 这里定义的是4月16日早上9点21分开始执行
    				.withSchedule(CronScheduleBuilder.cronSchedule("0 00 10 16 4 ? *"))
    				.build();
    		SchedulerFactory factory = new StdSchedulerFactory();
    		//创建调度器
    		Scheduler scheduler = factory.getScheduler();
    		//启动调度器
    		scheduler.start();
    		//jobDetail和trigger加入调度
    		scheduler.scheduleJob(jobDetail, trigger);
    	}
    }
    
    public class HelloJob implements Job{
    	private final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    	@Override
    	public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
    		Date now = new Date();
    		String currentDate = sdf.format(now);
    		JobDetail jobDetail = jobExecutionContext.getJobDetail();
    		JobDataMap jobDataMap = jobDetail.getJobDataMap();
    		JobKey jobKey = jobDetail.getKey();
    		String jobName = jobKey.getName();
    		String group = jobKey.getGroup();
    		String message = (String) jobDataMap.get("message");
    		System.out.println("现在时间是:"+currentDate+":开始执行任务生成表格,或者发送邮件");
    		System.out.println("jobName---"+jobName);
    		System.out.println("group---"+group);
    		System.out.println("message---"+message);
    	}
    }
    
    现在时间是:2019-04-16 10:00:00:开始执行任务生成表格,或者发送邮件
    jobName---myJob
    group---myGroup
    message---this is a message
    

    (7.2) Tigger

    (7.2.1)startTime和endTime

    有时候我们希望一个定时任务在一定的时间内是每天执行,比如2017年11月24日到2017年12月15日之间执行,这时候我们就要使用startTimeendTime来限定事件范围了。例子中我们把时间规定在几秒钟之内运行,方便查看效果。

    public class HelloJob implements Job{
    	private static final SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    	@Override
    	public void execute(JobExecutionContext context) throws JobExecutionException {
    		Date now = new Date();
    		String currentTime = sdf.format(now);
    		System.out.println("执行时间为:"+currentTime);
    	}
    }
    
    public class HelloScheduler {
    	public static void main(String[] args) throws SchedulerException {
    		//创建jobDetail绑定HelloJob
    		JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
    						.withIdentity("myJob","myGroup").build();
    		//设定开始时间,结束时间确定范围
    		Date triggerStartTime = new Date();
    		//3秒后开始执行
    		triggerStartTime.setTime(triggerStartTime.getTime()+3000);
    		Date triggerEndTime = new Date();
    		//10秒后结束执行
    		triggerEndTime.setTime(triggerEndTime.getTime()+10000);
    		//创建触发器trigger每个2秒执行一次,一直执行
    		Trigger trigger = TriggerBuilder.newTrigger().withIdentity("mtTrigger", "myGroup")
    						.startAt(triggerStartTime)
    						.endAt(triggerEndTime)
    						.withSchedule(SimpleScheduleBuilder.simpleSchedule()
    						.withIntervalInSeconds(2).repeatForever()).build();
    		//创建调度者工厂
    		SchedulerFactory schedulerFactory = new StdSchedulerFactory();
    		//创建调度者
    		Scheduler scheduler = schedulerFactory.getScheduler();
    		//启动调度器
    		scheduler.start();
    		//设置调度任务
    		scheduler.scheduleJob(jobDetail, trigger);
    	}
    }
    

    3秒后执行,10秒内结束执行

    执行时间为:2019-04-16 10:36:09
    执行时间为:2019-04-16 10:36:11
    执行时间为:2019-04-16 10:36:13
    执行时间为:2019-04-16 10:36:15
    
    (7.2.2)BaseCalndar

    calendar不是java.util.Calendarcalendar是为了补充Trigger的时间,可以排除或加入一下特定的时间QuartzCalender 专门用于屏闭一个时间区间,使 Trigger 在这个区间中不被触发。

    • AnnualCalendar:排除每一年中指定的一天或者多少天 ,精度是天
    • CronCalendar:使用表达式排除某些时间段不执行,精度取决于Cron表达式,最大精度到秒
    • DailyCalendar:指定的时间范围内的每一天不执行,指定每天的时间段,格式是HH:MM[:SS[:mmm]]。也就是最大精度可以到毫秒。
    • HolidayCalendar:排除节假日,精度到天
    • MonthlyCalendar:排除月份中的数天,可选值为1-31。精度是天
    • WeeklyCalendar:排除星期中的一天或多天,可选值比如为java.util.Calendar.SUNDAY,精度是天。

    这里使用CronCalendar排除

    public class HelloJob implements Job{
    	private static final SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    	@Override
    	public void execute(JobExecutionContext context) throws JobExecutionException {
    		Date now = new Date();
    		String currentTime = sdf.format(now);
    		System.out.println("执行时间为:"+currentTime);
    	}
    }
    
    public class HelloScheduler {
    	public static void main(String[] args) throws SchedulerException, ParseException {
    		//创建jobDetail绑定HelloJob
    		JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
    						.withIdentity("myJob","myGroup").build();
    		//创建触发器trigger每个2秒执行一次,一直执行
    		Trigger trigger = TriggerBuilder.newTrigger().withIdentity("mtTrigger", "myGroup")
    				.withSchedule(SimpleScheduleBuilder.simpleSchedule()
    						.withIntervalInSeconds(2).repeatForever())
    				//将calendar排除规则绑定到触发器
    				.modifiedByCalendar("myCalendar")
    				.build();
    		//创建调度者工厂
    		SchedulerFactory schedulerFactory = new StdSchedulerFactory();
    		//创建调度者
    		Scheduler scheduler = schedulerFactory.getScheduler();
    		CronCalendar calendar = new CronCalendar("* * 0-12,18-23 ? * *");
    		//向Scheduler注册日历
    		scheduler.addCalendar("myCalendar", calendar, false, false);
    		//启动调度器
    		scheduler.start();
    		//设置调度任务
    		scheduler.scheduleJob(jobDetail, trigger);
    	}
    }
    
    11:39:12.270 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 0 triggers
    11:39:12.381 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 0 triggers
    

    上面指定的是0-12 18-23不执行发现12点之前没有执行

    (7.3) Trigger的实现类

    (7.3.1) CalendarIntervalTrigger

    CalendarIntervalTrigger:是一个具体的Trigger,用来触发基于定时重复的JobDetail

    Trigger将会每隔N个calendar在trigger中定义的时间单元触发一次。这个trigger不适合使用SimpleTrigger完成(例如由于每一个月的时间不是固定的描述),也不适用于CronTrigger(例如每5个月)。

    相较于SimpleTrigger有两个优势:1、更方便,比如每隔1小时执行,你不用自己去计算1小时等于多少毫秒。 2、支持不是固定长度的间隔,比如间隔为月和年。但劣势是精度只能到秒

    它适合的任务类似于:9:00 开始执行,并且以后每周 9:00 执行一次

    它的属性有:

    • interval 执行间隔
    • intervalUnit 执行间隔的单位(秒,分钟,小时,天,月,年,星期)
    CalendarIntervalScheduleBuilder
         .calendarIntervalSchedule()
         .withIntervalInDays(1)  //每天执行一次
       //.withIntervalInHours(1)
       //.withIntervalInMinutes(1)
       //.withIntervalInMonths(1)
       //.withIntervalInSeconds(1)
       //.withIntervalInWeeks(1)
       //.withIntervalInHours(1)
         .build()
    
    (7.3.2) DailyTimeIntervalTrigger

    指定每天的某个时间段内,以一定的时间间隔执行任务。并且它可以支持指定星期。

    它适合的任务类似于:指定每天9:00 至 18:00 ,每隔70秒执行一次,并且只要周一至周五执行。

    它的属性有:

    • startTimeOfDay 每天开始时间
    • endTimeOfDay 每天结束时间
    • daysOfWeek 需要执行的星期
    • interval 执行间隔
    • intervalUnit 执行间隔的单位(秒,分钟,小时,天,月,年,星期)
    • repeatCount 重复次数
    public static void main(String[] args) throws SchedulerException {
            SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            //1.创建一个jobDetail的实例,将该实例与HelloJob Class绑定
            JobDetail jobDetail = JobBuilder
                    .newJob(HelloJob.class)
                    .withIdentity("myJob", "group1") //定义name 和 group
                    .build();
     
            //2.创建一个Trigger触发器的实例
            Trigger simpleTrigger = TriggerBuilder.newTrigger()
                    .withIdentity("zhlTrigger")
                    .withSchedule(
                            DailyTimeIntervalScheduleBuilder.dailyTimeIntervalSchedule()
                                    .startingDailyAt(TimeOfDay.hourAndMinuteOfDay(8, 0)) //每天8:00开始
                                    .endingDailyAt(TimeOfDay.hourAndMinuteOfDay(17, 0)) //17:00 结束
                                    .onDaysOfTheWeek(MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY) //周一至周五执行
                                    .withIntervalInHours(1) //每间隔1小时执行一次
                                    .withRepeatCount(100) //最多重复100次(实际执行100+1次)
                    )
                    .modifiedByCalendar("holidays")   //将我们设置好的Calander与trigger绑定
                    .build();
     
            //3.创建schedule实例
            StdSchedulerFactory factory = new StdSchedulerFactory();
            Scheduler scheduler = factory.getScheduler();
            System.out.println("现在的时间 :"+sf.format(new Date()));
            System.out.println();
            System.out.println("最近的一次执行时间 :"+sf.format(scheduler.scheduleJob(jobDetail,simpleTrigger))); //scheduler与jobDetail、trigger绑定,并打印出最近一次执行的事件
            scheduler.start();
        }
    

    (8) Scheduler工厂模式

    所有的Scheduler实例应该由SchedulerFactory来创建,一般包含:StdSchedulerFactory、DirectSchedulerFactory(参数信息需要在代码中维护故不常用)。

    StdSchedulerFactory使用一组参数来创建和初始化Quartz调度器,配置参数一般存储在quartz.properties文件中,调用getScheduler方法就能创建和初始化调度器对象。

    Scheduler的主要函数:

    • Data scheduleJob(JobDetail jobDetail,Trigger trigger);
    • void start();——启动Scheduler;
    • void standby();——将Scheduler暂时挂起,可以用start()继续执行任务;
    • void shutDown()关闭Scheduler且不能被重启

    JAVA定时任务系列

    JAVA定时任务系列(一、基于注解,基于接口)
    JAVA定时任务系列(二、JDK原生定时工具:Timer)
    JAVA定时任务系列(三、定时任务框架Quartz)

  • 相关阅读:
    Evanyou Blog 彩带
    Evanyou Blog 彩带
    Evanyou Blog 彩带
    Evanyou Blog 彩带
    Evanyou Blog 彩带
    Evanyou Blog 彩带
    Evanyou Blog 彩带
    Evanyou Blog 彩带
    Evanyou Blog 彩带
    Evanyou Blog 彩带
  • 原文地址:https://www.cnblogs.com/MrYuChen-Blog/p/14794521.html
Copyright © 2011-2022 走看看