一、为什么要使用Quartz
比如在电商系统中:买家下单时系统生成订单,用户有30分钟左右的时间去支付订单,如果30分钟后还没有支付,那么订单就不见了。
实现方式:系统中有个定时任务,每隔10秒钟(当然也可以设置为30秒、1分钟等)去检测大于等于30分钟之前创建的还未支付的订单,检查到该订单后就将订单删除或者将订单的状态改为失效,使用户无法看到该订单。
像这样的定时任务使用场景还有很多,比如:每天的固定时间点自动生成报表、每个月份的月底自动生成报表;卖家订单发货成功后,买家7天还没有点击确认收货,系统自动确认收货等等。
Quartz是一个完全由Java编写的开源任务调度框架,可以方便灵活的自动去执行我们编写的任务代码,实现指定的业务逻辑。Quartz使用起来非常简单,功能却非常强大,支持任务和触发器的多对多的关系,还能把多个任务与不同的触发器关联,触发器支持非常灵活的时间设置,如:在一天中的某个时间执行,一周的某几天执行,在每月的某一天执行,重复执行直到一个特定的时间/日期等等。Quartz还支持集群、自动故障转移,多机部署服务时,单机故障不会影响到系统的可服务性。
二、创建Spring Boot工程
三、创建HelloJob类
package com.ljhua.quartz1;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
public class HelloJob {
private int count = 0;
public void handle() {
int curJob = count++;
System.out.println("处理逻辑-" + Thread.currentThread().getId() + "-start:" + new Date() + " ### " + curJob);
try {
Thread.sleep(30 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("处理逻辑-" + Thread.currentThread().getId() + "---end:" + new Date() + " ### " + curJob);
}
}
四、创建HelloConfiguration类
package com.ljhua.quartz1;
import org.quartz.JobDetail;
import org.quartz.Trigger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
@Configuration
public class HelloConfiguration {
@Bean(name = "jobDetail")
public MethodInvokingJobDetailFactoryBean detailFactoryBean(HelloJob helloJob) {
MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean();
// 是否并发执行
jobDetail.setConcurrent(false);
// 设置任务的名字
jobDetail.setName("helloJob");
// 设置任务的分组,在多任务的时候使用
jobDetail.setGroup("JobGroup");
// 需要执行的对象
jobDetail.setTargetObject(helloJob);
/*
* TODO 非常重要
* 执行HelloJob类中的handle方法
*/
jobDetail.setTargetMethod("handle");
return jobDetail;
}
@Bean(name = "jobTrigger")
public CronTriggerFactoryBean cronJobTrigger(JobDetail jobDetail) {
CronTriggerFactoryBean tigger = new CronTriggerFactoryBean();
tigger.setJobDetail(jobDetail);
//cron表达式,每10秒执行一次
tigger.setCronExpression("0/10 * * * * ?");
tigger.setName("jobTrigger");
return tigger;
}
@Bean(name = "scheduler")
public SchedulerFactoryBean schedulerFactory(Trigger jobTrigger) {
SchedulerFactoryBean factoryBean = new SchedulerFactoryBean();
// 用于quartz集群,QuartzScheduler 启动时更新己存在的Job
factoryBean.setOverwriteExistingJobs(true);
// 延时启动,应用启动1秒后
factoryBean.setStartupDelay(1);
// 注册触发器
factoryBean.setTriggers(jobTrigger);
return factoryBean;
}
}
执行工程,运行结果如下:
可以看到代码中tigger.setCronExpression("0/10 * * * * ?")把调度的时间间隔设置为10秒,但实际上handle()函数上一次执行和下一次执行的时间间隔是0秒。这是因为handle()函数里执行Thread.sleep(30 * 1000),系统睡眠了30秒(假设你的业务逻辑执行一次需要30秒),导致handle()函数执行一次的耗时(30秒)大于调度间隔(10秒),所以真实的执行结果就成了handle()函数2次执行的间隔为0秒。
把jobDetail.setConcurrent(false)的参数false改为true后jobDetail.setConcurrent(true),再来看下程序的运行结果是怎样?
可以看到start ### 0,start ### 1,start ### 2,start ### 3,start ### 4,start ### 5,start ### 6都是间隔10秒,虽然函数执行一次的耗时还是30秒,调度间隔设置还是10秒,但是这次执行结果:handle()函数上一次执行和下一次执行的时间间隔是10秒。区别就在于改了一行代码jobDetail.setConcurrent(true),为什么二次执行的结果不同?因为jobDetail.setConcurrent(true)是同步模式,即无论handle()函数是否处理完业务逻辑,都会按调度时间的固定间隔去调用handle(),而jobDetail.setConcurrent(false)是非同步模式,即上一次调度的handle()函数处理完了业务逻辑才会发起下一次调度。
那么问题来了:有些业务场景对定时任务的执行时间有很强的执行时间要求,同时又不能接受多个线程同时处理同一笔业务可能会出现的故障,怎么做才合理呢?关于这个问题以后再述。