Quartz集群配置
公众企业应用几乎都会碰到任务调度的需求,就拿论坛来说:每隔半个小时生成精华文章的RSS文件,每天凌晨统计论坛用户的积分排名,每隔30分钟执行锁定用户解锁任务。任务调度本身涉及到多线程并发、运行时间规则制定和解析、场景保持与恢复、线程池维护等诸多方面的工作。如果直接使用自定义线程这种刀耕火种的原始办法,开发任务调度程序是一项颇具挑战性的工作。Java开源的好处就是:领域问题都能找到现成的解决方案。
OpenSymphony所提供的Quartz自2001年发布版本以来已经被众多项目作为任务调度的解决方案,Quartz在提供巨大灵活性的同时并未牺牲其简单性,它所提供的强大功能使你可以应付绝大多数的调度需求。Quartz 在开源任务调度框架中的翘首,它提供了强大任务调度机制,难能可贵的是它同时保持了使用的简单性。Quartz 允许开发人员灵活地定义触发器的调度时间表,并可以对触发器和任务进行关联映射。此外,Quartz提供了调度运行环境的持久化机制,可以保存并恢复调度现场,即使系统因故障关闭,任务调度现场数据并不会丢失。此外,Quartz还提供了组件式的侦听器、各种插件、线程池等功能。
Quartz最新版本是2.1.3,Quartz是通过借助关系数据库和JDBC作业存储来实现集群管理的,集群通过故障切换和负载平衡的功能,能给调度器带来高可用性和伸缩性。
目前Quartz集群只能工作在JDBC-Jobstore (JobStoreTX 或者JobStoreCMT)方式下,从本质上来说,是使集群上的每一个节点通过共享同一个数据库来工作的(Quartz通过启动两个维护线程来维护数据库状态实现集群管理,一个是检测节点状态线程,一个是恢复任务线程)。
负载平衡是自动完成的,集群的每个节点会尽快触发任务。当一个触发器的触发时间到达时,第一个节点将会获得任务(通过锁定),成为执行任务的节点。
故障切换的发生是在当一个节点正在执行一个或者多个任务失败的时候。当一个节点失败了,其他的节点会检测到并且标识在失败节点上正在进行的数据库中的任务。
任何被标记为可恢复(任务详细信息的"requests recovery"属性)的任务都会被其他的节点重新执行;没有标记可恢复的任务只会被释放出来,将会在下次相关触发器触发时执行。
集群通过故障切换和负载平衡的功能,能给调度器带来高可用性和伸缩性。目前集群只能工作在JDBC- Jobstore (JobStoreTX或者JobStoreCMT)方式下,从本质上来说,是使集群上的每一个节点通过共享同一个数据库来工作的(Quartz通过启动两个维护线程来维护数据库状态实现集群管理,一个是检测节点状态线程,一个是恢复任务线程)。负载平衡是自动完成的,集群的每个节点会尽快触发任务。当一个触发器的触发时间到达时,第一个节点将会获得任务(通过锁定),成为执行任务的节点。故障切换的发生是在当一个节点正在执行一个或者多个任务失败的时候。当一个节点失败了,其他的节点会检测到并且标识在失败节点上正在进行的数据库中的任务。任何被标记为可恢复(任务详细信息的"requests recovery"属性)的任务都会被其他的节点重新执行。没有标记可恢复的任务只会被释放出来,将会在下次相关触发器触发时执行。
Quartz对任务调度的领域问题进行了高度的抽象,提出了调度器、任务和触发器这3个核心的概念,并在org.quartz通过接口和类对重要的这些核心概念进行描述:
●Job:是一个接口,只有一个方法void execute(JobExecutionContext context),开发者实现该接口定义运行任务,JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中。
●JobDetail:Quartz在每次执行Job时,都重新创建一个Job实例,所以它不直接接受一个Job的实例,相反它接收一个Job实现类,以便运行时通过newInstance()的反射机制实例化Job。因此需要通过一个类来描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息,JobDetail承担了这一角色。
通过该类的构造函数可以更具体地了解它的功用:JobDetail(java.lang.String name, java.lang.String group, java.lang.Class jobClass),该构造函数要求指定Job的实现类,以及任务在Scheduler中的组名和Job名称。
●Trigger:是一个类,描述触发Job执行的时间触发规则。主要有SimpleTrigger和CronTrigger这两个子类。当仅需触发一次或者以固定时间间隔周期执行,SimpleTrigger是最适合的选择;而CronTrigger则可以通过Cron表达式定义出各种复杂时间规则的调度方案:如每早晨9:00执行,周一、周三、周五下午5:00执行等。
●Calendar:org.quartz.Calendar和java.util.Calendar不同,它是一些日历特定时间点的集合(可以简单地将org.quartz.Calendar看作java.util.Calendar的集合——java.util.Calendar代表一个日历时间点,无特殊说明后面的Calendar即指org.quartz.Calendar)。一个Trigger可以和多个Calendar关联,以便排除或包含某些时间点。假设,我们安排每周星期一早上10:00执行任务,但是如果碰到法定的节日,任务则不执行,这时就需要在Trigger触发机制的基础上使用Calendar进行定点排除。针对不同时间段类型,Quartz在org.quartz.impl.calendar包下提供了若干个Calendar的实现类,如AnnualCalendar、MonthlyCalendar、WeeklyCalendar分别针对每年、每月和每周进行定义。
●Scheduler:代表一个Quartz的独立运行容器,Trigger和JobDetail可以注册到Scheduler中,两者在Scheduler中拥有各自的组及名称,组及名称是Scheduler查找定位容器中某一对象的依据,Trigger的组及名称必须唯一,JobDetail的组和名称也必须唯一(但可以和Trigger的组和名称相同,因为它们是不同类型的)。Scheduler定义了多个接口方法,允许外部通过组及名称访问和控制容器中Trigger和JobDetail。
Scheduler可以将Trigger绑定到某一JobDetail中,这样当Trigger触发时,对应的Job就被执行。一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job。可以通过SchedulerFactory创建一个Scheduler实例。Scheduler拥有一个SchedulerContext,它类似于ServletContext,保存着Scheduler上下文信息,Job和Trigger都可以访问SchedulerContext内的信息。SchedulerContext内部通过一个Map,以键值对的方式维护这些上下文数据,SchedulerContext为保存和获取数据提供了多个put()和getXxx()的方法。可以通过Scheduler# getContext()获取对应的SchedulerContext实例。
●ThreadPool:Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提高运行效率。
我们使用quartz-2.1.3.zip,因为Quartz集群依赖于数据库,所以必须首先创建Quartz数据库表,Quartz包括了所有被支持的数据库平台的SQL脚本。
在 <quartz_home>/docs/dbTables 目录下找到那些SQL脚本(这里的 <quartz_home> 是解压Quartz分发包后的目录),这里采用的Quartz 2.1.3版本,总共11张表,不同版本,表个数可能不同。数据库为oracle,用tables_oracle.sql创建数据库表。
全部表如下所示。
表名 |
描述 |
QRTZ_BLOG_TRIGGERS |
Trigger作为Blob类型存储(用于Quartz用户用JDBC创建他们自己定制的Trigger类型,JobStore 并不知道如何存储实例的时候) |
QRTZ_CALENDARS |
以Blob类型存储Quartz的Calendar信息 |
QRTZ_CRON_TRIGGERS |
存储Cron Trigger,包括Cron表达式和时区信息 |
QRTZ_FIRED_TRIGGERS |
存储与已触发的Trigger相关的状态信息,以及相联Job的执行信息 |
QRTZ_JOB_DETAILS |
存储每一个已配置的Job的详细信息 |
QRTZ_LOCKS |
存储程序的非观锁的信息(假如使用了悲观锁) |
QRTZ_PAUSED_TRIGGER_GRPS |
存储已暂停的Trigger组的信息 |
QRTZ_SCHEDULER_STATE |
存储少量的有关 Scheduler的状态信息,和别的 Scheduler 实例(假如是用于一个集群中) |
QRTZ_JOB_LISTENERS |
存储有关已配置的 JobListener的信息 |
QRTZ_SIMPLE_TRIGGERS |
存储简单的 Trigger,包括重复次数,间隔,以及已触的次数 |
QRTZ_SIMPROP_TRIGGERS |
|
QRTZ_TRIGGERS |
存储已配置的 Trigger的信息 |
说明:tables_oracle.sql里有相应的dml初始化
说明:保存job详细信息,该表需要用户根据实际情况初始化
job_name: 集群中job的名字,该名字用户自己可以随意定制,无强行要求。
job_group: 集群中job的所属组的名字,该名字用户自己随意定制,无强行要求。
job_class_name:集群中job实现类的完全包名,quartz就是根据这个路径到classpath找到该job类的。
is_durable:是否持久化,把该属性设置为1,quartz会把job持久化到数据库中
job_data:一个blob字段,存放持久化job对象。
存储与已触发的 Trigger 相关的状态信息,以及相联 Job 的执行信息。
说明:集群中note实例信息,quartz定时读取该表的信息判断集群中每个实例的当前状态。
instance_name: 配置文件中org.quartz.scheduler.instanceId配置的名字,就会写 入该字段,如果设置为AUTO,quartz会根据物理机名和当前时间产生一个名字。
last_checkin_time: 上次检查时间
checkin_interval: 检查间隔时间
trigger_name: trigger的名字,该名字用户自己可以随意定制,无强行要求
trigger_group: trigger所属组的名字,该名字用户自己随意定制,无强行要求
job_name: qrtz_job_details表job_name的外键
job_group: qrtz_job_details表job_group的外键
trigger_state: 当前trigger状态设置为ACQUIRED,如果设为WAITING,则job不会触发
trigger_cron: 触发器类型,使用cron表达式
trigger_name: qrtz_triggers表trigger_name的外键
trigger_group: qrtz_triggers表trigger_group的外键
cron_expression:cron表达式
默认文件名称quartz.properties,通过设置"org.quartz.jobStore.isClustered"属性为"true"来激活集群特性。在集群中的每一个实例都必须有一个唯一的"instance id" ("org.quartz.scheduler.instanceId" 属性), 但是应该有相同的"scheduler instance name" ("org.quartz.scheduler.instanceName"),也就是说集群中的每一个实例都必须使用相同的quartz.properties 配置文件。
除了以下几种例外,配置文件的内容其他都必须相同:
a.不同的线程池大小。
b.不同的"org.quartz.scheduler.instanceId"属性值(这个可以很容易做到,设定为"AUTO"即可)。
注意:
1、永远不要在不同的机器上运行集群,除非他们的时钟是使用某种形式的同步服务(守护)非常有规律的运行(时钟必须在一分一秒内)来达到同步。
2、永远不要触发一个非集群的实例,如果其他的实例正在同一个数据库表上运行。你将使你的数据严重腐蚀,出现非预期行为。
实现一个job
package quartz.test; import java.util.Date; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; //如果有想反复执行的动作,作业,任务就把相关的代码写在execute这个方法里,前提:实现Job这个接口 //至于SimpleJob这个类什么时候实例化,execute这个方法何时被调用,我们不用关注,交给Quartz public class SimpleJob implements Job { private static Log _log = LogFactory.getLog(SimpleJob.class); public SimpleJob() { } public void execute(JobExecutionContext context) throws JobExecutionException { //这个作业只是简单的打印出作业名字和此作业运行的时间 String jobName = context.getJobDetail().getFullName(); _log.info("SimpleJob says: " + jobName + " executing at " + new Date()); System.out.println("-Calling the job ---------shang- " + new Date()); } } |
定义调度器对上面的job进行调度
package quartz.test;
import java.util.Date; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.SchedulerMetaData; import org.quartz.SimpleTrigger; import org.quartz.TriggerUtils; import org.quartz.impl.StdSchedulerFactory; import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.Log; /** * 这个例子展示Quartz的 简单触发器 所具备的基本调度能力 */ public class SimpleTriggerExample {
public void run() throws Exception { Log log = LogFactory.getLog(SimpleTriggerExample.class); log.info("------- 初始化 --------"); // 首先我们得获得一个调度器 SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); log.info("------- 初始化结束 --------"); log.info("------- 调度一个作业,简单的说就是给一个任务定一个时间启动 ----------------"); // 所有的作业都可以在sched.start()被调用前调度 // 获取下个15秒的开始,如当前是10秒,则在15秒执行,如实16秒,则在30秒执行 // 如果不喜欢这个方法,自己定义一个Date也可以 long ts = TriggerUtils.getNextGivenSecondDate(null, 15).getTime(); // job1将在ts这个时间触发执行,我们的作业叫job1,属于group1组 JobDetail job = new JobDetail("job1", "group1", SimpleJob.class); SimpleTrigger trigger = new SimpleTrigger("trigger1", "group1", new Date(ts));//此处ts可以自己设置一个Date trigger.setRepeatCount(100); trigger.setRepeatInterval(5*1000L); // 将作业加入调度队列,返回作业执行时间 Date ft = sched.scheduleJob(job, trigger);
sched.start();//调度器启动作业,相当于神舟7号点火了,具体什么时候飞天,还得看火着的速度 log.info(job.getFullName() + " 将在: " + ft + " 执行 并且重复执行: " + trigger.getRepeatCount() + " 次, 每次执行间隔 " + trigger.getRepeatInterval() / 1000 + " 秒");
log.info("------- 等待1分钟... ------------"); try { // wait five minutes to show jobs Thread.sleep(1000*60); // executing... } catch (Exception e) { } log.info("------- 关闭调度器 --------"); sched.shutdown(true); log.info("------- 关闭结束---------"); } public static void main(String[] args) throws Exception { SimpleTriggerExample example = new SimpleTriggerExample(); example.run(); } }
|
配置文件最好自己指定一个,否则会使用quartz.jar包里面的默认quartz.properties文件
#===================================================================== # Configure Main Scheduler Properties 主要属性配置 #===================================================================== org.quartz.scheduler.instanceName = Scheduler org.quartz.scheduler.instanceId = AUTO org.quartz.scheduler.rmi.export = false org.quartz.scheduler.rmi.proxy = false
#===================================================================== # Configure ThreadPool 线程池配置 #===================================================================== org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 3 org.quartz.threadPool.threadPriority = 5
#===================================================================== # jobStore 任务存储配置 #===================================================================== #org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore #org.quartz.jobStore.misfireThreshold = MISFIRE_THRESHOLD #or org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.MSSQLDelegate org.quartz.jobStore.useProperties = false org.quartz.jobStore.dataSource = myDS org.quartz.jobStore.tablePrefix = qrtz_ org.quartz.jobStore.isClustered = false
#===================================================================== # Date Source 数据源的配置 #===================================================================== org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3306/quartz_tables?useUnicode=true&characterEncoding=utf8 org.quartz.dataSource.myDS.user = root org.quartz.dataSource.myDS.password = 123456 org.quartz.dataSource.myDS.maxConnections = 5 |
为 Quartz 配置集群环境的步骤比设置类似的JAVAEE集群环境容易的多:
配置每个节点的 quartz.properties 文件。
配置 JDBC JobStore。
使用Scheduler信息(Job和Trigger) 装载数据库。
启动每个 Quartz 节点。
5. 通过设置"org.quartz.jobStore.isClustered"属性为"true"来激活集群特性。在集群中的每一个实例都必须有一个唯一的"instance id" ("org.quartz.scheduler.instanceId" 属性), 但是应该有相同的"scheduler instance name" ("org.quartz.scheduler. instanceName"),也就是说集群中的每一个实例都必须使用相同的 quartz.properties配置文件。除了以下几种例外,配置文件的内容其他都必须相同:不同的线程池大小,不同的"org.quartz.scheduler.instanceId"属性值(这个可以很容易做到,设定为"AUTO"即可)。
注意: 永远不要在不同的机器上运行集群,除非他们的时钟是使用某种形式的同步服务(守护)非常有规律的运行(时钟必须在一分一秒内)来达到同步。还有: 永远不要触发一个非集群的实例,如果其他的实例正在同一个数据库表上运行。你将使你的数据严重腐蚀,出现非预期行为。
环境:
Windows xp 主机:
Vmware 7虚拟的两台RHEL5:
Server A:192.168.255.130
Server B:192.168.255.131(防火墙已关闭)
Mysql数据库 安装在windows 主机上
Server A:
quartz.properties文件配置信息如下:
#=====================================================================# Configure Main Scheduler Properties #===================================================================== org.quartz.scheduler.instanceName = myScheduler org.quartz.scheduler.instanceId = AUTO #===================================================================== # 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.misfireThreshold = 60000 org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.MSSQLDelegate org.quartz.jobStore.dataSource = myDS org.quartz.jobStore.tablePrefix = qrtz_ #是否集群 org.quartz.jobStore.isClustered = true org.quartz.jobStore.clusterCheckinInterval = 200 #===================================================================== # Non-Managed Configure Date Source #===================================================================== org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver org.quartz.dataSource.myDS.URL = jdbc:mysql://10.3.43.73:3306/quartz_cluster?useUnicode=true&characterEncoding=utf8 org.quartz.dataSource.myDS.user = root org.quartz.dataSource.myDS.password = 123456 org.quartz.dataSource.myDS.maxConnections = 5
|
Java代码:
A: package cluster;
package cluster; import java.util.Date; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; public class SimpleJob implements Job {
public SimpleJob() { } public void execute(JobExecutionContext context) throws JobExecutionException { //String jobName = context.getJobDetail().getFullName(); System.out.println("A Calling the job ---------shang- " + new Date()); } } |
B: package cluster
package cluster;
import java.util.Date; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.SimpleTrigger; import org.quartz.TriggerUtils; import org.quartz.impl.StdSchedulerFactory;
public class SimpleTriggerExample { public void run() throws Exception { Log log = LogFactory.getLog(SimpleTriggerExample.class); log.info("------- initial--------"); SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); log.info("------- initial end--------"); log.info("------- scheduler a job----------"); long ts = TriggerUtils.getNextGivenSecondDate(null, 15).getTime(); JobDetail job = new JobDetail("job1", "group1", SimpleJob.class); SimpleTrigger trigger = new SimpleTrigger("trigger1", "group1", new Date(ts)); trigger.setRepeatCount(100); trigger.setRepeatInterval(5*1000L); Date ft = sched.scheduleJob(job, trigger); //sched.start(); log.info(job.getFullName() + " time: " + ft + "repeatcount: " + trigger.getRepeatCount() + "repeatInterval" + trigger.getRepeatInterval() / 1000 + " s"); log.info("------- wait 1 minite----------"); try { // wait five minutes to show jobs Thread.sleep(1000*60); // executing... } catch (Exception e) { } log.info("------- close scheduler---------------------"); sched.shutdown(true); log.info("------- end----------------"); } public static void main(String[] args) throws Exception { SimpleTriggerExample example = new SimpleTriggerExample(); example.run(); } } |
C: package cluster
package cluster;
import org.quartz.*; import org.quartz.impl.StdSchedulerFactory;
public class JobreScheduler { public static void main(String args[]){ try{ SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sche = sf.getScheduler(); sche.start(); System.out.println("C scheduler start"); }catch (Exception e){ e.printStackTrace(); } } } |
Server B:
quartz.properties文件配置信息如下:
#=====================================================================# Configure Main Scheduler Properties #===================================================================== org.quartz.scheduler.instanceName = myScheduler org.quartz.scheduler.instanceId = AUTO #===================================================================== # 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.misfireThreshold = 60000 org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.MSSQLDelegate org.quartz.jobStore.dataSource = myDS org.quartz.jobStore.tablePrefix = qrtz_
#是否集群 org.quartz.jobStore.isClustered = true org.quartz.jobStore.clusterCheckinInterval = 200 #===================================================================== # Non-Managed Configure Date Source #===================================================================== org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver org.quartz.dataSource.myDS.URL = jdbc:mysql://10.3.43.73:3306/quartz_cluster?useUnicode=true&characterEncoding=utf8 org.quartz.dataSource.myDS.user = root org.quartz.dataSource.myDS.password = 123456 org.quartz.dataSource.myDS.maxConnections = 5
|
Java代码:
A:
package cluster; import java.util.Date; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; public class SimpleJob implements Job {
public SimpleJob() { } public void execute(JobExecutionContext context) throws JobExecutionException { //String jobName = context.getJobDetail().getFullName(); System.out.println("B Calling the job ---------shang- " + new Date()); } }
|
B:
package cluster;
import java.util.Date; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.SimpleTrigger; import org.quartz.TriggerUtils; import org.quartz.impl.StdSchedulerFactory;
public class SimpleTriggerExample { public void run() throws Exception { Log log = LogFactory.getLog(SimpleTriggerExample.class); log.info("------- initial--------"); SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); log.info("------- initial end--------"); log.info("------- scheduler a job----------"); long ts = TriggerUtils.getNextGivenSecondDate(null, 15).getTime(); JobDetail job = new JobDetail("job1", "group1", SimpleJob.class); SimpleTrigger trigger = new SimpleTrigger("trigger1", "group1", new Date(ts)); trigger.setRepeatCount(100); trigger.setRepeatInterval(5*1000L); Date ft = sched.scheduleJob(job, trigger); //sched.start(); log.info(job.getFullName() + " time: " + ft + "repeatcount: " + trigger.getRepeatCount() + "repeatInterval" + trigger.getRepeatInterval() / 1000 + " s"); log.info("------- wait 1 minite----------"); try { // wait five minutes to show jobs Thread.sleep(1000*60); // executing... } catch (Exception e) { } log.info("------- close scheduler---------------------"); sched.shutdown(true); log.info("------- end----------------"); } public static void main(String[] args) throws Exception { SimpleTriggerExample example = new SimpleTriggerExample(); example.run(); } } |
C:
package cluster;
import org.quartz.*; import org.quartz.impl.StdSchedulerFactory;
public class JobreScheduler { public static void main(String args[]){ try{ SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sche = sf.getScheduler(); sche.start(); System.out.println("B scheduler start"); }catch (Exception e){ e.printStackTrace(); } } } |
Server A与Server B中的配置和代码完全一样
运行方法:
运行任意主机上的程序代码B,将任务加入调度;
运行两台主机上的代码C
观察运行结果
集群是否配置成功的评判标准:
1、启动两台服务器后只有一台在运行调度程序,另外一台等待。
2、查看数据库中的表项,可以看到两个集群实例。如下图所示。
中断任意服务器,另一台服务器会启动任务调度,继续运行任务。
1、Quartz代码库(使用SVN客户端打开):http://svn.terracotta.org/svn/quartz
2、Quartz主页:http://www.quartz-scheduler.org
3、本文档中的代码下载地址:http://download.csdn.net/detail/thinkhejie/4195973
<bean id="schedulerFactoryBean"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="dataSource">
<ref bean="dataSource_blog"/>
</property><property
name="applicationContextSchedulerContextKey" value="applicationContext"/>
<property name="configLocation" value="classpath:quartz.properties"/>
<!--这个是必须的,QuartzScheduler 延时启动,应用启动完后 QuartzScheduler 再启动 -->
<property name="startupDelay" value="60"/>
<!--这个是可选,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了 -->
<property name="overwriteExistingJobs" value="true"/>
<property name="triggers">
<!-- 多个Trigger在此配置 -->
<list>
<ref bean="pushTimerTrigger" />
<ref bean="topTimerTrigger" />
<ref bean="scoreTimerTrigger" />
<ref bean="billingTimerTrigger" />
<ref bean="volumeIssueTimerTrigger" />
<ref bean="favoriteUserTimerTrigger" />
<ref bean="processBalanceTimerTrigger" />
<ref bean="subsUserTimerTrigger" />
</list>
</property>
</bean>
注意:
1.dataSource:项目中用到的数据源,里面包含了quartz用到的12张数据库表;这 里使用jboss jndi连接数据库做quartz集群,并将dataSource_blog注入SchedulerFactoryBean。
2. applicationContextSchedulerContextKey:
org.springframework.scheduling.quartz.SchedulerFactoryBean这个类中把spring上下 文以key/value的方式存放在了quartz的上下文中了,可以用applicationContextSchedulerContextKey所 定义的key得到对应的spring上下文;
3. configLocation:用于指明quartz的配置文件的位置
#==============================================================
#Configure Main Scheduler Properties
#==============================================================
org.quartz.scheduler.instanceName = TestScheduler1
org.quartz.scheduler.instanceId = AUTO
#==============================================================
#Configure ThreadPool
#==============================================================
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 2
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
#==============================================================
#Configure JobStore
#==============================================================
org.quartz.jobStore.misfireThreshold = 60000
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.maxMisfiresToHandleAtATime=10
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 20000
注意:
org.quartz.scheduler.instanceName属性可为任何值,用在 JDBC JobStore 中来唯一标识实例,但是所有集群节点中必须相同。
org.quartz.scheduler.instanceId 属性为 AUTO即可,基于主机名和时间戳来产生实例 ID。
org.quartz.jobStore.class属性为 JobStoreTX,将任务持久化到数据中。因为集群中节点依赖于数据库来传播 Scheduler 实例的状态,你只能在使用 JDBC JobStore 时应用 Quartz 集群。这意味着你必须使用 JobStoreTX 或是 JobStoreCMT 作为 Job 存储;你不能在集群中使用 RAMJobStore。
org.quartz.jobStore.isClustered 属性为 true,你就告诉了 Scheduler 实例要它参与到一个集群当中。这一属性会贯穿于调度框架的始终,用于修改集群环境中操作的默认行为。
org.quartz.jobStore.clusterCheckinInterval 属性定义了Scheduler 实例检入到数据库中的频率(单位:毫秒)。Scheduler 检查是否其他的实例到了它们应当检入的时候未检入;这能指出一个失败的 Scheduler 实例,且当前 Scheduler 会以此来接管任何执行失败并可恢复的 Job。通过检入操作,Scheduler 也会更新自身的状态记录。clusterChedkinInterval 越小,Scheduler 节点检查失败的 Scheduler 实例就越频繁。默认值是 15000 (即15 秒)。
<bean id="billingTimerJob" class="frameworkx.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="billingTimer" />
<property name="targetMethod" value="createPreSeasontimer" />
<property name="shouldRecover" value="true"/>
</bean>
<!-- cronExpression的介绍: 按顺序 <value> 秒 分 小时 日期 月份 星期 年<value> 字段 允许值 允许的特殊字符:秒 0-59 , - * / 分 0-59 , - * / 小时 0-23 , - * / 日期 1-31 , - * ? / L W C,月份 1-12 或者 JAN-DEC , - * / 星期 1-7 或者 SUN-SAT , - * ? / L C # 年(可选)
留空,1970-2099 , - * / “*”字符被用来指定所有的值。如:”*“在分钟的字段域里表示“每分钟”。参考《http://www.cnblogs.com/xiaopeng84/archive/2009/11/26/1611427.html》-->
<beanid="billingTimerTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail" ref="billingTimerJob" />
<property name="cronExpression" value="0 0 1 1 1,4,7,10 ?" />
</bean>
注:关于Job配置,这里有两点需要注意
MethodInvokingJobDetailFactoryBean
在这里使用牛人修改后的 frameworkx.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean,可以参考: http://jira.springframework.org/browse/SPR-3797。
直接使用org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean会报java.io.NotSerializableException异常。
2. shouldRecover
shouldRecover属性必须设置为 true,当Quartz服务被中止后,再次启动或集群中其他机器接手任务时会尝试恢复执行之前未完成的所有任务。
因为Job需要持久化到数据库中,BillingTimer必须实现Serializable接口,另外在BillingTimer引入一个公共的service(SpringBeanService)应用范型并通过ApplicationContext从spring下文获取serviceBean实例来实现调用BillingTimerService,
从而解决BillingTimerService调用BillingTimerDao进而调用SimpleJdbcTemplate时,SimpleJdbcTemplate不通被序列化的问题。
BillingTimer.java源代码清单如下:
@Component
public class BillingTimer implements Serializable {
private static final long serialVersionUID = -8961129525628230999L;
@Autowired private SpringBeanService springBeanService;
private static final Log log = Log.getInstance(BillingTimer.class);
public void createPreSeasontimer() {
int[] num = null;
try {
num = springBeanService.getBean(BillingTimerService.class,
"billingTimerService").createseasonBill();
if (null != num) {
log.info("ok");
} else {
log.error("failed");
}
} catch (Exception e) {
log.error(e.getMessage());
e.printStackTrace();
}
}
}
SpringBeanService源代码清单如下:
@Service
public class SpringBeanService implements Serializable{
private static final long serialVersionUID = -2228376078979553838L;
public <T> T getBean(Class<T> clazz,String beanName){
ApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
return (T)context.getBean(beanName);
}
}