Quartz
源文: http://www.blogjava.net/baoyaer/articles/155645.html
了解Quartz体系结构
Quartz对任务调度的领域问题进行了高度的抽象,提出了调度器、任务和触发器这3个核心的概念,
并在org.quartz通过接口和类对重要的这些核心概念进行描述
Job:是一个接口,只有一个方法void execute(JobExecutionContext context),开发者实现该接口定义运行任务,
JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中;
JobDetail:Quartz在每次执行Job时,都重新创建一个Job实例,所以它不直接接受一个Job的实例,相反它接收一个Job实现类,
以便运行时通过newInstance()的反射机制实例化Job。
因此需要通过一个类来描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息,JobDetail承担了这一角色。
通过该类的构造函数可以更具体地了解它的功用:
JobDetail(java.lang.String name, java.lang.String group, java.lang.Class jobClass),
该构造函数要求指定Job的实现类,以及任务在Scheduler中的组名和Job名称;
Trigger:是一个类,描述触发Job执行的时间触发规则。主要有SimpleTrigger和CronTrigger这两个子类。
当仅需触发一次或者以固定时间间隔周期执行,SimpleTrigger是最适合的选择;
而CronTrigger则可以通过Cron表达式定义出各种复杂时间规则的调度方案:如每早晨9:00执行,周一、周三、周五下午5:00执行等
Calendar:org.quartz.Calendar和java.util.Calendar不同,它是一些日历特定时间点的集合
(可以简单地将org.quartz.Calendar看作java.util.Calendar的集合——java.util.Calendar代表一个日历时间点,
无特殊说明后面的Calendar即指org.quartz.Calendar)。一个Trigger可以和多个Calendar关联,以便排除或包含某些时间点。
假设,我们安排每周星期一早上10:00执行任务,但是如果碰到法定的节日,任务则不执行,
这时就需要在Trigger触发机制的基础上使用Calendar进行定点排除。针对不同时间段类型,
Quartz在org.quartz.impl.calendar包下提供了若干个Calendar的实现类,
如AnnualCalendar、MonthlyCalendar、WeeklyCalendar分别针对每年、每月和每周进行定义
Scheduler:代表一个Quartz的独立运行容器,Trigger和JobDetail可以注册到Scheduler中,两者在Scheduler中拥有各自的组及名称,
组及名称是Scheduler查找定位容器中某一对象的依据,Trigger的组及名称必须唯一,JobDetail的组和名称也必须唯一(但可以和Trigger的组和名称相同,因为它们是不同类型的)。
Scheduler定义了多个接口方法,允许外部通过组及名称访问和控制容器中Trigger和JobDetail。
Scheduler可以将Trigger绑定到某一JobDetail中,这样当Trigger触发时,对应的Job就被执行。一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job。
可以通过SchedulerFactory创建一个Scheduler实例。Scheduler拥有一个SchedulerContext,它类似于ServletContext,保存着Scheduler上下文信息,
Job和Trigger都可以访问SchedulerContext内的信息。SchedulerContext内部通过一个Map,以键值对的方式维护这些上下文数据,
SchedulerContext为保存和获取数据提供了多个put()和getXxx()的方法。可以通过Scheduler# getContext()获取对应的SchedulerContext实例;
ThreadPool:Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提高运行效率
SimpleTrigger拥有多个重载的构造函数,用以在不同场合下构造出对应的实例:
●SimpleTrigger(String name, String group):通过该构造函数指定Trigger所属组和名称;
●SimpleTrigger(String name, String group, Date startTime):除指定Trigger所属组和名称外,还可以指定触发的开发时间;
●SimpleTrigger(String name, String group, Date startTime, Date endTime, int repeatCount, long repeatInterval):
除指定以上信息外,还可以指定结束时间、重复执行次数、时间间隔等参数;
●SimpleTrigger(String name, String group, String jobName, String jobGroup, Date startTime, Date endTime, int repeatCount, long repeatInterval):
这是最复杂的一个构造函数,在指定触发参数的同时,还通过jobGroup和jobName,让该Trigger和Scheduler中的某个任务关联起来。
import java.util.Date;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class SimpleJob implements Job{
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
System.out.println(arg0.getTrigger().getName()+ " triggered. time is:" + (new Date()));
}
}
import java.util.Date;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleTrigger;
import org.quartz.impl.StdSchedulerFactory;
public class SimpleTriggerRunner {
public static void main(String[] args) {
// 创建一个JobDetail实例,指定SimpleJob
JobDetail jobDetail = new JobDetail("job1_1", "jGroup1",
SimpleJob.class);
// 通过SimpleTrigger定义调度规则:马上启动,每2秒运行一次,共运行100次
SimpleTrigger simpleTrigger = new SimpleTrigger("trigger1_1", "tgroup1");
simpleTrigger.setStartTime(new Date());
simpleTrigger.setRepeatInterval(2000);
simpleTrigger.setRepeatCount(100);
// 通过SchedulerFactory获取一个调度器实例
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler;
try {
scheduler = schedulerFactory.getScheduler();
scheduler.scheduleJob(jobDetail, simpleTrigger);// 注册并进行调度
scheduler.start();// 调度启动
} catch (SchedulerException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
CronTrigger 能够提供比 SimpleTrigger 更有具体实际意义的调度方案,调度规则基于 Cron 表达式,CronTrigger 支持日历相关的重复时间间隔(比如每月第一个周一执行),
而不是简单的周期时间间隔。因此,相对于SimpleTrigger而言,CronTrigger在使用上也要复杂一些。
import org.quartz.CronExpression;
import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.impl.StdSchedulerFactory;
public class CronTriggerRunner {
public static void main(String args[]) {
try {
JobDetail jobDetail = new JobDetail("job1_2", "jGroup1",
SimpleJob.class);
// 1:创建CronTrigger,指定组及名称
CronTrigger cronTrigger = new CronTrigger("trigger1_2", "tgroup1");
CronExpression cexp = new CronExpression("0/5 * * * * ?");// -2:定义Cron表达式
cronTrigger.setCronExpression(cexp);// -3:设置Cron表达式
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
scheduler.scheduleJob(jobDetail, cronTrigger);
scheduler.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
任务调度信息存储
在默认情况下Quartz将任务调度的运行信息保存在内存中,这种方法提供了最佳的性能,因为内存中数据访问最快。
不足之处是缺乏数据的持久性,当程序路途停止或系统崩溃时,所有运行的信息都会丢失。
通过配置文件调整任务调度信息的保存策略
其实Quartz JAR文件的org.quartz包下就包含了一个quartz.properties属性配置文件并提供了默认设置。如果需要调整默认配置,
可以在类路径下建立一个新的quartz.properties,它将自动被Quartz加载并覆盖默认的设置。
先来了解一下Quartz的默认属性配置文件:
配置详细介绍:http://www.quartz-scheduler.org/documentation/quartz-1.x/configuration/ConfigMain
代码清单5 quartz.properties:默认配置
①集群的配置,这里不使用集群
#instanceName属性可为任何值,用在 JDBC JobStore 中来唯一标识实例,但是所有集群节点中必须相同
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
#属性为 AUTO即可,程序会基于主机名和时间戳来产生实例 ID
org.quartz.scheduler.instanceId = AUTO
#通过rmi作为服务器导出Quartz Scheduler
org.quartz.scheduler.rmi.export = false
#是否支持连接远程服务器的scheduler
org.quartz.scheduler.rmi.proxy = false
#在调用job之前,是否开启一个用户事务
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false
②配置调度器的线程池
#线程池实现类,采用SimpleThreadPool即可
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
#控制多少个线程被创建用来调度Job
org.quartz.threadPool.threadCount = 10
#设置工作者线程的优先级
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
③配置任务调度现场数据保存机制
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.useProperties = true
#org.quartz.jobStore.dataSource = myDS
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 30000
org.quartz.jobStore.maxMisfiresToHandleAtATime=10
#org.quartz.jobStore.doubleCheckLockMisfireHandler=fal
Quartz的属性配置文件主要包括三方面的信息:
1)集群信息;
2)调度器线程池;
3)任务调度现场数据的保存。
如果任务数目很大时,可以通过增大线程池的大小得到更好的性能。默认情况下,Quartz采用org.quartz.simpl.RAMJobStore保存任务的现场数据,
顾名思义,信息保存在RAM内存中,我们可以通过以下设置将任务调度现场数据保存到数据库中
Spring配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd" default-autowire="byName">
<bean id="scheduleService" class="com.erayt.frame.batch.ScheduleService">
<property name="scheduler" ref="scheduler" />
<property name="tgrGroup" value="${GROUP_TGR}" />
<property name="tgrBatchtoday" value="${BATCHTODAYTGR_NAME}" />
</bean>
<bean id="scheduler"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<!-- 指定数据源 -->
<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="schedulerName" value="XREPORT-PFUND-SCHEDULER" />
<property name="configLocation"
value="classpath:quartz.properties" />
<!--是org.springframework.scheduling.quartz.SchedulerFactoryBean这个类中把spring上下 文以key/value的方式存放在了quartz的上下文中了,
可以用applicationContextSchedulerContextKey所定义的key得到对应的spring上下文 -->
<property name="applicationContextSchedulerContextKey"
value="applicationContextKey" />
<property name="autoStartup" value="true" />
<property name="startupDelay" value="10" />
<property name="overwriteExistingJobs" value="true" />
<property name="triggers">
<list>
<ref local="batchTrigger"/>
</list>
</property>
<property name="jobDetails">
<list>
<ref local="pfundzJob" />
</list>
</property>
<property name="schedulerContextAsMap">
<map>
<entry key="tgrGroup" value="${GROUP_TGR}"/>
</map>
</property>
</bean>
<bean id="batchTrigger" class="org.quartz.CronTrigger">
<property name="name" value="${BATCHTODAYTGR_NAME}" />
<property name="group" value="基金任务组" />
<property name="jobGroup" value="${GROUP_JOB}" />
<property name="jobName" value="pfundzJob" /><!-- 需要找到与之对应的配置 -->
<property name="cronExpression" value="${BATCHTODAY_TIME}" />
</bean>
<bean id="pfundzJob"
class="org.springframework.scheduling.quartz.JobDetailBean">
<property name="jobClass" value="com.erayt.pfund.batch.PfundzJob" />
<property name="group" value="${GROUP_JOB}" />
<!-- 标识job是持久的,删除触发器的时候不被删除 -->
<property name="durability" value="true" />
<!--requestsRecovery属性为true,则当Quartz服务被中止后,再次启动任务时会尝试恢复执行之前未完成的所有任务-->
<property name="requestsRecovery" value="false" />
</bean>
<bean id="pfundzOverNightBatch" class="com.erayt.pfund.batch.PfundzOverNightBatch">
<property name="batchList">
<list>
<!-- <ref bean="exportDBHandler"/> -->
<ref bean="scheduleJobHandler"/>
<ref bean="changeSystemDate"/>
<ref bean="synchronizeCache"/>
</list>
</property>
</bean>
</beans>
取得Spring的调度bean并执行
public class PfundzJob extends QuartzJobBean {
private static final Logger LOGGER = LoggerFactory
.getLogger(PfundzJob.class);
private static final String APPLICATION_CONTEXT_SCHEDULER_CONTEXTKEY = "applicationContextKey";
/**
* 晚批处理
*/
public static final int TYPE_OVERNIGHT = 1;
protected void executeInternal(JobExecutionContext arg0)
throws JobExecutionException {
try {
XmlWebApplicationContext context = (XmlWebApplicationContext) arg0
.getScheduler().getContext()
.get(APPLICATION_CONTEXT_SCHEDULER_CONTEXTKEY);
PfundzOverNightBatch overNightBatch = (PfundzOverNightBatch) context.getBean("pfundzOverNightBatch");
overNightBatch.exector();
} catch (SchedulerException e) {
LOGGER.error("晚间批量任务调度失败!【触发器名称:"+arg0.getTrigger().getName()+"】");
}
}
}