目录
quartz企业级开发中的常见应用的一些问题及常见的解决方案
Quartz 基本概念及原理
Quartz Scheduler 开源框架
Quartz 是 OpenSymphony 开源组织在任务调度领域的一个开源项目,完全基于 Java 实现。该项目于 2009 年被 Terracotta 收购,目前是 Terracotta 旗下的一个项目。读者可以到 http://www.quartz-scheduler.org/站点下载 Quartz 的发布版本及其源代码。笔者在产品开发中使用的是版本 1.8.4,因此本文内容基于该版本。本文不仅介绍如何应用 Quartz 进行开发,也对其内部实现原理作一定讲解。
作为一个优秀的开源调度框架,Quartz 具有以下特点:
- 强大的调度功能,例如支持丰富多样的调度方法,可以满足各种常规及特殊需求;
- 灵活的应用方式,例如支持任务和调度的多种组合方式,支持调度数据的多种存储方式;
- 分布式和集群能力,Terracotta 收购后在原来功能基础上作了进一步提升。本文暂不讨论该部分内容
另外,作为 Spring 默认的调度框架,Quartz 很容易与 Spring 集成实现灵活可配置的调度功能。
下面是本文中用到的一些专用词汇,在此声明:
scheduler:
任务调度器
trigger:
触发器,用于定义任务调度时间规则
job:
任务,即被调度的任务
misfire:
错过的,指本来应该被执行但实际没有被执行的任务调度
Quartz 任务调度的基本实现原理
核心元素
Quartz 任务调度的核心元素是 scheduler, trigger 和 job,其中 trigger 和 job 是任务调度的元数据, scheduler 是实际执行调度的控制器。
在 Quartz 中,trigger 是用于定义调度时间的元素,即按照什么时间规则去执行任务。Quartz 中主要提供了四种类型的 trigger:SimpleTrigger,CronTirgger,DateIntervalTrigger,和 NthIncludedDayTrigger。这四种 trigger 可以满足企业应用中的绝大部分需求。我们将在企业应用一节中进一步讨论四种 trigger 的功能。
在 Quartz 中,job 用于表示被调度的任务。主要有两种类型的 job:无状态的(stateless)和有状态的(stateful)。对于同一个 trigger 来说,有状态的 job 不能被并行执行,只有上一次触发的任务被执行完之后,才能触发下一次执行。Job 主要有两种属性:volatility 和 durability,其中 volatility 表示任务是否被持久化到数据库存储,而 durability 表示在没有 trigger 关联的时候任务是否被保留。两者都是在值为 true 的时候任务被持久化或保留。一个 job 可以被多个 trigger 关联,但是一个 trigger 只能关联一个 job。
在 Quartz 中, scheduler 由 scheduler 工厂创建:DirectSchedulerFactory 或者 StdSchedulerFactory。 第二种工厂 StdSchedulerFactory 使用较多,因为 DirectSchedulerFactory 使用起来不够方便,需要作许多详细的手工编码设置。 Scheduler 主要有三种:RemoteMBeanScheduler, RemoteScheduler 和 StdScheduler。本文以最常用的 StdScheduler 为例讲解。这也是笔者在项目中所使用的 scheduler 类。
Quartz 核心元素之间的关系如下图所示:
线程视图
Scheduler 调度线程主要有两个: 执行常规调度的线程,和执行 misfired trigger 的线程。常规调度线程轮询存储的所有 trigger,如果有需要触发的 trigger,即到达了下一次触发的时间,则从任务执行线程池获取一个空闲线程,执行与该 trigger 关联的任务。Misfire 线程是扫描所有的 trigger,查看是否有 misfired trigger,如果有的话根据 misfire 的策略分别处理。下图描述了这两个线程的基本流程:
基本的开发流程及简单实例
下载相应的开发包
http://www.quartz-scheduler.org/downloads/
简单实例
- 准备数据库和Quartz用的数据表
新建数据库,并通过建表语句生成
建表语句 | 建表结构 |
- 定义任务实现类
实现无状态的Job接口 |
|
- 实现主程序
通过cron实现每隔两秒钟执行一次的任务调度 |
|
- 核心代码
暂停任务 | Scheduler scheduler = schedulerFactoryBean.getScheduler(); JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup()); scheduler.pauseJob(jobKey); |
恢复任务 | Scheduler scheduler = schedulerFactoryBean.getScheduler(); JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup()); scheduler.resumeJob(jobKey); |
删除任务 | Scheduler scheduler = schedulerFactoryBean.getScheduler(); JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup()); scheduler.deleteJob(jobKey); |
立即运行任务 | Scheduler scheduler = schedulerFactoryBean.getScheduler(); JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup()); scheduler.triggerJob(jobKey); |
- 运行程序,查看数据库和运行结果
表名 | 表记录 |
qrtz_triggers | |
qrtz_job_details |
quartz企业级开发中的常见应用的一些问题及常见的解决方案
使用有状态的StatefulJob还是无状态的Job | 在quartz中,Job是一个接口,企业应用需要实现这个接口,以定义自己的任务,基本来说,任务分为有状态和无状态两种,实现Job接口的任务缺省为无状态的,Quartz中海油一个应用接口StatefulJob,是有状态的,有状态的, 当任务未执行完,是不能并发执行的,比如若有些任务不能并发执行,可以考虑有状态的接口,并配合misfiredjob |
如何设置Quartz的线程池和并发任务 | Quartz自带了一个线程池的实现:SimpleThreadPool,并提供了默认的配置参数,不如线程数等,并不是线程数,越多越好,可以根据自己的业务灵活调整 |
如何处理Misfired任务 | 1)系统因为某些原因被重启。在系统关闭到重新启动之间的一段时间里,可能有些任务会 被 misfire; 2)Trigger 被暂停(suspend)的一段时间里,有些任务可能会被 misfire; 3)线程池中所有线程都被占用,导致任务无法被触发执行,造成 misfire; 4)有状态任务在下次触发时间到达时,上次执行还没有结束; 为了处理 misfired job,Quartz 中为 trigger 定义了处理策略,主要有下面两种: MISFIRE_INSTRUCTION_FIRE_ONCE_NOW:针对 misfired job 马上执行一次; MISFIRE_INSTRUCTION_DO_NOTHING:忽略 misfired job,等待下次触发; 建议读者在应用开发中,将该设置作为可配置选项,使得用户可以在使用过程中,针对已经添加的 tirgger 动态配置该选项。 |