JobDataMap 不能被序列化如何解决研究中

JobDataMap被用来保存一系列的(序列化的)对象,这些对象在Job执行时能够获得。JobDataMap是Java Map接口的一个实现,并且还增长了一些存储和读取主类型数据的便捷方法。
若是使用一个持久的JobStore,那么必须注意存放在JobDataMap中的内容。由于放入JobDataMap中的内容将被序列化,并且 容易出现类型转换问题。很明显,标准Java类型将是很是安全的,但除此以外的类型,任什么时候候,只要有人改变了你要序列化其实例的类的定义,就要注意是否 打破了程序的兼容性。另外,你能够对JobStore和JobDataMap采用一种使用模式:就是只把主类型和String类型存放在Map中,这样就 能够减小后面序列化的问题。 java


Triggers也能够有JobDataMaps与之相关联。当scheduler中的Job被多个有规律或者重复触发的Triggers所使用时很是有用。对于每次独立的触发,你可为Job提供不一样的输入数据。
从Job执行时的JobExecutionContext中取得JobDataMap是惯用手段,它融合了从JobDetail和从Trigger中获的JobDataMap,当有相同名字的键时,它用后者的值覆盖前者值。web

 

静态处理方式:spring

在集群环境下,你们会碰到一直困扰的问题,即多个 APP 下如何用 quartz 协调处理自动化 JOB 。sql

你们想象一下,如今有 A , B , C3 台机器同时做为集群服务器对外统一提供 SERVICE 数据库

A , B , C 3 台机器上各有一个 QUARTZ ,他们会按照即定的 SCHEDULE 自动执行各自的任务。安全

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

那多线程里就会存在“资源竞争”的问题,便可能产生脏读,脏写,因为三台 APP SERVER 里都有 QUARTZ ,所以会存在重复处理 TASK 的现象。多线程

通常外面的解决方案是只在一台 APP 上装 QUARTZ ,其它两台不装,这样集群就形同虚设了;架构

另外一种解决方案是动代码,这样就要影响到原来已经写好的 QUARTZ JOB 的代码了,这对程序开发人员来讲比较痛苦;oracle

本人仔细看了一下 Spring 的结构和 QUARTZ 的文档,结合 Quartz 自身能够实例化进数据的特性找到了相关的解决方案。

本方案优势:

  1. 每台做为集群点的 APP SERVER 上均可以布署 QUARTZ ;
  2. QUARTZ 的 TASK ( 12 张表)实例化如数据库,基于数据库引擎及 High-Available 的策略(集群的一种策略)自动协调每一个节点的 QUARTZ ,当任一一节点的 QUARTZ 非正常关闭或出错时,另几个节点的 QUARTZ 会自动启动;
  3. 无需开发人员更改原已经实现的 QUARTZ ,使用 SPRING+ 类反射的机制对原有程序做切面重构;

本人也事先搜索了一些资料,发觉全部目前在 GOOGLE 上或者在各大论坛里提供的解决方案,要么是只解决了一部分,要么是错误的,要么是版本太老,要么就是彻底抄别人的。

尤为是在使用 QUARTZ+SPRING 对数据库对象做实例化时会抛错(源于 SPRING 的一个 BUG ),目前网上的解决方案所有是错的或者干脆没说,本人在此方案中也会提出如何解决。

解决方案:

 

  1. 把 QUARTZ 的 TASK 实例化进数据库, QUARTZ 只有实例化进入数据库后才能作集群,外面的解决方案说实例化在内存里所有是错的,把quartz-1.8.4/docs/dbTables/tables_oracle.sql 在 ORACLE9I2 及以上版本中执行一下会生成 12 张表;
  2. 生成 quartz.properties 文件,把它放在工程的 src 目录下,使其可以被编译时归入 class path 。

通常咱们的开发人员都喜欢使用 SPRING+QUARTZ ,所以这个 quartz.properties 都不用怎么去写,可是在集群方案中 quartz.properties 必写,若是不写 quartz 会调用自身 jar 包中的 quartz.properties 做为默认属性文件,同时修改 quartz.xml 文件。

 

Quartz.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="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>

 

quartz.properties 文件的内容:

 

  • 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 

  1. 重写 quartz 的 QuartzJobBean 类

缘由是在使用 quartz+spring 把 quartz 的 task 实例化进入数据库时,会产生: serializable 的错误,缘由在于:

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

                                <property name="targetObject">

                                                <ref bean="quartzJob"/>

                                </property>

                                <property name="targetMethod">

                                                <value>execute</value>

                                </property>

</bean>

这个 MethodInvokingJobDetailFactoryBean 类中的 methodInvoking 方法,是不支持序列化的,所以在把 QUARTZ 的 TASK 序列化进入数据库时就会抛错。网上有说把 SPRING 源码拿来,修改一下这个方案,而后再打包成 SPRING.jar 发布,这些都是很差的方法,是不安全的。

必须根据 QuartzJobBean 来重写一个本身的类,而后使用 SPRING 把这个重写的类(咱们就名命它为: MyDetailQuartzJobBean )注入 appContext 中后,再使用 AOP 技术反射出原有的 quartzJobx( 就是开发人员原来已经作好的用于执行 QUARTZ 的 JOB 的执行类 ) 。

下面来看 MyDetailQuartzJobBean 类:

 

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) {

                                                                logger.error(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;

                }

 

}

再来看完整的 quartz.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="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>

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

                               

                </bean>

               

 

 

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

                </bean>

 

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

                                <property name="jobClass">

                                                <value>com.testcompany.framework.quartz. MyDetailQuartzJobBean </value>

                                </property>

                                <property name="jobDataAsMap">

                                                <map>

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

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

                                                </map>

                                </property>

                </bean>

 

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

                                <property name="jobDetail">

                                                <ref bean="jobTask" />

                                </property>

                                <property name="cronExpression">

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

                                </property>

                </bean>

</beans>

  1. 下载最新的 quartz1.8 版,把 quartz-all-1.8.4.jar, quartz-oracle-1.8.4.jar,quartz-weblogic-1.8.4.jar 这三个包放到 web-inf/lib 目录下,布署。

 

 

测试:

 

几个节点都带有 quartz 任务,此时只有一台 quartz 在运行,另几个节点上的 quartz 没有运行。

 

此时手动 shutdown 那台运行 QUARTZ (在程序里加 system.out.println(“execute once…”), 运行 quartz 的那个节点在后台会打印 execute once )的节点,过了 7 秒左右,另外一个节点的 quartz 自动监测到了集群中运行着的 quartz 的 instance 已经 shutdown ,所以 quartz 集群会自动把任一台可用的 APP 上启动起一个 quartz job 的任务。

 

自此, QUARTZ 使用 HA 策略的集群大功告成,不用改原有代码,配置一下咱们就可做到 QUARTZ 的集群与自动错误冗余。

相关文章
相关标签/搜索