quartz在集群环境下的最终解决方案
在集群环境下,大家会碰到一直困扰的问题,即多个 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在集群环境下的最终解决方案相关推荐
- 集群环境下定时调度的解决方案之Quartz集群
集群环境下定时调度的解决方案之Quartz集群 参考文章: (1)集群环境下定时调度的解决方案之Quartz集群 (2)https://www.cnblogs.com/yinfengjiujian/p ...
- Java技术分享:集群环境下的定时任务
定时任务的实现方式有多种,例如JDK自带的Timer+TimerTask方式,Spring 3.0以后的调度任务(Scheduled Task),Quartz框架等. Timer+TimerTask是 ...
- 分布式集群环境下,如何实现session共享三(环境搭建)
这是分布式集群环境下,如何实现session共享系列的第三篇.在上一篇:分布式集群环境下,如何实现session共享二(项目开发)中,准备好了一个通过原生态的servlet操作session的案例.本 ...
- 在非容器(集群)环境下运行dapr
作者:李俱顺 原文:https://www.4async.com/2021/03/2021-03-11-running-dapr-without-container/ 前一段时间一直关注的dapr正式 ...
- 集群环境下,你不得不注意的ASP.NET Core Data Protection 机制
引言 最近线上环境遇到一个问题,就是ASP.NET Core Web应用在单个容器使用正常,扩展多个容器无法访问的问题.查看容器日志,发现以下异常: System.Security.Cryptogra ...
- Hadoop集群环境下网络架构的设计与优化
2019独角兽企业重金招聘Python工程师标准>>> 大数据时代,研究大数据的IT 厂商把研究重心放在优化大数据系统软件架构.优化业务逻辑.优化数据分析算法.优化节点性能等方向,而 ...
- MEMCACHED在集群环境下对并发更新是否保持数据一致
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 今天在和 ...
- weblogic 12C集群环境下的session复制
做过weblogic集群环境的人应该都清楚,要想实现session同步,必须满足两个条件:第一,在weblogic.xml里面增加session同步相关的代码:第二,所有放入session的类都要序列 ...
- ORACLE集群日志收集,【RAC】Oracle RAC集群环境下日志文件结构
在Oracle RAC环境中,对集群中的日志的定期检查是必不可少的.通过查看集群日志,可以早期定位集群环境中出现的问题,以便将问题消灭在萌芽状态.简单介绍一下有关Oracle集群环境中日志的结构,方便 ...
最新文章
- 清华博士后用10分钟讲解AlphaCode背后的技术原理,原来程序员不是那么容易被取代的!...
- 从工业云到工业互联网平台演进的五个阶段
- .NET分层登陆——机房收费系统再总结
- HDU 2757 Ocean Currents
- java 回滚异常_Spring事务管理只对出现运行期异常进行回滚
- 古代的房价跟现在比怎么样?50万能在唐朝买个茅厕吗?
- 正则表达式的捕获性分组/反向引用
- Luogu P1164小A点菜
- 理解BERT每一层都学到了什么
- Onvif协议学习:8、设备校时
- EPS电动转向系统分析
- [《所遇随心》偶感小记]2012年8月28日
- HTML12张图片魔方,纯CSS3 实现3D魔方
- 端午节,我们失去的太多了
- 雷迪9000使用说明_标准版DM雷迪操作及维护手册 精品
- SQL 实验项目6_存储过程
- 软件测试分类(按测试阶段划分)
- 大数据在金融行业的应用有哪些
- 计算机网络第二章 物理层练习题(中文带答案解析)
- Bootstrap(五) 导航条、分页导航