Quartz
这个是在自己公司做的小分享,代码没有贴进来,可以看下思路和设计理念!
一、简介
1.概要
Quartz是一个功能丰富的开源作业调度库,可以集成到几乎任何Java应用程序中 - 从最小的独立应用程序到最大的电子商务系统。Quartz可用于创建执行数十,数百甚至数十万个作业的简单或复杂的计划; 任务定义为标准Java组件的任务,可以执行任何可以对其进行编程的任何操作。Quartz Scheduler包括许多企业级功能,例如支持JTA事务和集群。
Quartz是OpenSymphony开源任务调度框架,纯java
2.特点
简单易用,调度功能强大
支持丰富多样的调度方法,可以满足各种常规及特殊需求
灵活性
作为 Spring 默认的调度框架成,支持任务和调度的多种组合方式,支持调度数据的多种存储方式;
- 分布式和集群能力
伸缩性,高可用性,负载均衡
二、核心概念
设计模式
build模式 + factory模式 + 组建模式
流程概要图
重要组成
Scheduler 任务调度器
Job:任务,即被调度的任务
Trigger:触发器,用于定义任务调度时间规则
Misfire:错过的,指本来应该被执行但实际没有被执行的任务调度
Listen: 监听
详述
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实例;Job:
生命周期:
每次调度器执行job时,它在调用execute方法前会创建一个新的job实例,调用完成后,关联的job对象实例会被释放,然后被垃圾回收。是一个接口,只有一个方法void execute(JobExecutionContext context),开发者实现该接口定义运行任务,JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中.
JobDetail:
调度器需要借助JobDetail对象来添加job
Quartz在每次执行Job时,都重新创建一个Job实例,所以它不直接接受一个Job的实例,相反它接收一个Job实现类,以便运行时通过newInstance()的反射机制实例化Job。因此需要通过一个类来描述Job的实现类及其它相关的静态信息,如Job-name名字、group组、jobclass、jobDataMap 、描述、关联监听器等信息,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进行定点排除。
三、项目实践
1.小试牛刀
- 通过测试实例认知quartz
- 单体测试–定时任务
由简及繁–简单套路一下code演示
Cron表达式详解
简述
Cron表达式:就是用简单的xxoo符号按照一定的规则,就能把各种时间维度表达的淋漓尽致,无所不在其中,然后用来做任务调度.
Cron表达式的符号
字段 允许值 允许的特殊字符 秒 0-59 , - * / 分 0-59 , - * / 时 0-23 , - * / 月内时间 1-31 , - * ? / L W C 月 1-12 或者 JAN-DEC , - * / 周内日期 1-7 或者 SUN-SAT , - * ? / L C # 年(可选) 留空, 1970-2099 , - * / 字符意义
特殊字符|含义 -------|------
|匹配所有的值。如:在分钟的字段域里表示 每分钟
?|只在日期域和星期域中使用。它被用来指定“非明确的值”
-|指定一个范围。如:“10-12”在小时域意味着“10点、11点、12点”
,|指定几个可选值。如:“MON,WED,FRI”在星期域里表示“星期一、星期三、星期五”
/|指定增量。如:“0/15”在秒域意思是没分钟的0,15,30和45秒。“5/15”在分钟域表示没小时的5,20,35和50。符号“”在“/”前面(如:/10)等价于0在“/”前面(如:0/10)
L|表示day-of-month和day-of-week域,但在两个字段中的意思不同,例如day-of-month域中表示一个月的最后一天。如果在day-of-week域表示‘7’或者‘SAT’,如果在day-of-week域中前面加上数字,它表示一个月的最后几天,例如‘6L’就表示一个月的最后一个星期五
W|只允许日期域出现。这个字符用于指定日期的最近工作日。例如:如果你在日期域中写 “15W”,表示:这个月15号最近的工作日。所以,如果15号是周六,则任务会在14号触发。如果15号是周日,则任务会在周一也就是16号触发。如果是在日期域填写“1W”即使1号是周六,那么任务也只会在下周一,也就是3号触发,“W”字符指定的最近工作日是不能够跨月份的。字符“W”只能配合一个单独的数值使用,不能够是一个数字段,如:1-15W是错误的
LW|L和W可以在日期域中联合使用,LW表示这个月最后一周的工作日
只允许在星期域中出现。这个字符用于指定本月的某某天。例如:“6#3”表示本月第三周的星期五(6表示星期五,3表示第三周)。“2#1”表示本月第一周的星期一。“4#5”表示第五周的星期三
C|允许在日期域和星期域出现。这个字符依靠一个指定的“日历”。也就是说这个表达式的值依赖于相关的“日历”的计算结果,如果没有“日历”关联,则等价于所有包含的“日历”。如:日期域是“5C”表示关联“日历”中第一天,或者这个月开始的第一天的后5天。星期域是“1C”表示关联“日历”中第一天,或者星期的第一天的后1天,也就是周日的后一天(周一)
2.整合Spring
流程
quartz.properties
org.quartz.scheduler.instanceName: DefaultQuartzScheduler
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPoolorg.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
org.quartz.jobStore.misfireThreshold: 60000org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
再套路一下code演示
三、集群
Quartz集群中的每个节点是一个独立的Quartz应用,它又管理着其他的节点。该集群需要分别对每个节点
分别启动或停止,不像应用服务器的集群,独立的Quartz节点并不与另一个节点或是管理节点通信。
Quartz应用是通过数据库表来感知到另一应用。只有使用持久的JobStore才能完成Quqrtz集群。
未来应该会实现类似于rabbitmq的集群信息同步机制。---我猜的哈!
1.配置properties
2.配置QRTZ_数据源 (mysql)
3.实现方式的变更
不变更:
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]
原因:
Quartz集群只支持JDBCJobStore存储方式,而MethodInvokingJobDetailFactoryBean不能序列化存储job数据到数据库.
4.任务管理
更新可以设置
因为这个任务是保存到数据库中了, 如果我们想取消某个定时任务怎么办呢?
目前没找到删除的办法,只能从数据库删除.
四、总结
IBM-Quartz:企业级任务调度应用
https://www.ibm.com/developerworks/cn/opensource/os-cn-quartz/
Elastic-Job:基于成熟的开源产品Quartz和Zookeeper及其客户端Curator进行二次开发
http://blog.csdn.net/liaomengge/article/details/51340908
作业注册中心|作业分片|弹性扩容缩容容错处理|运维界面,可以管理作业和注册中心
附件
cron表达式,结合quartz来描述。首先cron表达式是有7个域的,依次分别是:秒分时日月周年,其中年是可选类型,也就是说他如果在不设定年分的情况下是每年。
*1、星号()*
他在每个域都可以存在,包含所有合法的值,看好这里是包含所有,意思就是说在那一个域上表示这个域上的所有值,加入在月,表示1-12,也就是说是每月,都会被执行。
示例1:0**1*? note:每月1号凌晨都会被执行。
示例2:0**?** note:每分钟的00秒被执行。
2、问号(?)
他在cron表达式中,必定存在,有且只有一个,且他只能在日和周的域中存在,表示,不关系这个域是什么值,看好了,跟*的包含所有值不同,他是不关系是月的哪一天或是周的哪一天。如果在周域定义具体的数值,日域必须为问号(?),如果在日域定义啦具体的数值,那么周域就必须为问号(?)。为什么,因为避免含糊不清的表达方式,比如3月的第20天,刚好是第三周的第5天,那么如果在日域定义20,周域定义5这样,就双重定义,很蛋疼。cron表达式也不允许。
示例1:0 10 18 ? 3 WEB note:每年3月的每个星期三,下午6点10分都会被触发
示例2:0 10 18 15 3 ? note:每年三月的第15天,下午6点10分都会被触发
3、横杠(-)
他在cron表达式中,可以存在任何域,如果存在某个域,表示起止时间,他表示一个时间段。
实例1:0 10 18 1-5 * ? note:每月的1号到5号(包含每月1号和5号,每月共计5天都会被触发),下午6点10分都会被触发
实例2:0 10-15 * ? * * note:每小时的第10分钟到第15分钟(包含每小时的第10分钟和第15分钟,每小时共计5分钟都会被触发),都会被触发
4、逗号(,)
他在cron表达式中,可以存在任何域,如果存在某个域,表示可选值,他是个多点的概念。
实例1:10,20 * * ? * * note:每分钟的第10秒与第20秒都会被触发
实例2:0 10,20 * 1,2 * ? note:每月的第1天与第2天的,每小时的第10分钟与第20分钟被触发。
5、斜划线(/)
他在cron表达式中,可以存在任何域,表示增量,是在域中设定一个起始的值,然后每隔多少时间的概念。
实例1:5/20 * * ? * * note:每分钟的第5秒,第25秒,第45秒 都会被执行。
实例2:0 * 2/2 ? * * note:每天的第2小时,第4小时,第6小时,第8小时 … 第22小时的00分00秒都会被触发。
6、井号(#)
他在cron表达式中,只能存在周这一个域,表示第几周的星期几,如果超出范围,则忽略不记。
实例1:* * * ? * 3#4 note:每月的第4个星期的周2,凌晨触发。
实例2:* * * ? * 6#2 note:每月的第2个星期的周5,凌晨触发