第二十二章 : Spring Boot 集成定时任务(一)
前言
本章知识点: 介绍使用Spring Boot内置的@Scheduled注解来实现定时任务-单线程和多线程;以及介绍Quartz定时任务调度框架:简单定时调度器(SimpleSchedule
)和Cron表达式调度器(CronSchedule
)来调度触发的定时任务。
Springboot 版本 2.3.2.RELEASE ,RabbitMQ 3.9.11,Erlang 24.2
场景
- 系统维护:操作系统和各种软件通常需要定期进行更新和打补丁。通过定时任务,这些更新可以在预定时间自动进行,无需人工干预。
- 文件同步:在多个设备或服务器之间同步文件时,可以使用定时任务来确保文件的一致性。例如,使用cron在Linux系统上定期同步文件夹。
- 发送通知或警报:许多系统需要定期发送通知或警报,如系统状态报告,警报阈值越过通知等。这些可以通过定时任务来实现。
- 数据抽取和加载:在大数据环境中,通常需要从不同的数据源抽取数据,并将其加载到数据仓库或分析系统中。定时任务可以定期执行这些操作。
- 测试和监控:定期进行系统或应用的健康检查,性能测试等,以便及时发现问题并进行修复。
- 日志清理:定期清理过期的日志文件,以防止磁盘空间被耗尽。
- 网关定期同步:对于需要和远程系统进行同步的网关服务,定时任务可以帮助实现数据的定期更新。
- 定期重新启动服务:某些服务可能需要定期重新启动以保持良好的性能。定时任务可以执行这个操作。
- 定期更新统计信息:在各种系统中,统计信息的及时更新对于决策制定和性能优化都非常重要。定时任务可以帮助实现这个需求。
Spring Boot定时任务的方式
Spring Boot提供了两种实现定时任务的方式:
1) 一种是Spring Boot内置的注解方式,只需在类上增加@Scheduled即可实现;
2)另一种是基于Quartz实现,Quartz是目前完善的定时任务解决方案,适合处理复杂的应用场景。
@Scheduled定时任务
参数说明
value
:指定计划任务的时间间隔。可以是固定时间间隔的字符串表示,例如"0 0/5 * * * ?"表示每5分钟执行一次。也可以是cron表达式,例如"0 0 12 * * ?"表示每天中午12点执行。fixedRate
:固定速率,表示任务以固定的时间间隔执行。如果设置了该参数,那么方法会在每隔一定时间间隔后执行一次。与value
参数类似,参数值可以是固定的时间间隔字符串或cron表达式。fixedDelay
:固定延迟,表示任务在完成一次执行后,等待一定的延迟时间再执行下一次。与value
参数类似,参数值可以是固定的时间间隔字符串或cron表达式。initialDelay
:初始延迟,表示任务在启动后需要等待一定的延迟时间才开始执行。参数值可以是固定的时间间隔字符串或cron表达式。cron
:cron表达式,用于指定任务的执行时间。可以精确到秒级别。例如,"0 0 12 * * ?"表示每天中午12点执行。timezone
:时区,用于指定任务的执行时间。该参数通常与cron表达式一起使用,以确定任务在特定时区中的执行时间。threadName
:线程名称,用于指定执行任务的线程名称。如果未设置该参数,则默认使用"ScheduledThreadExecutor"。inheritable
:是否可继承,表示是否允许子类继承该注解的设置。默认为false。stateful
:是否状态保持,表示是否在每次执行任务时都保持状态。默认为false。jobName
:任务名称,用于指定计划任务的名称。如果未设置该参数,则默认使用方法名称作为任务名称。jobGroup
:任务组,用于将相关的计划任务分组在一起执行。如果未设置该参数,则默认使用方法所属的类作为任务组。
代码示例
Spring Boot提供了内置的@Scheduled
注解实现定时任务的功能。使用@Scheduled
注解创建定时任务非常简单,只需几行代码即可完成。
注意:默认情况下,Spring Boot定时任务是单线程方式执行的。
如果同一时刻有两个定时任务需要执行,那么只能在一个定时任务完成之后再执行下一个。
如果只有一个定时任务,这样做肯定没问题;当定时任务增多时,如果一个任务被阻塞,则会导致其他任务无法正常执行。要解决这个问题,需要配置任务调度线程池。
单线程定时任务
-
创建定时任务类
首先创建SchedulerTask类,然后在任务方法上添加@Scheduled注解,@EnableScheduling开启定时任务,具体的代码如下:
import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.text.SimpleDateFormat; import java.util.Date; @EnableScheduling @Component @Slf4j public class SchedulerTask { @Scheduled(cron="*/10 * * * * ?") protected void taskCron(){ SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); log.info("现在时间Scheduled1: {}" , dateFormat.format(new Date())); } }
-
启动项目
创建好SchedulerTask定时任务后启动项目,查看后台任务的运行情况,如图22-1所示。
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication(scanBasePackages = "org.sea.example.day14.task") public class SpringbootDay14Application { public static void main(String[] args) { SpringApplication.run(SpringbootDay14Application.class, args); } }
图22-1 后台定时任务执行日志
后台日志显示,SchedulerTask任务每隔10秒输出当前时间,说明定义的任务正在后台定时执行
多线程定时任务
-
增加多线程配置类
增加SchedulerConfig配置类,代码如下:
import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.Executor; @Configuration @Slf4j public class SchedulerConfig { @Bean public Executor asyncTaskScheduler() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(3); executor.setMaxPoolSize(10); executor.setQueueCapacity(3); executor.initialize(); log.info("多线程配置类初始化"); return executor; } }
设置执行线程池为3,最大线程数为10。
-
SchedulerTask定时任务
在类上增加@EnableAsync注解,在方法上增加@Async注解,使得后台任务能够异步执行,代码如下:
mport lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.text.SimpleDateFormat; import java.util.Date; @EnableAsync @EnableScheduling @Component @Slf4j public class SchedulerAsyncTask { @Async @Scheduled(cron="*/10 * * * * ?") protected void task1(){ SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); log.info("现在时间Scheduled1: {}" , dateFormat.format(new Date())); } @Async @Scheduled(cron="*/10 * * * * ?") protected void task2(){ SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); log.info("现在时间Scheduled2: {}" , dateFormat.format(new Date())); } @Async @Scheduled(cron="*/10 * * * * ?") protected void task3(){ SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); log.info("现在时间Scheduled3: {}" , dateFormat.format(new Date())); } }
在上面的示例中,定时任务类SechedulerTask增加了@EnableAsync注解,开启了异步事件支持。同时,在定时方法上增加@Async注解,使任务能够异步执行,这样各个后台任务就不会阻塞。
-
启动项目
创建好SchedulerTask定时任务后启动项目,查看后台任务的运行情况,如图22-2所示。
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication(scanBasePackages = "org.sea.example.day14.task2") public class SpringbootDay14Application { public static void main(String[] args) { SpringApplication.run(SpringbootDay14Application.class, args); } }
图22-2 后台定时任务执行日志
通过后台日志可以看到,Spring Boot启动线程池负责调度执行后台任务,各个后台任务之间相对独立、互不影响。
-
Quartz
1、什么是Quartz?
Quartz是OpenSymphony开源组织在任务调度(Job Scheduling,也称为作业调度)领域下的开源项目,它是Java开发的开源任务调度管理系统,具有使用灵活、配置简单的特点,能够实现复杂应用场景下的任务调度管理。当定时任务愈加复杂时,使用Spring Boot注解@Scheduled已经不能满足业务需要。相比之下,Quartz灵活而又不失简单,能够创建简单或复杂的调度任务,其主要具有如下功能:
1)持久化:将任务和状态持久化到数据库。
2)任务管理:对调度任务进行有效的管理。
3)集群:借助关系数据库和JDBC任务存储支持集群。
2、 Quartz的基本概念
Quartz是一个由Java开发的开源框架,用于执行定时任务。以下是Quartz的一些基本概念:
- Job:Job是一个接口,代表一个具体的任务。任务的具体逻辑在execute方法中实现。每次执行Job时,Quartz都会重新创建一个Job实例。
- JobDetail:用于定义Job的实例。因为相同的任务逻辑可能会被多次执行,所以使用JobDetail来创建每个独立的Job实例,确保各个任务可以独立运行。
- Trigger:Trigger是一个接口,用于定义执行给定Job的时间规则。主要有SimpleTrigger和CronTrigger这两个子接口。
- Scheduler:Scheduler是一个接口,代表Quartz的独立运行容器,用于与调度程序交互。
- JobBuilder:用于定义或构建JobDetail实例,帮助创建Job的实例。
- TriggerBuilder:用于定义或构建Trigger实例。
Trigger和JobDetail可以注册到Scheduler中,两者在Scheduler中拥有各自的组和名称,组和名称是Scheduler查找、定位容器中某个对象的依据,Trigger的组和名称必须唯一,JobDetail的组和名称也必须唯一(但可以与Trigger的组和名称相同,因为它们是不同类型的)。Scheduler定义了多个接口方法,允许外部通过组和名称访问控制容器中的Trigger与JobDetail。
总而言之,Scheduler相当于一个容器,其中包含各种Job和Trigger,四者之间的关系如图22-3所示
图22-3 四者之间的关系图
Quartz通过Scheduler触发Trigger规则实现任务的管理和调度。除此之外,Quartz还提供了TriggerBuilder
和JobBuilder
类来构建Trigger
实例和Job
实例。
Quartz主要有简单定时调度器(SimpleSchedule
)和Cron表达式调度器(CronSchedule
)来调度触发的定时任务。下面通过示例演示这两种调度器的用法。
简单定时调度器(SimpleSchedule
)
-
添加Quartz依赖
在pom.xml中配置Quartz的依赖包spring-boot-starter-quartz,具体配置如下:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency>
-
创建任务
创建定时任务的实现类SimpleJob,并继承QuartzJobBean,示例代码如下:
import lombok.extern.slf4j.Slf4j; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.scheduling.quartz.QuartzJobBean; @Slf4j public class SimpleJob extends QuartzJobBean { private String name; public void setName(String name) { this.name = name; } @Override protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException { log.info(String.format("Hello %s!", this.name)); } }
-
构建JobDetail、CronTrigger
接下来构建JobDetail和Trigger实例。首先使用SimpleScheduleBuilder创建Scheduler实例,然后关联JobDetail和Trigger实例。示例代码如下:
import org.quartz.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class SimpleScheduler { @Bean public JobDetail simpleJobDetail() { return JobBuilder.newJob(SimpleJob.class).withIdentity("simpleJobDetail") .usingJobData("name", "test simpleJob") .storeDurably() .build(); } @Bean public Trigger sampleJobTrigger() { SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(10).repeatForever(); return TriggerBuilder .newTrigger() .forJob(simpleJobDetail()) .withIdentity("sampleJobTrigger") .withSchedule(scheduleBuilder).build(); } }
-
运行任务
启动项目,验证任务是否能正常运行。如图22-4所示,
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /**修改scanBasePackages */ @SpringBootApplication(scanBasePackages = "org.sea.example.day14.job") public class SpringbootDay14Application { public static void main(String[] args) { SpringApplication.run(SpringbootDay14Application.class, args); } }
图22-4 简单任务运行日志
SimpleJob后台任务成功运行,每隔10秒执行一次,这说明使用SimpleSchedule创建简单的定时任务运行成功。
Cron表达式调度器(CronSchedule
)
-
定义Job
import lombok.extern.slf4j.Slf4j; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.scheduling.quartz.QuartzJobBean; @Slf4j public class CronJob extends QuartzJobBean { private String name; public void setName(String name) { this.name = name; } @Override protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException { log.info(String.format("Hello %s!", this.name)); } }
-
构建JobDetail、CronTrigger
import org.quartz.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class CronScheduler { @Bean public JobDetail cronJobDetail() { return JobBuilder.newJob(CronJob.class).withIdentity("cronJobDetail") .usingJobData("name", "test cronJob") .storeDurably() .build(); } @Bean public Trigger cronJobTrigger() { CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/10 * * * * ?"); return TriggerBuilder .newTrigger() .forJob(cronJobDetail()) .withIdentity("cronJobTrigger") .withSchedule(scheduleBuilder).build(); } }
-
运行任务
启动项目,验证任务是否能正常运行。如图22-5所示,
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /**修改scanBasePackages */ @SpringBootApplication(scanBasePackages = "org.sea.example.day14.job2") public class SpringbootDay14Application { public static void main(String[] args) { SpringApplication.run(SpringbootDay14Application.class, args); } }
图22-5 Cron定时任务运行日志
CronJob后台任务成功运行,每隔10秒执行一次,这说明使用
CronScheduleBuilder
创建简单的定时任务运行成功。