定时任务几种实现方式
Java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务,没怎么用过就不说了。
Spring3.0以后自带的task,可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多。
java的线程池类ScheduledExecutorService也可以实现一些简单的定时任务,周期性任务。
Quartz是一个功能比较强大的的调度器,可以让你的程序在指定时间执行,也可以按照某一个频度执行,可以方便的分布式部署、便捷的监控和管理任务,适合任务很多的情况。
Spring Scheduler注解方式实现
代码还是挺少的
@Component
public class SchedulerPractice{
// @Scheduled(fixedDelay=60000)
@Scheduled(cron = "0 0/1 * * * ?") public void execute() { logger.info("every one minute------"); }
}
1
2
3
4
5
配置文件
1
2
3
4
5
6
7
pool-size=”2” 有多个任务可以配置以线程池执行
注解使用方式
@Scheduled(cron = "${cron_expression}")
1
分布式多实例运行
scheduler与web配置在一起,在高可用的情况下,如果有多个web容器实例,scheduler会在多个实例上同时运行。
解决办法:
使用写死服务器Host的方式执行task,存在单点风险,负载均衡手动完成。(或者一台代码中配置任务,其他不配置任务)
在task的基类加入一些逻辑,当开始运行时,将状态(运行机器的IP、时间等)写入数据库、缓存(redis)或者zk,运行结束时重置状态。其它实例看到有这样的数据,就直接返回。带来的问题是:
一定要保证结束运行后将状态重置,否则下一个运行周期,所有的task都会返回的。
因为读写状态并非原子操作,偶尔也会发生task同时运行的事。
使用zk分布式锁,比如在任务执行方法上自定义注解,在注解中配置锁在zk的路径,在该注解上自定义个拦截器,在拦截器中获取zk锁。
Java线程池ScheduledExecutorService
示例代码如下
public void cronThread(){
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);
scheduledThreadPool.scheduleWithFixedDelay(new ThreadPractice(), 0, 3, TimeUnit.SECONDS);
}
1
2
3
4
5
6
new ThreadPractice()是一个实现了Runnable的类。
ScheduledExecutorService 类有两个方法
public ScheduledFuture> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit)
public ScheduledFuture> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit)
1
2
3
4
5
6
7
8
9
这两个方法和@scheduled 注解中的fixedDelay和fixedRate类似。
Quartz
Quartz框架是一个全功能、开源的任务调度服务,可以集成几乎任何的java应用程序—从小的单片机系统到大型的电子商务系统。可以方便的分布式部署、便捷的监控和管理任务,Quartz可以执行上千上万的任务调度。
核心概念
Quartz核心的概念:scheduler任务调度、Job任务、Trigger触发器、JobDetail任务细节
Job任务:其实Job是接口,其中只有一个execute方法, 只要实现此接口,实现execute方法即可。
JobDetail:任务细节,Quartz执行Job时,需要新建个Job实例,但是不能直接操作Job类,所以通过JobDetail来获取Job的名称、描述信息。
Trigger触发器:执行任务的规则;比如每天,每小时等。触发器有SimpleTrigger和CronTrigger,这个触发器实现了Trigger接口。对于复杂的时间表达式来说,比如每个月15日上午几点几分,使用CronTrigger,对于简单的时间来说,比如每天执行几次,使用SimpleTrigger。
scheduler任务调度:是最核心的概念,需要把JobDetail和Trigger注册到scheduler中,才可以执行。
quartz单机示例
使用的quartz的jar的版本是:2.2.1 ,低版本的核心类可能有些不同。
job类
public class MyJob implements Job {
@Override
//把要执行的操作,写在execute方法中
public void execute(JobExecutionContext arg0) throws JobExecutionException {
System.out.println(“test Quartz"+new Date());
}
}
1
2
3
4
5
6
7
8
测试代码
@Test
public void startJobTest() {
SchedulerFactory schedulerfactory = new StdSchedulerFactory();
Scheduler scheduler = null;
try {
// 通过schedulerFactory获取一个调度器
scheduler = schedulerfactory.getScheduler();
// 创建jobDetail实例,绑定Job实现类
// 指明job的名称,所在组的名称,以及绑定job类
JobDetail job = JobBuilder.newJob(ImageTableMonitorJob.class).withIdentity("job1", "jgroup1").build();
// 定义调度触发规则
// 使用simpleTrigger规则
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("simpleTrigger", "triggerGroup")
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(1).withRepeatCount(8)).startNow().build();
// 使用cornTrigger规则 每天10点42分
// Trigger trigger= TriggerBuilder.newTrigger().withIdentity("simpleTrigger", "triggerGroup")
// .withSchedule(CronScheduleBuilder.cronSchedule("0 42 10 * * ? *"))
// .startNow().build();
// 把作业和触发器注册到任务调度中
scheduler.scheduleJob(job, trigger);
// 启动调度
scheduler.start();
} catch (Exception e) {
// e.printStackTrace();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
spring提供了对quartz的整合,可以通过 org.springframework.scheduling.quartz.SchedulerFactoryBean 注入scheduler调度器,并且对调度器做些配置,比如使用线程池,并配置线程数量等。配置示例如下。
1
2
3
4
5
6
7
8
9
Quartz分布式原理
Quartz的集群部署方案在架构上是分布式的,没有负责集中管理的节点,而是利用数据库锁的方式来实现集群环境下进行并发控制。分布式部署时需要保证各个节点的系统时间一致。没弄过,就不细说了。
Quartz数据库核心表QRTZ_LOCKS中有5条记录CALENDAR_ACCESS,JOB_ACCESS,MISFIRE_ACCESS,STATE_ACCESS,TRIGGER_ACCESS 代表5把锁,分别用于实现多个Quartz Node对Job、Trigger、Calendar访问的同步控制。
cron表达式
字段 允许值 允许的特殊字符
秒 0-59 , - * /
分 0-59 , - * /
小时 0-23 , - * /
日期 1-31 , - * ? / L W C
月份 1-12 或者 JAN-DEC , - * /
星期 1-7 或者 SUN-SAT , - * ? / L C #
年(可选) 留空, 1970-2099 , - * /
- 区间
* 通配符
? 你不想设置那个字段
1
2
3
4
5
6
7
8
9
10
11
12
13
下面列出一些实例
CRON表达式 含义
"0 0 12 * * ?" 每天中午十二点触发
"0 15 10 ? * *" 每天早上10:15触发
"0 15 10 * * ?" 每天早上10:15触发
"0 15 10 * * ? *" 每天早上10:15触发
"0 15 10 * * ? 2005" 2005年的每天早上10:15触发
"0 * 14 * * ?" 每天从下午2点开始到2点59分每分钟一次触发
"0 0/5 14 * * ?" 每天从下午2点开始到2:55分结束每5分钟一次触发
"0 0/5 14,18 * * ?" 每天的下午2点至2:55和6点至6点55分两个时间段内每5分钟一次触发
"0 0-5 14 * * ?" 每天14:00至14:05每分钟一次触发
"0 10,44 14 ? 3 WED" 三月的每周三的14:10和14:44触发
"0 15 10 ? * MON-FRI" 每个周一、周二、周三、周四、周五的10:15触发
1
2
3
4
5
6
7
8
9
10
11
12
参考资料