赞
踩
完整代码示例: https://github.com/YunaiV/SpringBoot-Labs 的 lab-28 目录。
转自 : http://www.iocoder.cn/Spring-Boot/Job/
在产品的色彩斑斓的黑的需求中,有存在一类需求,是需要去定时执行的,此时就需要使用到定时任务。例如说,每分钟扫描超时支付的订单,每小时清理一次日志文件,每天统计前一天的数据并生成报表,每个月月初的工资单的推送,每年一次的生日提醒等等。
其中,最喜欢“每个月月初的工资单的推送”,你呢?
在 JDK 中,内置了两个类,可以实现定时任务的功能:
java.util.Timer
:可以通过创建 java.util.TimerTask
调度任务,在同一个线程中串行执行,相互影响。也就是说,对于同一个 Timer 里的多个 TimerTask 任务,如果一个 TimerTask 任务在执行中,其它 TimerTask 即使到达执行的时间,也只能排队等待。因为 Timer 是串行的,同时存在 坑坑 ,所以后来 JDK 又推出了 ScheduledExecutorService ,Timer 也基本不再使用。
java.util.concurrent.ScheduledExecutorService
:在 JDK 1.5 新增,基于线程池设计的定时任务类,每个调度任务都会被分配到线程池中并发执行,互不影响。这样,ScheduledExecutorService 就解决了 Timer 串行的问题。
在日常开发中,我们很少直接使用 Timer 或 ScheduledExecutorService 来实现定时任务的需求。主要有几点原因:
它们仅支持按照指定频率,不直接支持指定时间的定时调度,需要我们结合 Calendar 自行计算,才能实现复杂时间的调度。例如说,每天、每周五、2019-11-11 等等。
它们是进程级别,而我们为了实现定时任务的高可用,需要部署多个进程。此时需要等多考虑,多个进程下,同一个任务在相同时刻,不能重复执行。
项目可能存在定时任务较多,需要统一的管理,此时不得不进行二次封装。
所以,一般情况下,我们会选择专业的调度任务中间件。
关于“任务”的叫法,也有叫“作业”的。在英文上,有 Task 也有 Job 。本质是一样的,本文两种都会用。
然后,一般来说是调度任务,定时执行。所以胖友会在本文,或者其它文章中,会看到“调度”或“定时”的字眼儿。
在 Spring 体系中,内置了两种定时任务的解决方案:
第一种,Spring Framework 的 Spring Task 模块,提供了轻量级的定时任务的实现。
第二种,Spring Boot 2.0 版本,整合了 Quartz 作业调度框架,提供了功能强大的定时任务的实现。
注:Spring Framework 已经内置了 Quartz 的整合。Spring Boot 1.X 版本未提供 Quartz 的自动化配置,而 2.X 版本提供了支持。
在 Java 生态中,还有非常多优秀的开源的调度任务中间件:
Elastic-Job
唯品会基于 Elastic-Job 之上,演化出了 Saturn 项目。
Apache DolphinScheduler
XXL-JOB
目前国内采用 Elastic-Job 和 XXL-JOB 为主。从了解到的情况,使用 XXL-JOB 的团队会更多一些,主要是上手较为容易,运维功能更为完善。
本文,我们会按照 Spring Task、Quartz、XXL-JOB 的顺序,进行分别入门。而在文章的结尾,会简单聊聊分布式定时任务的实现原理。
示例代码对应仓库:lab-28-task-demo 。
考虑到实际场景下,我们很少使用 Spring Task ,所以本小节会写的比较简洁。如果对 Spring Task 比较感兴趣的胖友,可以自己去阅读 《Spring Framework Documentation —— Task Execution and Scheduling》 文档,里面有 Spring Task 相关的详细文档。
在本小节,我们会使用 Spring Task 功能,实现一个每 2 秒打印一行执行日志的定时任务。
在 pom.xml
文件中,引入相关依赖。
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>2.2.1.RELEASE</version>
- <relativePath/> <!-- lookup parent from repository -->
- </parent>
- <modelVersion>4.0.0</modelVersion>
-
- <artifactId>lab-28-task-demo</artifactId>
-
- <dependencies>
- <!-- 实现对 Spring MVC 的自动化配置 -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- </dependencies>
-
- </project>
因为 Spring Task 是 Spring Framework 的模块,所以在我们引入 spring-boot-starter-web
依赖后,无需特别引入它。
同时,考虑到我们希望让项目启动时,不自动结束 JVM 进程,所以我们引入了 spring-boot-starter-web
依赖。
在 cn.iocoder.springboot.lab28.task.config
包路径下,创建 ScheduleConfiguration 类,配置 Spring Task 。代码如下:
- // ScheduleConfiguration.java
-
- @Configuration
- @EnableScheduling
- public class ScheduleConfiguration {
- }
在类上,添加 @EnableScheduling
注解,启动 Spring Task 的定时任务调度的功能。
在 cn.iocoder.springboot.lab28.task.job
包路径下,创建 DemoJob 类,示例定时任务类。代码如下:
- // DemoJob.java
-
- @Component
- public class DemoJob {
-
- private Logger logger = LoggerFactory.getLogger(getClass());
-
- private final AtomicInteger counts = new AtomicInteger();
-
- @Scheduled(fixedRate = 2000)
- public void execute() {
- logger.info("[execute][定时第 ({}) 次执行]", counts.incrementAndGet());
- }
-
- }
在类上,添加 @Component
注解,创建 DemoJob Bean 对象。
创建 #execute()
方法,实现打印日志。同时,在该方法上,添加 @Scheduled
注解,设置每 2 秒执行该方法。
虽然说,@Scheduled
注解,可以添加在一个类上的多个方法上,但是个人习惯上,还是一个 Job 类,一个定时任务。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。