目录
前言
环境配置
Quartz
什么是Quartz?
应用场景
核心组件
Job
JobDetail
Trigger
CronTrigger
SimpleTrigger
Scheduler
任务存储
RAM
JDBC
导入依赖
定时任务
销量统计
Redis检测
使用
编辑
注意事项
在悦享校园1.0中引入了Quartz框架实现了对于商家每日的销量统计功能,而目前项目已升级到SpringBoot版本,因此需要进行对应的代码进行调整。除此之外考虑到若Redis出现故障时或使用该项目不想要配置Redis时如何保证该项目正常启动呢?即当Redis出现故障时如何无缝切换到数据库查询数据而不是抛出错误信息?由于项目中整合Redis的客户端为Lettuce,因此可以考虑使定时任务实现对Redis服务的监测从而无感切换数据查询操作。
JDK 1.8
Spring Boot 2.7.12
lettuce 6.1.10(包含在Spring-Boot-Starter-Data-Redis中)
Quartz 2.3.2
根据官方文档描述,Quartz 是一种功能丰富的,开放源码的作业调度库,可以在集成在任何的Java应用程序中,小到独立的应用程序,大到复杂的电子商务系统。 Quartz可以用来创建简单或复杂的日程安排执行几十,几百,甚至数以万计的作业数,作业被定义为标准的Java组件,可以通过编程使其执行。
如文章开头所述,当我们需要统计店铺的每日销量或者每周销量时,可以通过一个定时任务来执行相应的操作。以及Redis由于长时间不使用时可能会造成客户端除此之外,也可以将Quartz使用到如定期发送消息通知,如获取每日天气进行邮件推送到指定客户等其它的操作。这些都可通过Quartz来实现。除此之外Quartz还能完成其它复杂的任务。
用于存放真正需要定时执行的任务逻辑
用于对任务信息的相关描述,如任务名称、任务分组等。需要注意的是,JobDetail中含有一个Key属性,该属性将通过传入的任务名称和分组名称构建Key,若参数为空时则通过UUID来构建,从而确保该Key是唯一的。因此相同的任务名称和组名会覆盖之前的任务,这一点是需要注意的。
使用JobDetail+Job方式的设计,可以避免在并发情况下,对同一个实例的访问问题。
可以通过JobDataMap将数据存储并将数据传给Job实例。
Trigger即为触发器,用于指定将以何种方式执行定时任务,注意Trigger与JobDetail一一对应,即一个触发器只做用于一个定义任务上。以下为两种常用的触发器:
其核心在于使用Cron表达式进行任务的构建,如下是一个表示每月最后一天执行任务的Cron表达式:
# 每月的最后1天 0 0 L * * * 说明: Linux * * * * * - - - - - | | | | | | | | | +----- day of week (0 - 7) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat | | | +---------- month (1 - 12) OR jan,feb,mar,apr ... | | +--------------- day of month (1 - 31) | +-------------------- hour (0 - 23) +------------------------- minute (0 - 59)
鉴于网上关于Cron表达式的文章非常多,这里不做过多赘述。这里列举几个常用的Cron表达式:
(1)0/2 * * * * ? 表示每2秒 执行任务
(2)0 0/2 * * * ? 表示每2分钟 执行任务
(3)0 0 2 1 * ? 表示在每月的1日的凌晨2点调整任务
(4)0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业
(5)0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
(5)0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
可以通过Crontab.guru - The cron schedule expression editor 网站来验证Cron表达式。
相比于上述提到的Cron表达式,SimpleTrigger来构建一些指定间隔时间执行的任务更加容易,如每隔75s执行某个任务,则使用SimpleTrigger更佳。
任务调度器,它可以JobDetail与Trigger关联起来,通过任务调度器将启动任务的执行。一个任务调度器中可以包含多个关联的实例。
Quartz提供了两种定时任务存储功能,一种为RAM,另一种为JDBC。
默认情况下Quartz会将任务数据存储到内存中,优点为任务读取速度快,简单易用。但缺点是随着服务重启会导致任务丢失。(本文采用该方式)
Quartz提供了对定时任务持久化的功能,可以通过创建对应的数据表将定时任务持久化到数据库中,如此一来可以确保任务不会因为服务重启而丢失,可以更好的管理任务。缺点是需要额外的进行数据库表创建。本文采用默认的RAM方式存储,可以自行通过具体需求场景来选择。
由于本文项目使用了SpringBoot,因此导入如下依赖即可
org.springframework.boot
spring-boot-starter-quartz
任务创建可以通过继承 QuartzJobBean 类并重写其excuteInternal方法,或实现 Job 接口的excute方法,从QuartzJobBean的源码可知,其实现了Job接口,因此以上的创建方式任选其一即可。
销量统计任务类,此处仅使用日志打印体现,具体的业务逻辑可自行编写
@Slf4j
public class SellDailyJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
log.info("这是每日销量统计定时任务。。。");
}
}
执行规则配置类
@Configuration
public class SellDailyConfig{
@Bean("sellDailyJob")
public JobDetail jobDetail(){
// 指定任务执行的类
return JobBuilder.newJob(SellDailyJob.class)
// 任务名称和分组名,不可重复
.withIdentity("sellDailyJob", "group")
.withDescription("任务描述:内存方式运行")
.storeDurably()
.build();
}
@Bean("sellDailyTrigger")
public Trigger trigger() {
return TriggerBuilder.newTrigger()
// 触发器名和分组名,不可重复
.withIdentity("trigger", "group")
.forJob(jobDetail())
.startNow()
// 使用Cron表达式构建执行事件 每5s执行一次
.withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?"))
.build();
}
}
Redis健康检测定时任务类,构建方法同上
@Slf4j
public class RedisCheckJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
log.info("这是Redis定时心跳任务");
}
}
执行规则配置类
@Configuration
@Slf4j
public class RedisCheckConfig {
// 指定生成的Bean实例对象名称
@Bean("redisCheck")
public JobDetail jobDetail() {
return JobBuilder.newJob(RedisCheckJob.class)
// 任务名和任务分组
.withIdentity("RedisCheckJob", "group")
.withDescription("任务描述:内存方式运行")
.storeDurably()
.build();
}
@Bean("redisTrigger")
public Trigger trigger() {
return TriggerBuilder.newTrigger()
// 触发器名称和分组
.withIdentity("redisCheck", "group")
.forJob(jobDetail())
.startNow()
// 使用SimpleSchedule构建定时任务
.withSchedule(
SimpleScheduleBuilder
.simpleSchedule()
// 每隔10s执行任务
.withIntervalInSeconds(10)
// 永不过期
.repeatForever())
.build();
}
}
上个部分中分别使用CronTrigger和SimpleTrigger构建了两种定时任务,而根据SpringBoot官方文档中所示,当Quartz可用时,SchedulerFactoryBean会将Scheduler自动装配到容器中,因此在SpringBoot中使用@Configuration+@Bean注解构建定时任务后,无需显式创建Scheduler,SpringBoot会自动加载这些定时任务交由Scheduler调度。
因此配置完成后只需启动应用程序即可,执行结果如下所示。
1.以上构建定时任务时需要创建任务类并在其中写入自定义的业务方法。并在配置类newJob中写入对应任务类。jobDetail和Trigger方法使用@Bean注解构建时,需要指定名称且不重复,否则其它配置类无法正常构建Bean实例。
2.jobDetail方法和trigger方法中的withIdentity应分别指定不同任务名和任务分组,需要保证其它配置类中以上两个属性之中至少有一个属性不同,否则同样将造成定时任务无法正常执行。(eg:A配置类中的任务名为work,分组名为group,B中配置与之相同则会导致A服务无法正常运行)