前言

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

 

Quartz总结的更多相关文章

  1. 免费开源的DotNet任务调度组件Quartz.NET(.NET组件介绍之五)

    很多的软件项目中都会使用到定时任务.定时轮询数据库同步,定时邮件通知等功能..NET Framework具有“内置”定时器功能,通过System.Timers.Timer类.在使用Timer类需要面对 ...

  2. Quartz

    Quartz是一个开源的作业调度框架,它完全由Java写成,并设计用于J2SE和J2EE应用中.它提供了巨大的灵 活性而不牺牲简单性.你能够用它来为执行一个作业而创建简单的或复杂的调度. eg: ja ...

  3. Spring Quartz实现任务调度

    任务调度 在企业级应用中,经常会制定一些"计划任务",即在某个时间点做某件事情 核心是以时间为关注点,即在一个特定的时间点,系统执行指定的一个操作 任务调度涉及多线程并发.线程池维 ...

  4. topshelf和quartz内部分享

    阅读目录: 介绍 基础用法 调试及安装 可选配置 多实例支持及相关资料 quartz.net 上月在公司内部的一次分享,现把PPT及部分交流内容整理成博客. 介绍 topshelf是创建windows ...

  5. Quartz.net持久化与集群部署开发详解

    序言 我前边有几篇文章有介绍过quartz的基本使用语法与类库.但是他的执行计划都是被写在本地的xml文件中.无法做集群部署,我让它看起来脆弱不堪,那是我的罪过. 但是quart.net是经过许多大项 ...

  6. Quartz.net开源作业调度框架使用详解

    前言 quartz.net作业调度框架是伟大组织OpenSymphony开发的quartz scheduler项目的.net延伸移植版本.支持 cron-like表达式,集群,数据库.功能性能强大更不 ...

  7. quartz.net 时间表达式----- Cron表达式详解

    序言 Cron表达式:就是用简单的xxoo符号按照一定的规则,就能把各种时间维度表达的淋漓尽致,无所不在其中,然后在用来做任务调度(定时服务)的quart.net中所认知执行,可想而知这是多么的天衣无 ...

  8. Quartz.NET Windows 服务示例

    想必大家在项目中处理简单的后台持续任务或者定时触发任务的时候均使用 Thread 或者 Task 来完成,但是项目中的这种需求一旦多了的话就得将任务调度引入进来了,那今天就简单的介绍一下 Quartz ...

  9. [Quartz笔记]玩转定时调度

    简介 Quartz是什么? Quartz是一个特性丰富的.开源的作业调度框架.它可以集成到任何Java应用. 使用它,你可以非常轻松的实现定时任务的调度执行. Quartz的应用场景 场景1:提醒和告 ...

  10. 关于Quartz.NET作业调度框架的一点小小的封装,实现伪AOP写LOG功能

    Quartz.NET是一个非常强大的作业调度框架,适用于各种定时执行的业务处理等,类似于WINDOWS自带的任务计划程序,其中运用Cron表达式来实现各种定时触发条件是我认为最为惊喜的地方. Quar ...

随机推荐

  1. select可选择、同时可自行输入

    HTML部分: <li class="bl-form-group"> <label>诊断医生</label> <div class=&qu ...

  2. (转)Linux修改SSH登录欢迎语

    场景:感觉这样做挺个性的,做个记录! 1 Linux修改SSH的欢迎语 众所周知,Linux系统并没有像Windows一样自带远程桌面连接,虽然可以通过后期安装VNC之类的软件来弥补这个缺点,但用了L ...

  3. 【SignalR学习系列】5. SignalR WPF程序

    首先创建 WPF Server 端,新建一个 WPF 项目 安装 Nuget 包 替换 MainWindows 的Xaml代码 <Window x:Class="WPFServer.M ...

  4. 【HTML】dl dt dd

    摘要 看到没怎么使用过的html 标签,记录下 定义 dl 类似于 ul ,无任何样式,自定义列表容器, ul 为无序列表容器,ol 为有序列表容器 dt dd 类似于 li ,无任何样式,为帮助实现 ...

  5. 为什么 1000 == 1000会返回false,100 == 100会返回true

    给你们看一段神奇的代码 /*对这段代码可以提供如下解释 * 判断两个对象是否相等的是看两个对象的引用是否相同 如果相同那么就返回true否则返回false * Integer会对-128~127之间的 ...

  6. 移动端和pc端事件绑定方式以及取消浏览器默认样式和取消冒泡

    ### 两种绑定方式 (DOM0)1.obj.onclick = fn; (DOM2)2. ie:obj.attachEvent(事件名称,事件函数); 1.没有捕获(非标准的ie 标准的ie底下有 ...

  7. 使用Fiddler调试手机端页面请求/抓包

    简介 Fiddler作为一个强大的抓包工具,也是非常强大的http(s)协议分析工具,我们通常用它跟踪请求,PC端使用这里暂不做介绍(这里前提是熟悉PC端的使用),使用很简单. 那么我们如何来用它来跟 ...

  8. java Socket(详解)转载

    在客户/服务器通信模式中, 客户端需要主动创建与服务器连接的 Socket(套接字), 服务器端收到了客户端的连接请求, 也会创建与客户连接的 Socket. Socket可看做是通信连接两端的收发器 ...

  9. 日常API之QQ登录

    这次的QQ登录我研究了好久惹,今天终于可以和大家分享啦! 大家都知道,QQ登录有很多方法,例如使用账号密码登录,手机版企鹅扫码登录等等(这些方法只能验证QQ是否成功登录,并没有聊天等功能) 首先就来使 ...

  10. 腾讯发布 Omix 1.0 - 用 JSX 或 hyperscript 创建用户界面

    腾讯发布 Omix 1.0 - 用 JSX 或 hyperscript 创建用户界面 今天,腾讯正式开源发布 Omix 1.0, 让开发者使用 JSX 或 hyperscript 创建用户界面. Gi ...