定时模块
目前,几乎所有的业务都会涉及到定时,比如定时发送生日提醒,定时发短信邮件,定时取消订单等等场景。那业界针对定时的框架也有很多,比如单机版本的 java timer、ScheduledExecutorService 等,分布式的 elastic-job、xxl-job 等等
本章节主要从最底层的实现原理入手,在 Spring 基础上搭建自己的定时任务模块
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>
<artifactId>r-pan-framework</artifactId>
<groupId>com.imooc.pan</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>r-pan-schedule</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.imooc.pan</groupId>
<artifactId>r-pan-core</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</project>ScheduleConfig
java
package com.imooc.pan.schedule;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
/**
* 定时模块配置类
* 配置定时器执行器
*/
@SpringBootConfiguration
public class ScheduleConfig {
@Bean
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
return taskScheduler;
}
}ScheduleTask
java
package com.imooc.pan.schedule;
/**
* 定时任务的任务接口
*/
public interface ScheduleTask extends Runnable {
/**
* 获取定时任务的名称
*
* @return
*/
String getName();
}ScheduleTaskHolder
java
package com.imooc.pan.schedule;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.concurrent.ScheduledFuture;
/**
* 定时任务和定时任务结果的缓存对象
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ScheduleTaskHolder implements Serializable {
/**
* 执行任务实体
*/
private ScheduleTask scheduleTask;
/**
* 执行任务的结果实体
*/
private ScheduledFuture scheduledFuture;
}ScheduleManager
java
package com.imooc.pan.schedule;
import com.imooc.pan.core.exception.RPanFrameworkException;
import com.imooc.pan.core.utils.UUIDUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
/**
* 定时任务管理器
* 1、创建并启动一个定时任务
* 2、停止一个定时任务
* 3、更新一个定时任务
*/
@Component
@Slf4j
public class ScheduleManager {
@Autowired
private ThreadPoolTaskScheduler taskScheduler;
/**
* 内部正在执行的定时任务缓存
*/
private Map<String, ScheduleTaskHolder> cache = new ConcurrentHashMap<>();
/**
* 启动一个定时任务
*
* @param scheduleTask 定时任务实现类
* @param cron 定时任务的cron表达式
* @return
*/
public String startTask(ScheduleTask scheduleTask, String cron) {
ScheduledFuture<?> scheduledFuture = taskScheduler.schedule(scheduleTask, new CronTrigger(cron));
String key = UUIDUtil.getUUID();
// 将定时任务和定时任务结果缓存起来
ScheduleTaskHolder holder = new ScheduleTaskHolder(scheduleTask, scheduledFuture);
cache.put(key, holder);
log.info("{} 启动成功!唯一标识为:{}", scheduleTask.getName(), key);
return key;
}
/**
* 停止一个定时任务
*
* @param key 定时任务的唯一标识
*/
public void stopTask(String key) {
if (StringUtils.isBlank(key)) {
return;
}
ScheduleTaskHolder holder = cache.get(key);
if (Objects.isNull(holder)) {
return;
}
ScheduledFuture scheduledFuture = holder.getScheduledFuture();
boolean cancel = scheduledFuture.cancel(true);
if (cancel) {
log.info("{} 停止成功!唯一标识为:{}", holder.getScheduleTask().getName(), key);
} else {
log.error("{} 停止失败!唯一标识为:{}", holder.getScheduleTask().getName(), key);
}
}
/**
* 更新一个定时任务的执行时间
*
* @param key 定时任务的唯一标识
* @param cron 新的cron表达式
* @return
*/
public String changeTask(String key, String cron) {
if (StringUtils.isAnyBlank(key, cron)) {
throw new RPanFrameworkException("定时任务的唯一标识以及新的执行表达式不能为空");
}
ScheduleTaskHolder holder = cache.get(key);
if (Objects.isNull(holder)) {
throw new RPanFrameworkException(key + "唯一标识不存在");
}
stopTask(key);
return startTask(holder.getScheduleTask(), cron);
}
}版本差异(定时模块 → 当前)
| 特性 | 旧版 | 当前 |
|---|---|---|
| 单机调度 | Spring @Scheduled / ScheduledExecutorService | 不变;Boot 3.x 支持虚拟线程池执行定时任务 |
| 分布式调度 | elastic-job / xxl-job | xxl-job 2.4.x 仍主流;elastic-job 3.x(Apache ShardingSphere 生态) |
| CRON 语法 | 6/7 字段 | 不变;Spring 6 支持 ISO-8601 周期格式(如 PT10M) |
| 补偿任务 | 自研 | 不变;可结合消息队列延迟消息(RocketMQ 5.x 定时消息) |
定时任务技术选型与补偿清理思路不随版本变化;Spring 6.x 新增 ISO-8601 周期表达式,且 @Scheduled 默认线程池可配置。