在集群环境下,大家会碰到一直困扰的问题,即多个 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" "www.springframework.org/dtd/spring-… ">

<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" "www.springframework.org/dtd/spring-… ">

<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. 集群环境下定时调度的解决方案之Quartz集群

    集群环境下定时调度的解决方案之Quartz集群 参考文章: (1)集群环境下定时调度的解决方案之Quartz集群 (2)https://www.cnblogs.com/yinfengjiujian/p ...

  2. Java技术分享:集群环境下的定时任务

    定时任务的实现方式有多种,例如JDK自带的Timer+TimerTask方式,Spring 3.0以后的调度任务(Scheduled Task),Quartz框架等. Timer+TimerTask是 ...

  3. 分布式集群环境下,如何实现session共享三(环境搭建)

    这是分布式集群环境下,如何实现session共享系列的第三篇.在上一篇:分布式集群环境下,如何实现session共享二(项目开发)中,准备好了一个通过原生态的servlet操作session的案例.本 ...

  4. 在非容器(集群)环境下运行dapr

    作者:李俱顺 原文:https://www.4async.com/2021/03/2021-03-11-running-dapr-without-container/ 前一段时间一直关注的dapr正式 ...

  5. 集群环境下,你不得不注意的ASP.NET Core Data Protection 机制

    引言 最近线上环境遇到一个问题,就是ASP.NET Core Web应用在单个容器使用正常,扩展多个容器无法访问的问题.查看容器日志,发现以下异常: System.Security.Cryptogra ...

  6. Hadoop集群环境下网络架构的设计与优化

    2019独角兽企业重金招聘Python工程师标准>>> 大数据时代,研究大数据的IT 厂商把研究重心放在优化大数据系统软件架构.优化业务逻辑.优化数据分析算法.优化节点性能等方向,而 ...

  7. MEMCACHED在集群环境下对并发更新是否保持数据一致

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 今天在和 ...

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

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

  9. ORACLE集群日志收集,【RAC】Oracle RAC集群环境下日志文件结构

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

最新文章

  1. 清华博士后用10分钟讲解AlphaCode背后的技术原理,原来程序员不是那么容易被取代的!...
  2. 从工业云到工业互联网平台演进的五个阶段
  3. .NET分层登陆——机房收费系统再总结
  4. HDU 2757 Ocean Currents
  5. java 回滚异常_Spring事务管理只对出现运行期异常进行回滚
  6. 古代的房价跟现在比怎么样?50万能在唐朝买个茅厕吗?
  7. 正则表达式的捕获性分组/反向引用
  8. Luogu P1164小A点菜
  9. 理解BERT每一层都学到了什么
  10. Onvif协议学习:8、设备校时
  11. EPS电动转向系统分析
  12. [《所遇随心》偶感小记]2012年8月28日
  13. HTML12张图片魔方,纯CSS3 实现3D魔方
  14. 端午节,我们失去的太多了
  15. 雷迪9000使用说明_标准版DM雷迪操作及维护手册 精品
  16. SQL 实验项目6_存储过程
  17. 软件测试分类(按测试阶段划分)
  18. 大数据在金融行业的应用有哪些
  19. 计算机网络第二章 物理层练习题(中文带答案解析)
  20. Bootstrap(五) 导航条、分页导航

热门文章

  1. lucene3.0范围查找TermRangeQuery
  2. 如何实现链接只能被点击一次
  3. CentOS 6虚拟机安装
  4. PHP“Cannot use object of type stdClass as array”
  5. Linux 性能监控常用命令
  6. DataGridView 密码列(显示为*号)的设置
  7. typedef的四个用途和两大陷阱
  8. 海思3559A上编译GDB源码操作步骤及简单使用
  9. Ubuntu下CodeBlocks的安装、配置及静态库动态库的简单使用举例
  10. 【EMC】EMC屏蔽设计