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

大家想象一下,现在有 A , B , C3 台机器同时作为集群服务器对外统一提供 SERVICE

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

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

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

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

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

本人仔细看了一下 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 

3. 重写 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>

4. 下载最新的 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 的集群与自动错误冗余。

quartz在集群环境下的最终解决方案的更多相关文章

  1. Ubuntu14(64位) 集群环境下安装Hadoop2.4

    经过前边的积累,今天最终实现了集群环境下部署Hadoop.并成功执行了官方的样例. 工作例如以下: 两台机器: NameNode:上网小本,3G内存.机器名:YP-X100e,IP:192.168.1 ...

  2. CAS服务器集群和客户端集群环境下的单点登录和单点注销解决方案

    CAS的集群环境,包括CAS的客户应用是集群环境,以及CAS服务本身是集群环境这两种情况.在集群环境下使用CAS,要解决两个问题,一是单点退出(注销)时,CAS如何将退出请求正确转发到用户sessio ...

  3. 在Hadoop1.2.1分布式集群环境下安装hive0.12

    在Hadoop1.2.1分布式集群环境下安装hive0.12 ● 前言: 1. 大家最好通读一遍过后,在理解的基础上再按照步骤搭建. 2. 之前写过两篇<<在VMware下安装Ubuntu ...

  4. 在tomcat集群环境下redis实现分布式锁

    上篇介绍了redis在集群环境下如何解决session共享的问题.今天来讲一下如何解决分布式锁的问题 什么是分布式锁? 分布式锁就是在多个服务器中,都来争夺某一资源.这时候我们肯定需要一把锁是不是 , ...

  5. weblogic 12C集群环境下的session复制

    做过weblogic集群环境的人应该都清楚,要想实现session同步,必须满足两个条件:第一,在weblogic.xml里面增加session同步相关的代码:第二,所有放入session的类都要序列 ...

  6. Oracle RAC 集群环境下日志文件结构

    Oracle RAC 集群环境下日志文件结构 在Oracle RAC环境中,对集群中的日志的定期检查是必不可少的.通过查看集群日志,可以早期定位集群环境中出现的问题,以便将问题消灭在萌芽状态.简单介绍 ...

  7. 分布式集群环境下,如何实现session共享一(应用场景)

    在web应用中,由于http的请求响应式,无状态.要记录用户相关的状态信息,比如电商网站的购物车,比如用户是否登录等,都需要使用session.我们知道session是由servlet容器创建和管理, ...

  8. 分布式集群环境下,如何实现session共享五(spring-session+redis 实现session共享)

    这是分布式集群环境下,如何实现session共享系列的第五篇.在上一篇:分布式集群环境下,如何实现session共享四(部署项目测试)中,针对nginx不同的负载均衡策略:轮询.ip_hash方式,测 ...

  9. 分布式集群环境下,如何实现session共享四(部署项目测试)

    这是分布式集群环境下,如何实现session共享系列的第四篇.在上一篇:分布式集群环境下,如何实现session共享三(环境搭建)中,已经准备好了相关的环境:tomcat.nginx.redis.本篇 ...

随机推荐

  1. C++入门学习——模板

    为什么须要模板? 我们已经学过重载(Overloading),对重载函数而言,C++ 通过函数參数(參数个数.參数类型)的正确匹配来调用重载函数.比如.为求两个数的最大值,我们定义 max () 函数 ...

  2. ZOJ 3684 Destroy 树的中心

    中心节点就是树的中心,2遍dfs求到树的直径.而中心一定在直径上,顺着直径找到中心就够了. 然后能够一遍树形DP找到最小值或者二分+推断是否訪问到叶子节点. #include <iostream ...

  3. PHP在浏览器上跟踪调试的方法以及使用ChromePhp、FirePHP的简介

    之前用ThinkPHP时发现有个 trace 函数能够跟踪调试,感觉非常有意思.网上搜索了下类似的东西.发现了 ChromePhp ,曾经没想过这样来调试 PHP 程序.感觉非常方便,非常实用. Th ...

  4. python开发【第1篇】【基础知识】

    1.python解释执行原理 python代码——字节码——机器码——计算机 每次运行都要进行转换成字节码,然后再有虚拟机把字节码转换成机器语言,最后才能在硬件上运行. 2.python编码 unic ...

  5. GuiLite 1.2 发布(希望通过这100+行代码来揭示:GuiLite的初始化,界面元素Layout,及消息映射的过程)

    经过开发群的长期验证,我们发现:即使代码只有5千多行,也不意味着能够轻松弄懂代码意图.痛定思痛,我们发现:虽然每个函数都很简单(平均长度约为30行),可以逐个击破:但各个函数之间如何协作,却很难说明清 ...

  6. hbase 增删改查 api 简单操作

    package com.utils; import java.io.IOException; import java.util.ArrayList; import java.util.List; im ...

  7. oracle-扫盲贴:存储过程实现增删改查

    原文引入:http://blog.csdn.net/yangzhawen/article/details/8617179 oracle-扫盲贴:存储过程实现增删改查 分类: oracle2013-02 ...

  8. java异常——重新抛出异常

    有时候希望把刚捕获的异常重新抛出,尤其是在使用Exception捕获所有异常的时候.既然已经得到了对当前异常对象的引用,可以直接把它重新抛出: catch(Exception e){ System.o ...

  9. SQLALchemy之介绍,基本使用

    一.介绍 SQLALchemy也是一个python的ORM框架,django内部的ORM框架只适用于django,而SQLALchemy适用于所有python的web框架 SQLAlchemy是一个基 ...

  10. hdu 2209 翻纸牌游戏【贪心】

    本来是冲着搜索去的--其实可以贪心 因为能改变第一位的只有第一位和第二位,然后改完之后后面的同理,也就是说只要贪心的推一遍就可以 但是注意要在翻第一个和不翻第一个之间取个min #include< ...