Quartz最初是由James House在souceforge上创建的一个开源定时调度框架(注:Quartz是石英的意思,受到微量电流时可产生一个固定的震动频率【石英钟原 理】),目前由OpenSymphony【http://www.opensymphony.com/quartz】提供主机维护服务,可以在上面下载到 最新的Quartz版本。目前使用Quartz的项目有Jboss、JIRA、Spring、Jarkarta等等。
对于一个调度作业,通常有两个因素构成:调度的内容【What】:即调度具体做什么工作,例如是发邮件、系统备份;调度的时间【when】:什么时候工 作,这个又包含两个内容,开始时间【调度第一次执行的时间】,调度时间【在调度开始之后,在什么时间的时候,执行定义的调度工作】。
Quartz框架提供了Job接口和Trigger接口来分别对应调度的内容和调度时间,由于Job接口和Trigger接口是完全分离的,彼此互不关 心,他们之间要建立某种关联,就需要通过Scheduler来帮助他们建立联系。
Quartz启动时,会初始化一套work线程池,这套线程被用于执行预定义的调度作业。Job和Trigger的关联根据配置的不同【基于内存的调度、 基于数据库的调度】而不同:如果是基于内存的调度,那么Job信息和Trigger信息分别放到两个HashMap中,通过RAMJobSupport来 建立关联;如果是基于数据库的调度,Job信息、Tirgger信息以及它们的关联信息都被存到数据库表中。
目前已有的调度框架除了Flux Scheduler, Enterprise Batching Queuing等一些商业版本之外,还有JDK自带的Timer和TimerTask,对于商业版本不作任何评估,目前只是对能免费使用的两个常用的调度 框架做一些比较:
JDK Timer/TimerTask和Quartz的比较
JDK Timer/TimerTask | Quartz |
每个任务一个线程 | 线程池 |
只是基于内存的调度 | 内存&数据库 |
不能支持很复杂的调度 | CronTrigger可以定义复杂的调 |
public class SimpleDemoJob implements Job{ public void execute(JobExecutionContext arg0) throws JobExecutionException { System.out.println("Hello"); } }
public class SimpleScheduler { public static void main(String[] args) throws Exception{ new SimpleScheduler().startScheduler(); } public void startScheduler()throws Exception{ Scheduler scheduler= StdSchedulerFactory.getDefaultScheduler() ; JobDetail jobDetail = new JobDetail("anotherJob","SimpleGroup",SimpleDemoJob.class); Trigger trigger = TriggerUtils.makeSecondlyTrigger(); trigger.setName("simpleTrgger"); trigger.setStartTime(new Date()); scheduler.scheduleJob(jobDetail, trigger); scheduler.start(); } }上面的两段代码完成了一个简单的调度:在控制台输出Hello, 从代码中可以看出Quartz实现了调度时间和调度作业的完全分离,它们之间的关联是通过调用Scheduler.scheduleJob方法来完成的。 同时也可以看出建立一个简单的调度需要有如下几个步骤:
通过上面的例子,我们了解了完成一个简单调度的流程,这个例子有如下几个特点:
在通常的业务场景中中,调度周期的定义是比较复杂的,有些调度只是在工作日完成,还需要考虑节假日的问题,通过SimpleTrigger很能完成,需要 使用功能更为强大的CronTrigger。
Cron是Unix系统的用于调度的后台守护进程,负责所有基于时间的事件,每隔一分钟去看一次crontab文件,看是否有需要被执行的调度任务。 Cron的调度时间定义是通过Cron表达式的。
Crontab包含六个字段:分【00-59】、时【00-23】、日【1-31】、月【1-12】、周【0-6】或者【sun-sat】,Cron表达 式允许出现特殊字符:*、/等,第六个字段为要执行的命令。
下面一个Cron命令表示每天早上8点在控制台输出 WAKE UP:
0 8 * * * echo "WAKE UP" 2>$1 /dev/console
Quartz的Cron表达式字段被扩展到了七个字段,而且最后一个字段并不是要执行的命令:
名称 | 是否必须 | 允许值 | 特殊字符 |
秒 | 是 | 0-59 | , - * / |
分 | 是 | 0-59 | , - * / |
时 | 是 | 0-23 | , - * / |
日 | 是 | 1-31 | , - * / L W C |
月 | 是 | 1-12或JAN-DEC | , - * / |
周 | 是 | 1-7或sun-sat | , - * / L C # |
年 | 否 | 空 或 1970-2099 | , - * / |
关于Cron表达式的字段说明:
例如:有一个调度任务,需要在周一到周五下午16:41执行,则这个Trigger可以按照如下方式建立:
Trigger trigger = new CronTrigger("AnotherCronTrigger",null,"0 41 16 ? * MON-FRI");
建立这个Trigger之后,和要调度的任务通过Scheduler进行关联就可以完成调度了。
到目前为止,我们可以认为前面所谈到的所有调度信息都是基于内存的【实际上修改配置项可以将它们修改为基于数据库的】,前面已经谈到过Quartz的一个很重要的优点就是支持基于数据库的调度,假定调度都是基于内存的,那么在系统出现异常或者当机的时候,这些调度信息很有可能就丢失了,使用基于数据库的调度则不会出现这样的情况了,调度信息都被存储在数据库中,系统出现异常,重新启动之后,调度信息就可以重新回复到异常前的状态。
通过配置quartz.properties可以将调度配置成基于内存的或者是基于数据库的;
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
#初始化线程池大小
org.quartz.threadPool.threadCount = 10
#线程优先级
org.quartz.threadPool.threadPriority = 5
org.quartz.jobStore.misfireThreshold = 60000
#基于内存的调度
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
Quartz支持N种数据库,基本上现在主流的都能很好的支持(Oracle, DB2, SQL Server, MySQL,Derby,FireBird, Infomix, PostgreSQL, HSQLDB)。