Quartz 是一个开源的作业调度框架,它完全由 Java 写成,并设计用于 J2SE 和 J2EE 应用中。它提供了巨大的灵活性而不牺牲简单性。你能够用它来为执行一个作业而创建简单的或复杂的调度。
在项目中有大量的后台任务需要调度执行,如构建索引、统计报表、周期同步数据等等,要求任务调度系统具备高可用性、负载均衡特性,使用Quartz 会很方便。
下文是spring和quartz进行整合,同时支持集群部署。quartz集群的支持是通过数据库进行任务调度的感知。
1.使用的版本情况:spring 3.1.2 + quartz 1.8.6
2.数据库导入quartz使用的table,在quartz的源码压缩包中可以找到相应的sql文件,导入即可。
3.配置数据库连接池
使用spring中配好的datasource即可。
4.配置quartz.properties
##Quartz 调度任务所需的配置文件
##org.quartz.scheduler.instanceName属性可为任何值,用在 JDBC JobStore 中来唯一标识实例,但是所有集群节点中必须相同。
org.quartz.scheduler.instanceName = instanceScheduler
##org.quartz.scheduler.instanceId 属性为 AUTO即可,基于主机名和时间戳来产生实例 ID。
org.quartz.scheduler.instanceId = AUTO
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 10
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
org.quartz.jobStore.misfireThreshold = 60000
##org.quartz.jobStore.class属性为 JobStoreTX,将任务持久化到数据中。
##因为集群中节点依赖于数据库来传播 Scheduler 实例的状态,你只能在使用 JDBC JobStore 时应用Quartz 集群。
##这意味着你必须使用 JobStoreTX 或是 JobStoreCMT 作为 Job 存储;你不能在集群中使用 RAMJobStore。
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass= org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.maxMisfiresToHandleAtATime= 10
##org.quartz.jobStore.isClustered 属性为 true,你就告诉了 Scheduler 实例要它参与到一个集群当中。
##这一属性会贯穿于调度框架的始终,用于修改集群环境中操作的默认行为。
org.quartz.jobStore.isClustered = true
##org.quartz.jobStore.clusterCheckinInterval 属性定义了Scheduler 实例检入到数据库中的频率(单位:毫秒)。
##Scheduler 检查是否其他的实例到了它们应当检入的时候未检入;这能指出一个失败的 Scheduler 实例,且当前 Scheduler 会以此来接管任何执行失败并可恢复的 Job。
##通过检入操作,Scheduler 也会更新自身的状态记录。clusterChedkinInterval 越小,Scheduler 节点检查失败的 Scheduler 实例就越频繁。默认值是 15000 (即15 秒)。
org.quartz.jobStore.clusterCheckinInterval = 20000
5.配置timerTask.xml
<?xml version="1.0" encoding= "UTF-8"?>
<!--
字段名(项) 必须 值范围 特殊字符 秒 是 0-59 , - * / 分 是 0-59 , - * / 时 是 0-23 , - * /
月的某天 是 1-31 , - * ? / L W 月 是 1-12 or JAN-DEC , - * / 星期的某天 是 1-7 or
SUN-SAT , - * ? / L # 年 否 empty, 1970-2099 , - * /
-->
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd" >
<beans>
<bean id="secondProcessor" class="com.xxx.framework.back.SecondProcessor" />
<bean id="minuteProcessor" class="com.xxx.framework.back.MinuteProcessor" />
<bean id="minutesJobDetail" class="org.springframework.scheduling.quartz.JobDetailBean" >
<property name="jobClass"
value="com.xxx.framework.timerTask.MinuteQuartzJob" />
<property name="jobDataAsMap" >
<map>
<entry key="taskExecuters" >
<list>
<value>minuteProcessor</value>
</list>
</entry>
<entry key="isWorking" value="true" /><!-- 是否开启分钟级任务 -->
</map>
</property>
</bean >
<bean id="secondsJobDetail" class="org.springframework.scheduling.quartz.JobDetailBean" >
<property name="jobClass"
value="com.xxx.framework.timerTask.SecondQuartzJob" />
<property name="jobDataAsMap" >
<map>
<entry key="taskExecuters" >
<list>
<!-- <value>secondProcessor</value> -->
</list>
</entry>
<entry key="isWorking" value="true" /><!-- 是否开启秒级任务 -->
</map>
</property>
</bean >
<bean id="minutesTaskScheduler" class="org.springframework.scheduling.quartz.CronTriggerBean" >
<property name="jobDetail" ref="minutesJobDetail" />
<property name="cronExpression" value="${background.minuteExecute}" />
</bean >
<bean id="secondsTaskScheduler" class="org.springframework.scheduling.quartz.CronTriggerBean" >
<property name="jobDetail" ref="secondsJobDetail" />
<property name="cronExpression" value="${background.secondExecute}" />
</bean >
<bean id="timeTask"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean"
destroy-method="destroy" >
<property name="dataSource" >
<ref bean="source" /> <!--数据源引用指向,包含集群所需的所有表-->
</property>
<property name="transactionManager" ref="transactionManager" />
<property name="overwriteExistingJobs" value="true" />
<property name="autoStartup" value="true" />
<property name="applicationContextSchedulerContextKey" value="applicationContext" />
<property name="configLocation" value="classpath:quartz.properties" />
<!--configLocation:用于指明quartz的配置文件的位置 -->
<property name="triggers" >
<list>
<ref bean="secondsTaskScheduler" /><!-- 秒级任务调用 -->
<ref bean="minutesTaskScheduler" /><!-- 分钟级任务调用 -->
</list>
</property>
</bean >
</beans>
相关的timerTask.properties文件
background.secondExecute=0/1 * * * * ?
background.minuteExecute=0 0/1 * * * ?
background.hourExecute=0 0 0/1 * * ?
background.dayStartExecute=59 59 23 * * ?
background.dayExecute=23 23 4 * * ?
background.weekStartExecute=0 0 0 ? * MON
6.编写相关的类
1)编写接口IBatchProcessor
public interface IBatchProcessor {
void execute( int times);
}
2)实现类MinuteProcessor
public class MinuteProcessor implements IBatchProcessor{
@Override
public void execute(int times) {
System. out.println("MinuteProcessor times : " + times);
}
}
3)配置job任务两种方式:继承QuartzJobBean的类,重写executeInternal();用org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean指定类和方法。
第一种方式:
public class MinuteQuartzJob extends QuartzJobBean {
private Logger logger = Logger.getLogger(getClass());
private List<String> taskExecuters;
/**
* 分钟级计数器
*/
private static int minuterCounter = 1;
private static long lastMinuterTaskTime = -1;
private boolean isWorking ;
private Lock minuteLock = new ReentrantLock();
public List<String> getTaskExecuters() {
return taskExecuters ;
}
public void setTaskExecuters(List<String> taskExecuters) {
this.taskExecuters = taskExecuters;
}
protected void executeInternal(JobExecutionContext jobCtx)
throws JobExecutionException {
if (!isWorking )
return;
if (minuteLock .tryLock()) {
try {
SchedulerContext schedCtx = jobCtx.getScheduler().getContext();
ApplicationContext appCtx = (ApplicationContext) schedCtx
.get( "applicationContext");
long currTime = System.currentTimeMillis();
if (lastMinuterTaskTime != -1) {
// 间隔不到55秒秒就触发了一次新的分钟级任务,不执行
if (currTime - lastMinuterTaskTime < 55000) {
logger.warn("分钟级任务触发时间有问题" );
return;
}
}
lastMinuterTaskTime = currTime;
for (String taskItem : taskExecuters) {
IBatchProcessor proc = (IBatchProcessor) appCtx
.getBean(taskItem);
proc.execute( minuterCounter);
}
minuterCounter++;
} catch (Exception ex) {
ex.printStackTrace();
} finally {
minuteLock.unlock();
}
} else {
logger.warn("无法获得分钟级后台任务锁!!!!!!" );
}
}
public boolean getIsWorking() {
return isWorking ;
}
public void setIsWorking(boolean isWorking) {
this.isWorking = isWorking;
}
}
第二种方式:
直接使用会报java.io.NotSerializableException异常。很恶心!哈哈,这种方式实现的话自己去查解决方法吧(重写MethodInvokingJobDetailFactoryBean)。