这几天给Spring+Quartz的集群折腾得死去活来,google了无数页总算搞定,记下一些要点备以后使用。
单独的Quartz集群在
http://unmi.blogjava.net/有Unmi翻译的Quartz Job Scheduling Framework一书做了详细说明,在此不再重复。
Spring+Quartz不集群的方式google百度也可以搜索出来一大堆,同样略过。
要点1 在Spring中使用Quartz的高级配置
问题描述 Quartz集群仅能使用JDBC JobStore工作,需要在Spring中使用Quartz的高级配置
解决办法1.1 通过SchedulerFactoryBean的configLocation属性指定Quartz配置文件的位置。
<bean id="quartzJobFactory" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="clusterTesterJobScheduledTask" />
</list>
</property>
<property name="configLocation" value="classpath:quartz.properties" />
</bean>
解决办法1.2 通过SchedulerFactoryBean的quartzProperties属性直接配置。
要点2 NotSerializableException
问题描述 在将Quartz的Job持久化到数据库的过程中产生NotSerializableException。详细异常信息为:
java.io.NotSerializableException: Unable to serialize JobDataMap for insertion into database because the value of property 'methodInvoker' is not serializable: org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean
解决办法 这是Spring的一个Bug,具体解决方案见
http://jira.springframework.org/browse/SPR-3797,上面有解决的MethodInvokingJobDetailFactoryBean代码。
要点3 还是NotSerializableException
问题描述 虽然MethodInvokingJobDetailFactoryBean的问题解决了,但是QuartzJob或者QuartzJob的属性没有实现Serializable接口(比如在QuartzJob中注入了DAO)。
解决办法3.1 如果是自己可以掌握的代码,可以为这些QuartzJob及其属性都加上实现Serializable接口。
解决办法3.2 编写一个实现Serializable接口,没有属性的QuartzJob,让它从Spring容器(ApplicationContext)中获取原来的那个QuartzJob的Bean,再调用原来QuartzJob的方法解决问题。该方法的问题在于怎么获取ApplicationContext,如果用ClassPathXmlApplicationContext,等于是另外创建了一个Spring容器(web.xml里面定义的是另外一个)。
解决办法3.3 将原有的接口暴露,在Job中想办法远程调用该接口。
这三种办法各有好坏,现在也想不出其他更好的办法,先用这些顶着吧。
要点4 集群之后把其中一个Quartz服务停了,其他的也不接手工作
问题描述 集群之后,A节点执行了大多数任务,B节点大部分时间处于空闲,停掉A节点,B节点也不会接手工作。
解决办法 修改Quartz的配置,将每个节点的org.quartz.scheduler.instanceId设置为不同的值,或者都设置为AUTO。另外org.quartz.jobStore.isClustered属性必须设为true,org.quartz.jobStore.clusterCheckinInterval属性为集群中每次检查的时间间隔(按我的理解,应该差不多等于一个服务器挂了之后,其他服务器接手的时间),单位为毫秒,默认值是15000。
要点5 MethodInvokingJobDetailFactoryBean几个属性的作用
问题描述 MethodInvokingJobDetailFactoryBean中concurrent和shouldRecover属性的作用
解释 concurrent为true,则允许一个QuartzJob并发执行,否则就是顺序执行。例如QuartzJob A执行时间为15秒,配置为每10秒执行一次;如果concurrent为true,则0秒的时候启动一次A,10秒的时候再启动一次A,20秒的时候再启动一次A,不管前面启动的A有没有执行完;如果concurrent为false,则0秒的时候启动一次A,15秒的时候A执行完毕,再第二次启动A。
shouldRecover属性为true,则当Quartz服务被中止后,再次启动或集群中其他机器接手任务时会尝试恢复执行之前未完成的所有任务。例如QuartzJob B,在每次00秒的时候启动,假如在03:00的任务执行完之后服务器1被中止,服务器2在05:15的时候才接手;如果shouldRecover属性为true,则服务器2会尝试着补回原来在04:00和05:00的时候应该做的任务,如果shouldRecover属性为false,则服务器2只会从06:00的时候再执行B。
附件是一个Demo,使用数据库为Oracle,创建数据库表的SQL脚本可以在Quartz的发行包(
http://www.opensymphony.com/quartz/download.action)中\docs\dbTables目录下找到。数据库相关配置都在quartz.properties中,可根据实际需要自行修改。