天天看点

quartz在集群环境下的最终解决方案

在集群环境下,大家会碰到一直困扰的问题,即多个

APP

下如何用

quartz

协调处理自动化

JOB

大家想象一下,现在有

A

B

C3

台机器同时作为集群服务器对外统一提供

SERVICE

C 3

台机器上各有一个

QUARTZ

,他们会按照即定的

SCHEDULE

自动执行各自的任务。

我们先不说实现什么功能,就说这样的架构其实有点像多线程。

那多线程里就会存在“资源竞争”的问题,即可能产生脏读,脏写,由于三台

APP SERVER

里都有

,因此会存在重复处理

TASK

的现象。

一般外面的解决方案是只在一台

上装

,其它两台不装,这样集群就形同虚设了;

另一种解决方案是动代码,这样就要影响到原来已经写好的

QUARTZ JOB

的代码了,这对程序开发人员来说比较痛苦;

本人仔细看了一下

Spring

的结构和

的文档,结合

Quartz

自身可以实例化进数据的特性找到了相关的解决方案。

本方案优点:

1.      

每台作为集群点的

上都可以布署

2.      

12

张表)实例化如数据库,基于数据库引擎及

High-Available

的策略(集群的一种策略)自动协调每个节点的

,当任一一节点的

非正常关闭或出错时,另几个节点的

会自动启动;

3.      

无需开发人员更改原已经实现的

,使用

SPRING+

类反射的机制对原有程序作切面重构;

本人也事先搜索了一些资料,发觉所有目前在

GOOGLE

上或者在各大论坛里提供的解决方案,要么是只解决了一部分,要么是错误的,要么是版本太老,要么就是完全抄别人的。

尤其是在使用

QUARTZ+SPRING

对数据库对象作实例化时会抛错(源于

SPRING

的一个

BUG

),目前网上的解决方案全部是错的或者干脆没说,本人在此方案中也会提出如何解决。

解决方案:

实例化进数据库,

只有实例化进入数据库后才能做集群,外面的解决方案说实例化在内存里全部是错的,把quartz-1.8.4/docs/dbTables/tables_oracle.sql

ORACLE9I2

及以上版本中执行一下会生成

张表;

生成

quartz.properties

文件,把它放在工程的

src

目录下,使其能够被编译时纳入

class path

一般我们的开发人员都喜欢使用

SPRING+QUARTZ

,因此这个

都不用怎么去写,但是在集群方案中

必写,如果不写

会调用自身

jar

包中的

作为默认属性文件,同时修改

quartz.xml

文件。

Quartz.xml

文件的内容

:

<?xml version="1.0" encoding="UTF-8"?>

">

<beans>

                <bean id="mapScheduler" lazy-init="false" autowire="no"

                                class="org.springframework.scheduling.quartz.SchedulerFactoryBean">

<property name="configLocation" value="classpath:quartz.properties" />

                                <property name="triggers">

                                                <list>

                                                                <ref bean="cronTrigger" />

                                                </list>

                                </property>

                                <!—

就是下面这句,因为该

bean

只能使用类反射来重构

                                <property name="applicationContextSchedulerContextKey" value="applicationContext" />          

</bean>

文件的内容:

org.quartz.scheduler.instanceName = mapScheduler  

org.quartz.scheduler.instanceId = AUTO 

 org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX 

 org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.oracle.weblogic.WebLogicOracleDelegate 

 org.quartz.jobStore.dataSource = myXADS 

 org.quartz.jobStore.tablePrefix = QRTZ_ 

 org.quartz.jobStore.isClustered = true 

 org.quartz.dataSource.myXADS.jndiURL=jdbc/TestQuartzDS

 org.quartz.dataSource.myXADS.jndiAlwaysLookup = DB_JNDI_ALWAYS_LOOKUP 

 org.quartz.dataSource.myXADS.java.naming.factory.initial = weblogic.jndi.WLInitialContextFactory 

 org.quartz.dataSource.myXADS.java.naming.provider.url = t3://localhost:7020 

 org.quartz.dataSource.myXADS.java.naming.security.principal = weblogic 

 org.quartz.dataSource.myXADS.java.naming.security.credentials = weblogic 

重写

QuartzJobBean

原因是在使用

quartz+spring

task

实例化进入数据库时,会产生:

serializable

的错误,原因在于:

<bean id="jobtask" class="org.springframework.scheduling.quartz.

MethodInvokingJobDetailFactoryBean

                                <property name="targetObject">

                                                <ref bean="quartzJob"/>

                                <property name="targetMethod">

                                                <value>execute</value>

这个

类中的

methodInvoking

方法,是不支持序列化的,因此在把

序列化进入数据库时就会抛错。网上有说把

源码拿来,修改一下这个方案,然后再打包成

SPRING.jar

发布,这些都是不好的方法,是不安全的。

必须根据

来重写一个自己的类,然后使用

把这个重写的类(我们就名命它为:

MyDetailQuartzJobBean

)注入

appContext

中后,再使用

AOP

技术反射出原有的

quartzJobx(

就是开发人员原来已经做好的用于执行

的执行类

)

下面来看

类:

public class MyDetailQuartzJobBean extends QuartzJobBean {

                protected final Log logger = LogFactory.getLog(getClass());

                private String targetObject;

                private String targetMethod;

                private ApplicationContext ctx;

                protected void executeInternal(JobExecutionContext context)

                                                throws JobExecutionException {

                                try {

                                                logger.info("execute [" + targetObject + "] at once>>>>>>");

                                                Object otargetObject = ctx.getBean(targetObject);

                                                Method m = null;

                                                try {

                                                                m = otargetObject.getClass().getMethod(targetMethod,

                                                                                                new Class[] {});

                                                                m.invoke(otargetObject, new Object[] {});

                                                } catch (SecurityException e) {

                                                                logger.error(e);

                                                } catch (NoSuchMethodException 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;

}

再来看完整的

(注意红色加粗部分尤为重要):

                                <property name="

applicationContextSchedulerContextKey

" value="

applicationContext

" />

                </bean>

                <bean id="quartzJob" class="com.testcompany.framework.quartz.QuartzJob">

<bean id="jobTask" class="org.springframework.scheduling.quartz.JobDetailBean">

                                <property name="jobClass">

                                                <value>com.testcompany.framework.quartz.

</value>

                                <property name="jobDataAsMap">

                                                <map>

                                                                <entry key="quartzJob" value="quartzJob" />

                                                                <entry key="targetMethod" value="execute" />

                                                </map>

                <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">

                                <property name="jobDetail">

                                                <ref bean="jobTask" />

                                <property name="cronExpression">

                                                <value>0/5 * * * * ?</value>

</beans>

4.      

下载最新的

quartz1.8

版,把

quartz-all-1.8.4.jar, quartz-oracle-1.8.4.jar,quartz-weblogic-1.8.4.jar

这三个包放到

web-inf/lib

目录下,布署。

测试:

几个节点都带有

任务,此时只有一台

在运行,另几个节点上的

没有运行。

此时手动

shutdown

那台运行

(在程序里加

system.out.println(“execute once…”),

运行

的那个节点在后台会打印

execute once

)的节点,过了

7

秒左右,另一个节点的

自动监测到了集群中运行着的

instance

已经

,因此

集群会自动把任一台可用的

上启动起一个

quartz job

的任务。

自此,

使用

HA

策略的集群大功告成,不用改原有代码,配置一下我们就可作到

的集群与自动错误冗余。