先说说项目背景,因为业务需求,项目中有很多定时任务,这自然用到了Quartz这个开源产品。而在这之前使用的Quartz主要是基于内存的,在应用程序启动时,由监听器创建定时任务,为了防止多个应用程序重新创建任务,只能在发布时,在另外的web.xml中禁用监听器。这样系统发布变得麻烦,因为不同的应用程序的配置不同。
除了发布麻烦,还有不能发挥集群的优势,一旦运行任务的服务器崩溃,集群中的其他服务器不能接受定时任务。
为了解决以上的两个问题,在系统中使用Quartz集群,同时考虑到系统中创建定时任务的入口很多,所以,本着统一管理的角度,建立了定时任务统一管理功能。
先上图:任务管理列表
编辑任务:
下面一步步说明quartz集群下如何实现该功能。(本系统架构为SpringMVC+Spring+Hibernate)
1、引入相应的的jar包
如果使用了Maven,则在pom.xml中加载quartz的dependency
<span style="font-size:14px;"><dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
</dependency></span>
或者直接引入quartz.2.2.1.jar包及其依赖包。需要主要的这个2.2.1的版本需要Spring 3.0.2以上的版本。
2、在Spring的配置文件中引入quartz的配置文件
<span style="font-size:14px;"><?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"
default-lazy-init="false" default-autowire="byName">
<!-- ClusterDemoJob -->
<bean id="clusterDemoJob" class="com.cnpc.framework.quartz.ClusterDemoJob" />
<!-- ClusterTesterJobDetail -->
<bean id="clusterDemoJobDetail"
class="com.test.framework.quartz.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="concurrent" value="true" />
<property name="shouldRecover" value="true" />
<property name="targetObject" ref="clusterDemoJob" />
<property name="targetMethod" value="execute" />
</bean>
<!-- ClusterDemoSimpleTriggerBean -->
<bean id="clusterDemoSimpleTriggerBean" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
<property name="startDelay" value="10000" />
<property name="repeatInterval" value="10000" />
<property name="jobDetail" ref="clusterDemoJobDetail" />
</bean>
<!-- ClusterDemoCronTriggerBean -->
<bean id="clusterDemoCronTriggerBean" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail" ref="clusterDemoJobDetail" />
<property name="cronExpression" value="0/10 * * * * ? *" />
</bean>
<!-- ClusterDemoSchedulerFactoryB -->
<bean id="schedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="dataSource">
<ref bean="dataSource" /><!-- 数据源引用指向,包含集群所需的所有12张表 -->
</property>
<!--
applicationContextSchedulerContextKey:是org.springframework.scheduling.quartz.SchedulerFactoryBean这个类中
把spring上下文以key/value的方式存放在了quartz的上下问中了,可以用applicationContextSchedulerContextKey所定义的key得到对应的spring上下文
-->
<!-- <property name="applicationContextSchedulerContextKey" value="applicationContextKey" />-->
<property name="configLocation" value="classpath:quartz.properties" />
<!--
autoStartup:表示是否调度随工程启动自动启动,如果是false表示不自动启动,则需要调用scheduler.start()进行启动
-->
<property name="autoStartup" value="true" />
<property name="triggers">
<list>
<ref bean="clusterDemoSimpleTriggerBean" />
<ref bean="clusterDemoCronTriggerBean" />
</list>
</property>
</bean>
</beans>
</span>
在这个xml里可以配置一些任务,这里可以说下trigger是触发器,job是任务,trigger分为比较简单的SimpleTrigger,可定义执行次数、间隔等的任务,以及比较复杂的使用时间表达式的CronTrigger。一个trigger只能对应一个job,一个job可以有多个trigger。上面的job就有两个trigger对应。其中ClusterDemoJob代码如下:
<span style="font-size:14px;">package com.test.framework.quartz.job;
import java.io.Serializable;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.test.framework.utils.DateUtil;
/**
* ClusterDemoJob.
*
* @author
*
*/
public class ClusterDemoJob implements Serializable {
/**
* Log
*/
private static Log log = LogFactory.getLog(ClusterDemoJob.class);
/**
* Execute.
*
* @throws InterruptedException
*/
public void execute() {
System.out.println("hello world-------start");
log.debug(DateUtil.getCurrDateTimeStr()+"Cluster demo execute begin!");
//Thread.sleep(1000);
log.debug("Cluster demo execute end!");
System.out.println("hello world-------end");
}
}
</span>
在实际的配置中,可能大部分的任务比较复杂,时间表达式要根据一定的逻辑动态形成,所以在xml中配置的是些比较固定的任务。这些任务在系统启动的时候,会序列化到数据库中(如果配置的是集群的话)。在我的项目中,我们的调度任务一是比较复杂,二是为了让基于内存的任务调度变为基于集群的任务调度的工作做最少的改动,所以沿用了监听器初始化调度的方法。实际上xml的配置是这样的:
<span style="font-size:14px;"><?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"
default-lazy-init="false" default-autowire="byName">
<bean id="schedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="dataSource">
<ref bean="dataSource" /><!-- 数据源引用指向,包含集群所需的所有12张表 -->
</property>
<property name="configLocation" value="classpath:quartz.properties" />
</bean>
</beans>
</span>
<span style="font-size:14px;"> </span>
其中dataSource是spring中统一配置的数据源:
<span style="font-size:14px;"><context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${dataSource.driverClassName}" />
<property name="url" value="${dataSource.url}" />
<property name="username" value="${dataSource.username}" />
<property name="password" value="${dataSource.password}" />
<property name="initialSize" value="${dataSource.initialSize}" />
<property name="maxActive" value="${dataSource.maxActive}" />
<property name="maxIdle" value="${dataSource.maxIdle}" />
<property name="minIdle" value="${dataSource.minIdle}" />
</bean></span>
3、quartz.properties配置
通过查看quartz的源代码可知,已经包含了quartz.properties配置文件,只不过默认是基于内存的配置,如果要支持集群,需要如下配置:
#============================================================================
# Configure Main Scheduler Properties
#============================================================================
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
org.quartz.scheduler.instanceId = AUTO
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false
#============================================================================
# Configure ThreadPool
#============================================================================
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 10
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
#============================================================================
# Configure JobStore
# 项目使用了oralce数据库
#============================================================================
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.misfireThreshold = 60000
org.quartz.jobStore.useProperties = false
org.quartz.jobStore.tablePrefix = QRTZ_
#org.quartz.jobStore.dataSource = myDS
org.quartz.jobStore.isClustered = true
#通过检入操作,Scheduler 也会更新自身的状态记录。clusterChedkinInterval 越小,Scheduler 节点检查失败的 Scheduler 实例就越频繁。默认值是 15000 (即15 秒)。
org.quartz.jobStore.clusterCheckinInterval = 15000
#============================================================================
# Configure DataSource
#============================================================================
#org.quartz.dataSource.myDS.driver = oracle.jdbc.driver.OracleDriver
#org.quartz.dataSource.myDS.URL = jdbc:oracle:thin:@localhost:1521:orcl
#org.quartz.dataSource.myDS.user = users
#org.quartz.dataSource.myDS.password = admin
#org.quartz.dataSource.myDS.maxConnections = 10
因为集群要将任务序列化到数据库中,所以需要用到11张表,下一篇文章将附上这11张表的创建sql。