集群环境可能出现的问题

在上一篇博客我们介绍了如何在自己的项目中从无到有的添加了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集群的更多相关文章

  1. Spring+Quartz集群环境下定时调度的解决方案

    集群环境可能出现的问题 在上一篇博客我们介绍了如何在自己的项目中从无到有的添加了Quartz定时调度引擎,其实就是一个Quartz 和Spring的整合过程,很容易实现,但是我们现在企业中项目通常都是 ...

  2. quartz在集群环境下的最终解决方案

    在集群环境下,大家会碰到一直困扰的问题,即多个 APP 下如何用 quartz 协调处理自动化 JOB . 大家想象一下,现在有 A , B , C3 台机器同时作为集群服务器对外统一提供 SERVI ...

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

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

  4. CAS Client集群环境的Session问题及解决方案

    [原创申明:文章为原创,欢迎非盈利性转载,但转载必须注明来源] 之前写过一篇文章,介绍单点登录的基本原理.这篇文章重点介绍开源单点登录系统CAS的登录和注销的实现方法.并结合实际工作中碰到的问题,探讨 ...

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

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

  6. CAS Client集群环境的Session问题及解决方案介绍,下篇介绍作者本人项目中的解决方案代码

    CAS Client集群环境的Session问题及解决方案  程序猿讲故事  2016-05-20  原文 [原创申明:文章为原创,欢迎非盈利性转载,但转载必须注明来源] 之前写过一篇文章,介绍单点登 ...

  7. 【SpringBoot】spring-session-data-redis 解决集群环境下session共享

    为什么会产生Session共享问题   集群情况下,session保存在各自的服务器的tomcat中,当分发地址至不同服务时,导致sesson取不到,就会产生session共享问题. 解决方案 负载均 ...

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

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

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

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

随机推荐

  1. 第十四章 Java常用类

    14.常用类 14.1 字符串相关的类 1课时 14.2 JDK 8之前时间日期API 1课时 14.3 JDK8中新时间日期API 1课时 14.4 JDK8中的Optional类 1课时 14.5 ...

  2. 第十二章 NIO

    12.NIO 12.1 Java NIO 概述 1课时 12.2 Java NIO.2 之Path.Paths 与 Files 的使用 1课时 12.3 自动资源管理 1课时 12.4 缓冲区(Buf ...

  3. MongoDB基础知识(二)

    一.基本概念 1:文档(document)是MongoDB中数据的基本单元,非常类似于关系型数据库管理系统中的行 2:集合(collection)可以看做是一个拥有动态模式(dynamic schem ...

  4. 1.2.3 Excel中姓名处理,将名加密星号

    在对应的单元格中我们输入公式: =IF(LEN(A22)>2,REPLACE(A22,2,LEN(A22)-1,"**"),REPLACE(A22,2,LEN(A22)-1, ...

  5. MVC Ajax.BeginForm 提交上传图片

    吃水不忘挖井人,如果对你有帮助,请说声谢谢.如果你要转载,请注明出处.谢谢! 异步提交时,出现图片不能上传. 起初我定格在  System.Web.Mvc  中.查询源码时,也是没有问题的.那问题出现 ...

  6. 【fork/join】java并发编程-fork/join示例

    package com.chinamobile.epic.tako.common.graphite.query.sync.impl; import com.google.common.collect. ...

  7. 前端-JavaScript1-2——JavaScript建立认知

    关于首篇的“ Hello world ! ”这事儿吧,挺有意思,就是学习任何的语言,我们都喜欢在屏幕上直接输出一点什么,当做最简单.最基本的案例.输出什么大家随意,但是很多人都习惯输出“hello w ...

  8. QQ第三方登录(预备节)

    第一次很多时候,用户因为复杂的注册程序,而不愿意再体验我们的网站服务,我们可以通过QQ第三方授权,这样既保证了用户数,而且也极大的提高了用户体验.废话就不多说了,直接进入正题... 要实现QQ第三方登 ...

  9. 在VMware中为Redhat HAT配置本地yum源

    在VMware中为Redhat HAT配置本地yum源 今天准备使用CM安装大数据环境,到需要几台机器都使用同一套yum源才可以,所以想到将Redhat镜像文件拷贝到虚拟机中,在挂起使用,最后通过ht ...

  10. RestExpress response中addHeader 导致stackOverflow

    问题描述: 最近在项目使用中要在restExpress的header中增加一个键值对,同事在使用的时候没有对header的value进行非空判断,于是在测试环境测试的时候就出现了一个异常