集群环境下定时调度的解决方案之Quartz集群
集群环境可能出现的问题
在上一篇博客我们介绍了如何在自己的项目中从无到有的添加了Quartz定时调度引擎,其实就是一个Quartz 和Spring的整合过程,很容易实现,但是我们现在企业中项目通常都是部署在集群环境中的,这样我们之前的定时调度就会出现问题了,因为我们的定时任务都加载在内存中的,每个集群节点中的调度器都会去执行,这就会存在重复执行和资源竞争的问题,那么如何来解决这样的问题呢,往下面看吧...
解决方案
在一般的企业中解决类似的问题一般都是在一个note上部署Quartz其他note不部署(或者是在其他几个机器加IP地址过滤),但是这样集群对于定时任务来说就没有什么意义了,而且存在着单点故障的隐患,也就是这台部署着Quartz的机器一旦挂了,我们的定时任务就停止服务了,这绝对不是我们想要的。
Quartz本身是支持集群的,我们通过Quartz的集群方式来解决这样的问题。
Quartz集群
虽然单个 Quartz 实例能给予你很好的 Job调度能力,但它不能令典型的企业需求,如可伸缩性、高可靠性满足。假如你需要故障转移的能力并能运行日益增多的 Job,Quartz 集群势必成为你方言的一部分了,并且即使是其中一台机器在最糟的时间崩溃了也能确保所有的 Job 得到执行。 QuartzJob Scheduling Framework
了解了Quartz集群的好处,接下来就对我们之前的工程进行改造,增加Quartz集群特性。
Quartz集群中节点依赖于数据库来传播 Scheduler 实例的状态,你只能在使用 JDBC JobStore 时应用 Quartz 集群
所以我们集群的第一步就是建立Quartz所需要的12张表:
1、建表
在quartz核心包里面通过quartz提供的建表语句建立相关表结构
生成的表结构如下
这几张表是用于存储任务信息,触发器,调度器,集群节点等信息
详细解释:
QRTZ_CALENDARS 以Blob 类型存储Quartz 的Calendar 信息
QRTZ_CRON_TRIGGERS 存储Cron Trigger,包括Cron 表达式和时区信息
QRTZ_FIRED_TRIGGERS 存储与已触发的Trigger 相关的状态信息,以及相联Job 的执行信息
QRTZ_PAUSED_TRIGGER_GRPS 存储已暂停的Trigger 组的信息
QRTZ_SCHEDULER_STATE 存储少量的有关Scheduler 的状态信息,和别的Scheduler 实例(假如是用于一个集群中)
QRTZ_LOCKS 存储程序的非观锁的信息(假如使用了悲观锁)
QRTZ_JOB_DETAILS 存储每一个已配置的Job 的详细信息
QRTZ_JOB_LISTENERS 存储有关已配置的JobListener 的信息
QRTZ_SIMPLE_TRIGGERS 存储简单的Trigger,包括重复次数,间隔,以及已触的次数
QRTZ_BLOG_TRIGGERS Trigger 作为Blob 类型存储(用于Quartz 用户用JDBC 创建他们自己定制的Trigger 类型,JobStore并不知道如何存储实例的时候)
QRTZ_TRIGGER_LISTENERS 存储已配置的TriggerListener 的信息
QRTZ_TRIGGERS 存储已配置的Trigger 的信息
所有的表默认以前缀QRTZ_开始。可以通过在quartz.properties配置修改(org.quartz.jobStore.tablePrefix= QRTZ_)。
2、编写quartz.properties文件
建立 quartz.properties文件把它放在工程的 src 目录下,内容如下:
#============================================================================ # Configure Main Scheduler Properties #============================================================================ org.quartz.scheduler.instanceName = Mscheduler org.quartz.scheduler.instanceId = AUTO
12
13 org.quartz.jobStore.clusterCheckinInterval=20000 #============================================================================ # Configure ThreadPool #============================================================================ org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 3 org.quartz.threadPool.threadPriority = 5 #============================================================================ # Configure JobStore #============================================================================ #org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX #org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate org.quartz.jobStore.useProperties = true #org.quartz.jobStore.dataSource = myDS org.quartz.jobStore.tablePrefix = QRTZ_ org.quartz.jobStore.isClustered = true org.quartz.jobStore.maxMisfiresToHandleAtATime=1 #============================================================================ # Configure Datasources #============================================================================ #mysql #org.quartz.dataSource.myDS.driver = com.ibm.db2.jcc.DB2Driver #org.quartz.dataSource.myDS.URL = jdbc:db2://localhost:50000/db #org.quartz.dataSource.myDS.user = db2 #org.quartz.dataSource.myDS.password = db2 #org.quartz.dataSource.myDS.maxConnections = 5 #oracle #org.quartz.dataSource.myDS.driver = oracle.jdbc.driver.OracleDriver #org.quartz.dataSource.myDS.URL = jdbc:oracle:thin:@localhost:1521:orcl #org.quartz.dataSource.myDS.user = scott #org.quartz.dataSource.myDS.password = shao #org.quartz.dataSource.myDS.maxConnections = 5 #For Tomcat org.quartz.jobStore.driverDelegateClass =org.quartz.impl.jdbcjobstore.oracle.OracleDelegate #For Weblogic & Websphere #org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.WebLogicDelegate org.quartz.jobStore.useProperties = false org.quartz.jobStore.dataSource = myDS #JNDI MODE #For Tomcat org.quartz.dataSource.myDS.jndiURL=java:comp/env/jdbc/oracle #For Weblogic & Websphere #org.quartz.dataSource.myDS.jndiURL=jdbc/oracle #============================================================================ # Configure Plugins #============================================================================ #org.quartz.plugin.triggHistory.class = org.quartz.plugins.history.LoggingJobHistoryPlugin #org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.JobInitializationPlugin #org.quartz.plugin.jobInitializer.fileNames = jobs.xml #org.quartz.plugin.jobInitializer.overWriteExistingJobs = true #org.quartz.plugin.jobInitializer.failOnFileNotFound = true #org.quartz.plugin.jobInitializer.scanInterval = 10 #org.quartz.plugin.jobInitializer.wrapInUserTransaction = false
红色加粗部分是集群需要的配置
核心配置解释如下:
org.quartz.jobStore.class 属性为JobStoreTX,
将任务持久化到数据中。因为集群中节点依赖于数据库来传播Scheduler实例的状态,你只能在使用JDBC JobStore 时应用Quartz 集群。
org.quartz.jobStore.isClustered 属性为true,通知Scheduler实例要它参与到一个集群当中。
org.quartz.jobStore.clusterCheckinInterval
属性定义了Scheduler实例检入到数据库中的频率(单位:毫秒)。
Scheduler 检查是否其他的实例到了它们应当检入的时候未检入;
这能指出一个失败的Scheduler 实例,且当前Scheduler 会以此来接管任何执行失败并可恢复的Job。
通过检入操作,Scheduler也会更新自身的状态记录。clusterChedkinInterval越小,Scheduler节点检查失败的Scheduler 实例就越频繁。默认值是 20000 (即20 秒)
3、修改spring-time.xml文件
<?xmlversion="1.0"encoding="UTF-8"?>
<!DOCTYPEbeansPUBLIC"-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans> <!-- 调度器lazy-init='false'那么容器启动就会执行调度程序 -->
<beanid="startQuertz"lazy-init="false"autowire="no"class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="dataSource" ref="dataSource"/>
9 <property name="configLocation" value="classpath:quartz.properties" />
<propertyname="triggers">
<list>
<refbean="doTime"/>
</list>
</property>
<!-- 允许在Quartz上下文中使用Spring实例工厂 -->
<propertyname="applicationContextSchedulerContextKey"value="applicationContext"/>
</bean> <!-- 触发器 -->
<beanid="doTime"class="org.springframework.scheduling.quartz.CronTriggerBean">
<propertyname="jobDetail"ref="jobtask"></property>
<!-- cron表达式 -->
<propertyname="cronExpression"value="10,15,20,25,30,35,40,45,50,55 * * * * ?"></property>
</bean> <!-- 任务 -->
<beanid="jobtask"class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<propertyname="targetObject"ref="synUsersJob"></property>
<propertyname="targetMethod"value="execute"></property>
</bean> <!-- 要调用的工作类 -->
<beanid="synUsersJob"class="org.leopard.core.quartz.job.SynUsersJob"></bean> </beans>
增加红色加粗部分代码,注入数据源和加载quartz.properties文件
OK Quartz集群的配置只有这几步,我们来启动项目。。。
我们启着启着….报错了!
17:00:59,718 ERROR ContextLoader:215 - Context initialization failed org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'startQuertz' defined in class path resource [config/spring/spring-time.xml]: Invocation of init method failed; nested exception is org.quartz.JobPersistenceException:Couldn't store job: Unable to serialize JobDataMap for insertion into database because the value of property 'methodInvoker' is not serializable: org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean [See nested exception: java.io.NotSerializableException: Unable to serialize JobDataMap for insertion into database because the value of property 'methodInvoker' is not serializable: org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean] |
我们主要来看红色部分,主要原因就是这个MethodInvokingJobDetailFactoryBean 类中的 methodInvoking 方法,是不支持序列化的,因此在把 quartz 的 task 序列化进入数据库时就会抛这个serializable的错误
4、解决serializable错误解决方案
网上查了一下,解决这个问题,目前主要有两种方案:
4.1.修改Spring的源码
博客地址:http://jira.springframework.org/browse/SPR-3797
作者重写了MethodInvokingJobDetailFactoryBean
4.2.通过AOP反射对Spring源码进行切面重构
博客地址:http://blog.csdn.net/lifetragedy/article/details/6212831
根据 QuartzJobBean 来重写一个自己的类,然后使用 SPRING 把这个重写的类(我们就名命它为: MyDetailQuartzJobBean )注入 appContext 中后,再使用 AOP 技术反射出原有的 quartzJobx( 就是开发人员原来已经做好的用于执行 QUARTZ 的 JOB 的执行类 ) 。
两种方式我都进行了测试,都可以解决问题,我们这里先通过第二种方式解决这个bug,没有修改任何Spring的源码
4.2.1、增加MyDetailQuartzJobBean.Java
package org.leopard.core.quartz; import java.lang.reflect.Method; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.quartz.QuartzJobBean; /**
* 解决Spring和Quartz整合bug
*
*/
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;
} }
5、再次修改spring-time.xml文件解决serializable问题
修改后的spring-time.xml文件内容如下:
<?xmlversion="1.0"encoding="UTF-8"?>
<!DOCTYPEbeansPUBLIC"-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans> <!-- 调度器lazy-init='false'那么容器启动就会执行调度程序 -->
<beanid="startQuertz"lazy-init="false"autowire="no"class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<propertyname="dataSource"ref="dataSource"/>
<propertyname="configLocation"value="classpath:quartz.properties"/>
<propertyname="triggers">
<list>
<refbean="doTime"/>
</list>
</property>
<!--这个是必须的,QuartzScheduler延时启动,应用启动完后 QuartzScheduler再启动-->
<propertyname="startupDelay"value="30"/>
<!--这个是可选,QuartzScheduler启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了-->
<propertyname="overwriteExistingJobs"value="true"/>
<!-- 允许在Quartz上下文中使用Spring实例工厂 -->
<propertyname="applicationContextSchedulerContextKey"value="applicationContext"/>
</bean> <!-- 触发器 -->
<beanid="doTime"class="org.springframework.scheduling.quartz.CronTriggerBean">
<propertyname="jobDetail"ref="jobtask"></property>
<!-- cron表达式 -->
<propertyname="cronExpression"value="10,15,20,25,30,35,40,45,50,55 * * * * ?"></property>
</bean> <!-- 任务 -->
<beanid="jobtask"class="org.springframework.scheduling.quartz.JobDetailBean">
<propertyname="jobClass">
33 <value>org.leopard.core.quartz.MyDetailQuartzJobBean</value>
34 </property>
<propertyname="jobDataAsMap">
<map>
<entrykey="targetObject"value="synUsersJob"/>
<entrykey="targetMethod"value="execute"/>
</map>
</property>
</bean> <!-- 要调用的工作类 -->
<beanid="synUsersJob"class="org.leopard.core.quartz.job.SynUsersJob"></bean> </beans>
主要看红色加粗部分...
测试
Ok 配置完成,我们把oa_ssh部署到两台tomcat上面,分别启动。
可以看到我们先启动的tomcat控制台打印出日志
另外一台没有打印日志
这时我们把执行定时任务的那台tomcat停止,可以看到等了一会之后,我们的另外一台tomcat会把之前tomcat执行的定时任务接管过来继续执行,我们的集群是成功的。
集群环境下定时调度的解决方案之Quartz集群的更多相关文章
- Spring+Quartz集群环境下定时调度的解决方案
集群环境可能出现的问题 在上一篇博客我们介绍了如何在自己的项目中从无到有的添加了Quartz定时调度引擎,其实就是一个Quartz 和Spring的整合过程,很容易实现,但是我们现在企业中项目通常都是 ...
- quartz在集群环境下的最终解决方案
在集群环境下,大家会碰到一直困扰的问题,即多个 APP 下如何用 quartz 协调处理自动化 JOB . 大家想象一下,现在有 A , B , C3 台机器同时作为集群服务器对外统一提供 SERVI ...
- CAS服务器集群和客户端集群环境下的单点登录和单点注销解决方案
CAS的集群环境,包括CAS的客户应用是集群环境,以及CAS服务本身是集群环境这两种情况.在集群环境下使用CAS,要解决两个问题,一是单点退出(注销)时,CAS如何将退出请求正确转发到用户sessio ...
- CAS Client集群环境的Session问题及解决方案
[原创申明:文章为原创,欢迎非盈利性转载,但转载必须注明来源] 之前写过一篇文章,介绍单点登录的基本原理.这篇文章重点介绍开源单点登录系统CAS的登录和注销的实现方法.并结合实际工作中碰到的问题,探讨 ...
- 在tomcat集群环境下redis实现分布式锁
上篇介绍了redis在集群环境下如何解决session共享的问题.今天来讲一下如何解决分布式锁的问题 什么是分布式锁? 分布式锁就是在多个服务器中,都来争夺某一资源.这时候我们肯定需要一把锁是不是 , ...
- CAS Client集群环境的Session问题及解决方案介绍,下篇介绍作者本人项目中的解决方案代码
CAS Client集群环境的Session问题及解决方案 程序猿讲故事 2016-05-20 原文 [原创申明:文章为原创,欢迎非盈利性转载,但转载必须注明来源] 之前写过一篇文章,介绍单点登 ...
- 【SpringBoot】spring-session-data-redis 解决集群环境下session共享
为什么会产生Session共享问题 集群情况下,session保存在各自的服务器的tomcat中,当分发地址至不同服务时,导致sesson取不到,就会产生session共享问题. 解决方案 负载均 ...
- 在Hadoop1.2.1分布式集群环境下安装hive0.12
在Hadoop1.2.1分布式集群环境下安装hive0.12 ● 前言: 1. 大家最好通读一遍过后,在理解的基础上再按照步骤搭建. 2. 之前写过两篇<<在VMware下安装Ubuntu ...
- Ubuntu14(64位) 集群环境下安装Hadoop2.4
经过前边的积累,今天最终实现了集群环境下部署Hadoop.并成功执行了官方的样例. 工作例如以下: 两台机器: NameNode:上网小本,3G内存.机器名:YP-X100e,IP:192.168.1 ...
随机推荐
- 回顾HashMap
一.HashMap的原理简述 HashMap是基于哈希表的非线程安全的Map实现,内部采用数组+链表实现,其内部类Node定义了数据元素类型,它扩展了Map.Entry<K,V>增加了指向 ...
- 第8章 枚举类&注解
8.枚举及注解 8.1 如何自定义枚举类 1课时 8.2 如何使用关键字enum定义枚举类 1课时 8.3 枚举类的主要方法 1课时 8.4 实现接口的枚举类 1课时 8-1 枚举类 枚举类入门 枚举 ...
- uniq的坑坑
很久没有做过文本统计之类的操作了,今天有点任务弄一下,幸亏机智的我列出了全部看了一遍,发现uniq的时候还是有重复的,然后总结了一下 假如我有1.txt这个文本: 10.0.0.1 10.0.0.1 ...
- Twitter的雪花算法(snowflake)自增ID
前言 这个问题源自于,我想找一个分布式下的ID生成器. 这个最简单的方案是,数据库自增ID.为啥不用咧?有这么几点原因,一是,会依赖于数据库的具体实现,比如,mysql有自增,oracle没有,得用序 ...
- windows 安装lua-5.3.4 --引用自https://blog.csdn.net/wangtong01/article/details/78296369
版权声明:本文为博主原创文章,转载时请标明出处.http://blog.csdn.net/wangtong01 https://blog.csdn.net/wangtong01/article/det ...
- 阿里轻量应用服务器 Tomcat 注意的地方 Unsupported major.minor version 52.0(unable to load class
本地编译工程,提交到远程服务其的tomcat上报这个错 Unsupported major.minor version 52.0(unable to load class com.cl.busines ...
- xcopy命令总结
xcopy命令总结1.拷贝多个文件和目录用xcopy /yhie或者xcopy /yhis命令,注意目标路径要以\结尾,例如:xcopy /yhie *.* e:\xxx\2.拷贝多个固定名字的文件用 ...
- C++Primer第五版——习题答案详解(十)
习题答案目录:https://www.cnblogs.com/Mered1th/p/10485695.html 第11章 关联容器 练习11.3 #include<iostream> #i ...
- Percona XtraDB Cluster Strict Mode(PXC 5.7)
在Percona XtraDB Cluster集群架构中,为了避免多主节点导致的数据异常,或者说一些不被支持的特性引发的数据不一致的情形,PXC集群可以通过配置pxc_strict_mode这个变量来 ...
- Linux和Windows启动后台程序
平时很多时候,我们需要通过脚本命令调用执行程序,集成一体后方便使用快捷.但是启动脚本窗口比较碍眼,能设置为后台运行既方便又美观. Linux启动后台程序 1.后台执行 nohup方法:不挂断的运行命令 ...