Spring整合Quartz浅析
来源:互联网 发布:java调用odata 编辑:程序博客网 时间:2024/06/06 19:09
Quartz概念基础
Quartz 是 OpenSymphony 开源组织在任务调度领域的一个开源项目,完全基于 Java 实现。
核心元素概念:
Job: 是一个接口,只有一个方法void execute(JobExecutionContext context),开发者实现该接口定义运行任务,JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中;
JobDetail: Quartz在每次执行Job时,都重新创建一个Job实例,所以它不直接接受一个Job的实例,相反它接收一个Job实现类,以便运行时通过newInstance()的反射机制实例化Job。因此需要通过一个类来描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息,JobDetail承担了这一角色。
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进行定点排除。
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使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提高运行效率。
Misfire:错过的,指本来应该被执行但实际没有被执行的任务调度
为什么要选择Quartz
- 强大的调度功能,例如支持丰富多样的调度方法,可以满足各种常规及特殊需求;
- 灵活的应用方式,例如支持任务和调度的多种组合方式,支持调度数据的多种存储方式;
- 支持分布式环境,不用担心每个节点都触发执行任务,而是只会负载均衡其中一个节点
- 容错机制,job失败(如关机、程序崩溃,程序异常不算)重启恢复
- 利用线程池,对比传统的Timer,一个任务一个线程
- 完全由Java写成,方便集成(Spring)
Spring整合Quartz
Step1、maven依赖引入
<properties> <springframework.version>4.2.0.RELEASE</springframework.version> <quartz.version>2.2.1</quartz.version> <logback.version>1.1.1</logback.version></properties><dependencies> <!-- Spring framework --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${springframework.version}</version> </dependency> <!-- Quartz framework --> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>${quartz.version}</version> </dependency> <!-- Log --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>${logback.version}</version> </dependency></dependencies>
Step2、配置调度任务Job
spring提供两种方式创建Job:
方式一:MethodInvokingJobDetailFactoryBean
这种方式最简单,targetObject指定任务Bean,targetMethod指定任务执行的方法:
<bean id="simpleJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject" ref="simpleJob" /> <property name="targetMethod" value="execute" /> <!-- 任务没处理完,下一轮又触发了,是否并发执行,默认true --> <property name="concurrent" value="false"/></bean>
如上配置,表示任务执行simpleJob中的execute方法,simpleJob内容如下:
package example;import org.springframework.stereotype.Component;import java.util.Date;@Componentpublic class SimpleJob { public void execute(){ System.out.println(new Date() + " : SimpleJob start..."); }}
注意: simpleJob的注入使用了自动扫描的方式,需要配置@Component注解和启用bean扫描:
<context:component-scan base-package="example"/>
方式二:JobDetailFactoryBean
<bean name="complexJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean"> <property name="jobClass" value="example.ComplexJob"/> <property name="jobDataAsMap"> <map> <entry key="timeout" value="5"/> </map> </property></bean>
这种方式相对复杂点,不过也很简单,jobClass指定任务类,jobDataAsMap可以为任务类属性设值,其灵活度更高。这种方式没有任务并发concurrent属性配置,可以通过@DisallowConcurrentExecution注解实现相同功能,对应example.ComplexJob内容如下
package example;import org.quartz.JobExecutionContext;import org.quartz.JobExecutionException;import org.springframework.scheduling.quartz.QuartzJobBean;import java.util.Date;@DisallowConcurrentExecutionpublic class ComplexJob extends QuartzJobBean { private int timeout; /** * Setter方法在任务初始化后被调用,它的值配置在JobDetailFactoryBean的jobDataAsMap属性中 */ public void setTimeout(int timeout) { this.timeout = timeout; } protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException { System.out.println(new Date() + " : ComplexJob start..."); }}
Job需要继承QuartzJobBean,任务调度时,executeInternal方法将被执行
Step3、配置触发器Trigger
spring也提供了两种方式配置触发器:
方式一:SimpleTriggerFactoryBean
这种方式可以指定任务运行的开始时间、延迟时间、间隔时间等
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean"> <!--触发执行任务--> <property name="jobDetail" ref="simpleJobDetail"/> <!-- 指定延迟开始,单位毫秒 --> <property name="startDelay" value="1000"/> <!-- 重复间隔,单位毫秒 --> <property name="repeatInterval" value="5000"/></bean>
方式二:CronTriggerFactoryBean
cron表达式,这种方式更加实用(关于cron表达本文就不展开了)
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <property name="jobDetail" ref="complexJobDetail"/> <!--cron表达式,如每5秒一次--> <property name="cronExpression" value="0/5 * * * * ?"/></bean>
Step4、配置调度器Scheduler
Scheduler任务调度器,是实际执行任务调度的控制器。可以包含多个Trigger(通过triggers属性配置)和多个Job(通过jobDetails属性配置)。另外提一下,Trigger和Job的关系:一个Trigger包含一个Job,但是一个Job可以被多个Trigger触发。
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="cronTrigger"/> </list> </property> <!-- 指定延迟5秒开始 --> <property name="startupDelay" value="5" /> <property name="schedulerName" value="myScheduler" /></bean>
Step5、测试
package main;import org.springframework.context.support.ClassPathXmlApplicationContext;public class Application { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-quartz.xml"); context.registerShutdownHook(); }}
控制台输出:
八月 01, 2017 11:50:50 上午 org.springframework.scheduling.quartz.SchedulerFactoryBean startScheduler信息: Will start Quartz Scheduler [myScheduler] in 5 seconds11:50:51.080 [Timer-0] DEBUG org.quartz.utils.UpdateChecker - Checking for available updated version of Quartz...11:50:55.145 [Quartz Scheduler [myScheduler]] INFO org.quartz.core.QuartzScheduler - Scheduler myScheduler_$_NON_CLUSTERED started.八月 01, 2017 11:50:55 上午 org.springframework.scheduling.quartz.SchedulerFactoryBean run信息: Starting Quartz Scheduler now, after delay of 5 seconds11:50:55.148 [myScheduler_QuartzSchedulerThread] DEBUG o.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers11:50:55.156 [myScheduler_Worker-1] DEBUG org.quartz.core.JobRunShell - Calling execute on job DEFAULT.complexJobDetail11:50:55.156 [myScheduler_QuartzSchedulerThread] DEBUG o.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggersTue Aug 01 11:50:55 CST 2017 : ComplexJob start...11:51:00.001 [myScheduler_Worker-2] DEBUG org.quartz.core.JobRunShell - Calling execute on job DEFAULT.complexJobDetail11:51:00.001 [myScheduler_QuartzSchedulerThread] DEBUG o.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggersTue Aug 01 11:51:00 CST 2017 : ComplexJob start...11:51:05.004 [myScheduler_Worker-3] DEBUG org.quartz.core.JobRunShell - Calling execute on job DEFAULT.complexJobDetail11:51:05.004 [myScheduler_QuartzSchedulerThread] DEBUG o.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggersTue Aug 01 11:51:05 CST 2017 : ComplexJob start...
Quartz集群
quartz集群架构图:
Quartz 分布式部署时,没有类似zookeeper协调系统来管理各个节点,各个节点并不感知其他节点的存在,只是通过数据库来进行间接的沟通。Quartz 集群容错和负载均衡等集群特性都是通过数据库来实现的。表结构可到官网下载,sql在docs/dbTables目录下,Quartz 数据库表功能说明:
表建好后,通过配置文件配置调度器Scheduler
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <!--注册触发器--> <property name="triggers"> <list> <ref bean="cronTrigger"/> </list> </property> <property name="configLocation" value="classpath:quartz.properties" /> <!-- 指定延迟5秒开始 --> <property name="startupDelay" value="5" /> <property name="schedulerName" value="myScheduler" /></bean>
quartz.properties清单:
#============================================================================# Configure Main Scheduler Properties #============================================================================org.quartz.scheduler.instanceName=myScheduler#集群中每个节点必须有唯一的instanceId,通过org.quartz.spi.InstanceIdGenerator生成org.quartz.scheduler.instanceId=AUTO#是否跳过检查Quartz有新版本可下载,Quartz会启动专门的线程检查更新,发现新版本会打印日志提示org.quartz.scheduler.skipUpdateCheck=true#============================================================================# Configure ThreadPool #============================================================================org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool#jobs执行可用线程,5个线程差不多可以并发100个短生命周期的任务org.quartz.threadPool.threadCount=5#线程优先级,范围1-10,默认5org.quartz.threadPool.threadPriority=5#============================================================================# Configure JobStore #============================================================================#线程池中没有可用线程造成触发超时阀值,默认60000毫秒org.quartz.jobStore.misfireThreshold=10000org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTXorg.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegateorg.quartz.jobStore.useProperties=falseorg.quartz.jobStore.dataSource=myDSorg.quartz.jobStore.tablePrefix=QRTZ_org.quartz.jobStore.isClustered=true#集群实例checks-in频率间隔,检测挂掉的实例org.quartz.jobStore.clusterCheckinInterval=10000#============================================================================# Configure DataSource#============================================================================org.quartz.dataSource.myDS.driver=com.mysql.jdbc.Driverorg.quartz.dataSource.myDS.URL=jdbc:mysql://192.168.15.32:3306/quartzorg.quartz.dataSource.myDS.user=rootorg.quartz.dataSource.myDS.password=rootorg.quartz.dataSource.myDS.maxConnections=5org.quartz.dataSource.myDS.validationQuery=select 0
配置好数据源后,设置org.quartz.jobStore.isClustered为true便能获取 Quartz 集群特性
Quartz踩过的坑
问题场景:由于业务需要,使用 Quartz 到各个业务线同步订单数据的,在测试过程中,发现有些订单数据同步正常了,而有些订单数据同步缺少了个别信息,反复检查了代码,未发现问题。
解决:查看QRTZ_SCHEDULER_STATE表,发现订单同步任务相同SCHED_NAME存在多个实例,由于公司的测试环境有多套,不同迭代需求可以在不同测试环境并行,这时候就会有问题:同一个java服务不同环境都启动了,他们有相同的SCHED_NAME,一样的配置,唯一不同的是任务的业务代码不同步,当任务触发负载到代码较旧的实例上执行时,就会造成订单数据同步缺少了个别信息的问题。
吐槽: Quartz 记录的集群实例信息就一个实例名称,通过这个名称完全不知道该实例在哪台机器上,你想停掉对应实例都不知道停哪台机器,要是能记录IP就更好了。
参考
https://docs.spring.io/spring/docs/current/spring-framework-reference/html/scheduling.html
http://www.quartz-scheduler.org
https://tech.meituan.com/mt-crm-quartz.html
http://blog.itpub.net/11627468/viewspace-1763498/
https://www.ibm.com/developerworks/cn/opensource/os-cn-quartz/index.html
https://my.oschina.net/songhongxu/blog/802574
- Spring整合Quartz浅析
- Spring-----Spring整合Quartz
- spring整合quartz
- spring整合quartz
- spring整合quartz
- Spring整合quartz配置
- Spring 整合quartz
- Spring整合quartz
- Spring整合Quartz示例
- spring整合quartz
- spring quartz框架整合
- spring跟QuartZ整合
- Spring-quartz整合
- Quartz 整合Spring 小试牛刀
- spring整合quartz
- spring整合quartz
- Spring+Quartz整合
- Spring整合Quartz
- BZOJ 2190 仪仗队 (莫比乌斯反演)
- 学习日记11
- SpringMVC第六篇【校验、统一处理异常】
- 守护进程
- day13-oracle总结
- Spring整合Quartz浅析
- Kotlin:基本语法篇
- 2017/8/11训练日记(二分)
- 简单工厂模式
- Clock||HDU1209
- 输入5个数(含负数、小数)将它们按由小到大的顺序排列起来
- 并查集!!!
- sublime常用设置
- Git暂存区