任务调度概述
各种企业应用几乎都有任务调度的需求,如论坛,每隔半个小时生成精华文章的RSS文件,每天凌晨统计论坛用户的积分排名等。
以时间为核心的调度,即在特定的时间点执行指定的操作;以资源为核心的调度,系统接收到请求时会立即创建一个新的线程服务于该请求,但资源是有限的,所以必须对资源进行控制。
Quartz认识
Quartz是开源任务调度框架中的翘楚,它完全由java写成,并设计用于J2Se和J2EE应用中。它提供了巨大的灵活性而不牺牲简单性。它允许开发人员灵活地定义触发器的调度时间表,并可对触发器和任务进行关联映射。此外,它提供了调度运行环境的持久机制,可保存并恢复调度现场,即使系统因故障关闭,任务调度现场数据并不会丢失,同时还提供了组件式的侦听器、各种插件、线程池等功能。
Quartz基础结构
Quartz对任务调度的领域问题进行了高度抽象,提出调度器、任务和触发器三个核心概念。
Scheduler:一个Quartz的独立运行容器,Trigger和JobDetail可注册到Scheduler中。一个Job可对应多个Trigger,一个Trigger只能对应一个Job。
Job:一个接口,只有execute(JobExecutionContext context)方法,开发者实现该接口定义需要执行的任务。
JobDetail:Quartz在每次执行一个Job实现类时,都会重新创建一个Job实例。它接收的是一个Job实现类,非一个Job实例,以便运行时通过newInstance()的反射调用的机制实例化Job。
Trigger:一个类,描述触发Job执行的时间触发规则。只要有SimpleTrigger子类(当仅需触发一次或者以固定间隔周期性执行)和CroneTrigger子类(可通过Cron表达式定义出各种复杂的调度方案)。
ThreadPool:Scheduler使用一个线程池作为任务运行的基础设施,任务可通过恭喜那个线程池中的线程提高运行效率。
持久化任务调度相关
Job的子接口StatefulJob,代表有状态的任务,该接口是一个没有方法的标签接口,仅让Quartz知道任务的类型。
无状态任务在执行时拥有自己的JobDataMap复制,对JobDataMap的更改不会影响下次的执行,可并发执行。
有状态的任务共享同一个JobDataMap实例,每次任务指令后都会对后JobDataMap所做的更改保存下来,后面的执行可看到此更改,对后面的执行产生影响。前次任务为执行完,会对下一次任务产生阻塞等待。
Quartz使用数据库持久化任务调度信息,无状态的JobDataMap仅会在Scheduler注册任务时保存一次,有状态任务对应的JobDataMap在每次执行任务后都会进行保存。
注意:Trigger自身的JobDataMap,对有无状态的任务都不进行持久化,不会对下次任务的执行产生影响。
与Spring的集成
<!-- 配置Quartz任务管理器-->
<!-- 工作的bean -->
<bean id="tradeJob" class="cn.com.agree.aweb.scheduler.AfaTradeScheduler">
<property name="dbOperation" ref="hibernateDao"></property>
</bean>
<!-- job的配置开始 -->
<bean id="tradeJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject">
<ref bean="tradeJob" />
</property>
<property name="targetMethod">
<value>tradeTask</value>
</property>
</bean>
<!-- job的配置结束 -->
<!-- 调度的配置开始 -->
<bean id="tradeJobCronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail">
<ref bean="tradeJobDetail" />
</property>
<property name="cronExpression">
<value>0/5 * * * * ?</value>
</property>
</bean>
<!-- 调度的配置结束 -->
<!-- 启动触发器的配置开始 -->
<bean name="quertzScheduler" lazy-init="false" autowire="no" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="tradeJobCronTrigger" />
</list>
</property>
</bean>
<!-- 启动触发器的配置结束 -->
异常处理
Caused by: java.lang.IncompatibleClassChangeError: class org.springframework.scheduling.quartz.CronTriggerBean has interface org.quartz.CronTrigger as super class
原因是Spring 3.0版本中内置的Quartz版本是<2.0的,在使用最新的Quartz包(>2.0)之后,接口不兼容。
解决办法有三种:
1.降低Quartz版本,降到1.X去。
2.升级Spring版本到3.1+,根据Spring的建议,将原来的**TriggerBean替换成**TriggerFactoryBean,例如CronTriggerBean 就可以替换成 CronTriggerFactoryBean。替换之后问题解决。
3.如果不在xml配置文件中引用,Spring 3.0 是支持 Quartz2.2.1(目前最新版本),直接在程序中调用即可。
集群原理
集群通过故障切换和负载平衡的功能,能给调度器带来高可用性和伸缩性。目前集群只能工作在JDBC-JobStore(JobStore TX或者JobStoreCMT)方式下,从本质上来说,是使集群上的每一个节点通过共享同一个数据库来工作的(Quartz通过启动两个维护线程来维护数据库状态实现集群管理,一个是检测节点状态线程,一个是恢复任务线程)。
负载平衡是自动完成的,集群的每个节点会尽快触发任务。当一个触发器的触发时间到达时,第一个节点将会获得任务(通过锁定),成为执行任务的节点。
故障切换的发生是在当一个节点正在执行一个或者多个任务失败的时候。当一个节点失败了,其他的节点会检测到并且标 识在失败节点上正在进行的数据库中的任务。任何被标记为可恢复(任务详细信息的”requests recovery”属性)的任务都会被其他的节点重新执行。没有标记可恢复的任务只会被释放出来,将会在下次相关触发器触发时执行。