本文记录搭建quartz集群的过程和遇到的各种坑:
搭建又单节点到集群搭建非常简单,步骤以官网为准
主要内容就是添加了quartz.properties 文件,并修改org.quartz.jobStore.class属性(集群搭建依赖于数据库,不能再用RAMStore)
我的配置见代码:
#============================================================================
# Configure Main Scheduler Properties
#============================================================================
org.quartz.scheduler.instanceName=schedules
org.quartz.scheduler.instanceId=AUTO
org.quartz.scheduler.skipUpdateCheck=true
#============================================================================
# Configure ThreadPool
#============================================================================
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=5
org.quartz.threadPool.threadPriority=5
#============================================================================
# Configure JobStore
#============================================================================
org.quartz.jobStore.misfireThreshold=60000
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
##org.quartz.jobStore.class属性为 JobStoreTX,将任务持久化到数据中。
##因为集群中节点依赖于数据库来传播 Scheduler 实例的状态,你只能在使用 JDBC JobStore 时应用 Quartz 集群。
##这意味着你必须使用 JobStoreTX 或是 JobStoreCMT 作为 Job 存储;你不能在集群中使用 RAMJobStore。
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.dataSource=myDS
org.quartz.jobStore.tablePrefix=QRTZ_
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
#============================================================================
# Configure Datasources
#============================================================================
org.quartz.dataSource.myDS.driver=com.mysql.jdbc.Driver
org.quartz.dataSource.myDS.URL=@QUARTZ_DB_URL@
org.quartz.dataSource.myDS.user=@QUARTZ_DB_USER@
org.quartz.dataSource.myDS.password=@QUARTZ_DB_PASSWORD@
org.quartz.dataSource.myDS.maxConnections=5
org.quartz.dataSource.myDS.validationQuery=select 0
在scheduler.xml中添加标红的内容,支出quartz配置文件的位置。scheduler.xml的其他内容下文再详解
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="tarzanDumpTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail">
<bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="durability" value="true" />
<property name="requestsRecovery" value="true" />
<property name="name" value="tarzanDump"/>
<property name="jobClass">
<value>com.my.scheduler.MyDetailQuartzJobBean</value>
</property>
<property name="jobDataAsMap">
<map>
<entry key="targetObject" value="tarzanDump" />
<entry key="targetMethod" value="execute" />
</map>
</property>
</bean>
</property>
<property name="cronExpression" value="0 0 3 * * ? *"></property>
</bean>
<bean id="cacheJamEnrollNumTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail">
<bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="durability" value="true" />
<property name="requestsRecovery" value="true" />
<property name="name" value="cacheJamEnrollNum"/>
<property name="jobClass">
<value>com.my.scheduler.MyDetailQuartzJobBean</value>
</property>
<property name="jobDataAsMap">
<map>
<entry key="targetObject" value="cacheJamEnrollNum" />
<entry key="targetMethod" value="execute" />
</map>
</property>
</bean>
</property>
<property name="cronExpression" value="0 0/10 * * * ? * "/>
</bean>
<bean id="SpringJobSchedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="tarzanDumpTrigger"/>
<ref bean="cacheJamEnrollNumTrigger"/>
</list>
</property>
<property name="applicationContextSchedulerContextKey" value="applicationContext" />
<property name="configLocation" value="./WEB-INF/quartz.properties"/>
</bean>
</beans>
可以看到,跟单节点配置几乎没有太多的区别。但是你会发现,里面的坑很多。一一说起
spring创建JobDetail的定时任务有两种方式:
第一种方式是利用Spring封装的Quartz类进行特定方法的实现,第二种是通过透明的使用Quartz达到定时任务开发的目的,总体说第二种对开发人员更方便!
配置Spring的任务调度抽象层简化了任务调度,在Quartz的基础上提供了更好的调度对象。Spring使用Quartz框架来完成任务调度,创建Quartz的作业Bean(JobDetail),有一下两种方法:
1:利用JobDetailBean包装QuartzJobBean子类(即Job类)的实例。
2:利用MethodInvokingJobDetailFactoryBean工厂Bean包装普通的Java对象(即Job类)。
说明:
1:采用第一种方法 创建job类,一定要继承QuartzJobBean ,实现 executeInternal(JobExecutionContextjobexecutioncontext)方法,此方法就是被调度任务的执行体,然后将此Job类的实例直接配置到JobDetailBean中即可。这种方法和在普通的Quartz编程中是一样的。
2:采用第二种方法 创建Job类,无须继承父类,直接配置MethodInvokingJobDetailFactoryBean即可。但需要指定一下两个属性:
targetObject:指定包含任务执行体的Bean实例。
targetMethod:指定将指定Bean实例的该方法包装成任务的执行体。
第二种情况下,因为spring的methodInvoker不可序列化,没有办法在持久化到数据库中,被我先抛弃了(网上也是有解决办法的,我没有仔细去看,采用了第一种方式)
我们可以这样配置:
<bean id="cronTriggerPunch"
class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail">
<!-- 使用嵌套Bean的方式来定义任务Bean -->
<bean class="org.springframework.scheduling.quartz.JobDetailBean">
<!-- 指定任务Bean的实现类 -->
<property name="jobClass" value="com.my.MyJob"/><!-- MyJob需要集成QuartzJobBean -->
</property>
<!-- 指定Cron表达式:周一到周五7点、12点执行调度 -->
<property name="cronExpression"
value="0 0 7,12 ? * MON-FRI"/>
</bean>
这种情况下,JobDetailBean里必须要指定jobClass这个属性。
可能有这样两种情况,
1.我们的一个其他组件需要用到一个MyJob的实例,这种情况下,我们可能期望把MyJob交给spring来管理,而在JobDetailBean中只是指向spring中的这个bean就好了
2.MyJob中的某些属性不能序列化。
如果只是第一种情况的话,可以用封装来解决问题。第二个问题就不是那么好办了。有些属性是真的没有办法被实例化或者代价很大。
下面介绍种解决方法:
自己定义一个MyJobBean继承QuartzJobBean
public class MyJobBean extends QuartzJobBean{
private static final Logger LOG = Logger.getLogger(MyJobBean.class);
private String targetObject;
private String targetMethod;
private ApplicationContext ctx;
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
try {
Object otargetObject = ctx.getBean(targetObject);
Method m = null;
try {
m = otargetObject.getClass().getDeclaredMethod(targetMethod);
m.invoke(otargetObject);
} catch (SecurityException e) {
LOG.error(e.getMessage(),e);
} catch (NoSuchMethodException e) {
LOG.error(e.getMessage(),e);
}
} catch (Exception e) {
throw new JobExecutionException(e);
}
}
public void setApplicationContext(ApplicationContext applicationContext) {
this.ctx = applicationContext;
}
public void setTargetObject(String targetObject) {
this.targetObject = targetObject;
}
public void setTargetMethod(String targetMethod) {
this.targetMethod = targetMethod;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="tarzanDump" class="com.my.TarzanDump"/>
<bean id="tarzanDumpTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail">
<bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="durability" value="true" />
<property name="requestsRecovery" value="true" />
<property name="name" value="tarzanDump"/>
<property name="jobClass">
<value>com.my.scheduler.MyDetailQuartzJobBean</value>
</property>
<property name="jobDataAsMap">
<map>
<entry key="targetObject" value="tarzanDump" />
<entry key="targetMethod" value="execute" />
</map>
</property>
</bean>
</property>
<property name="cronExpression" value="0 0 3 * * ? *"></property>
</bean>
<bean id="cacheJamEnrollNumTrigger" class="com.my.cacheJamEnrollNumTrigger"/>
<bean id="cacheJamEnrollNumTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail">
<bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="durability" value="true" />
<property name="requestsRecovery" value="true" />
<property name="name" value="cacheJamEnrollNum"/>
<property name="jobClass">
<value>com.my.scheduler.MyDetailQuartzJobBean</value>
</property>
<property name="jobDataAsMap">
<map>
<entry key="targetObject" value="cacheJamEnrollNum" />
<entry key="targetMethod" value="execute" />
</map>
</property>
</bean>
</property>
<property name="cronExpression" value="0 0/10 * * * ? * "/>
</bean>
<bean id="SpringJobSchedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="tarzanDumpTrigger"/>
<ref bean="cacheJamEnrollNumTrigger"/>
</list>
</property>
<property name="applicationContextSchedulerContextKey" value="applicationContext" />
<property name="configLocation" value="./WEB-INF/quartz.properties"/>
</bean>
</beans>
这种情况下tarzanDum和cacheJamEnrollNum这些bean都是在spring中管理,在MyJobBean从spring中获取到这些bean,并反射出来调用在scheduler.xml里指定的方法。可以解决上述的两个难题。
但是你会发现,数据库里的jobDetail序列化的实际上只是MyJobBean这个类的实例,并不涉及真正的job(tarzanDump和cacheJamEnrollNum)。当quartz节点从数据库里取到MyJobBean的时候,再向spring中请求获得相应的bean和方法。