外观
Spring Boot 定时任务
约 2201 字大约 7 分钟
2025-08-16
一、定时任务概述
1. 什么是定时任务
定时任务:按照预定时间自动执行的程序逻辑。
常见应用场景:
- 数据定时备份
- 系统状态监控
- 报表生成
- 缓存刷新
- 邮件/短信通知
- 订单超时处理
- 数据统计分析
2. Spring Boot 定时任务方案
Spring Boot 提供两种主要方案:
- Spring Task:基于注解的轻量级定时任务,适合简单场景
- Quartz:功能强大的企业级调度框架,适合复杂场景
选择建议:
- 80% 的简单场景 → Spring Task
- 需要持久化、集群、复杂调度 → Quartz
提示:对于大多数业务场景,Spring Task 已经足够使用,无需引入 Quartz 增加复杂度。
二、Spring Task 定时任务(最常用)
1. 基本配置
添加注解:
@SpringBootApplication
@EnableScheduling // 启用定时任务支持
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}无需额外依赖:Spring Boot 2.x 已内置 Task 模块,无需添加额外依赖。
2. @Scheduled 注解详解
核心参数:
| 参数 | 说明 | 示例 |
|---|---|---|
cron | cron 表达式,指定精确执行时间 | @Scheduled(cron = "0 0 2 * * ?") |
fixedRate | 任务执行间隔(从上一次任务开始到下一次任务开始) | @Scheduled(fixedRate = 5000) |
fixedDelay | 任务执行间隔(从上一次任务结束到下一次任务开始) | @Scheduled(fixedDelay = 5000) |
initialDelay | 首次执行延迟时间 | @Scheduled(initialDelay = 10000, fixedRate = 5000) |
使用示例:
@Service
public class ScheduledService {
// 每5秒执行一次(从上一次开始算起)
@Scheduled(fixedRate = 5000)
public void fixedRateTask() {
System.out.println("Fixed Rate Task: " + new Date());
}
// 上次任务完成后,等待5秒再执行
@Scheduled(fixedDelay = 5000)
public void fixedDelayTask() {
System.out.println("Fixed Delay Task: " + new Date());
// 模拟耗时操作
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// 每天凌晨2点执行
@Scheduled(cron = "0 0 2 * * ?")
public void dailyTask() {
System.out.println("Daily Task: " + new Date());
}
// 启动后延迟10秒,然后每5秒执行一次
@Scheduled(initialDelay = 10000, fixedRate = 5000)
public void initialDelayTask() {
System.out.println("Initial Delay Task: " + new Date());
}
}3. Cron 表达式详解
Cron 表达式格式:秒 分 时 日 月 周 年(可选)
| 位置 | 时间域 | 允许值 | 允许的特殊字符 |
|---|---|---|---|
| 1 | 秒 | 0-59 | , - * / |
| 2 | 分 | 0-59 | , - * / |
| 3 | 小时 | 0-23 | , - * / |
| 4 | 日 | 1-31 | , - * ? / L W |
| 5 | 月 | 1-12 | , - * / |
| 6 | 周 | 0-7(0和7都表示周日) | , - * ? / L # |
| 7 | 年(可选) | 1970-2099 | , - * / |
常用表达式:
| 表达式 | 含义 |
|---|---|
0 0 * * * ? | 每小时整点执行 |
0 0 2 * * ? | 每天凌晨2点执行 |
0 0 22 ? * MON-FRI | 周一到周五晚上10点执行 |
0 0/30 * * * ? | 每30分钟执行一次 |
0 0 9-17 * * MON-FRI | 工作日上午9点到下午5点,每小时执行一次 |
0 0 12 * * ? | 每天中午12点执行 |
0 15 10 ? * MON-FRI | 周一到周五上午10:15执行 |
提示:在线验证 Cron 表达式:https://www.cronmaker.com/
4. 动态修改定时任务
通过配置文件动态设置:
# application.yml
schedule:
fixed-rate: 5000
cron-expression: "0 0 2 * * ?"@Service
public class DynamicScheduledService {
@Value("${schedule.fixed-rate}")
private long fixedRate;
@Value("${schedule.cron-expression}")
private String cronExpression;
@Scheduled(fixedRateString = "${schedule.fixed-rate}")
public void dynamicFixedRateTask() {
System.out.println("Dynamic Fixed Rate Task: " + new Date());
}
@Scheduled(cron = "${schedule.cron-expression}")
public void dynamicCronTask() {
System.out.println("Dynamic Cron Task: " + new Date());
}
}通过数据库动态配置:
@Service
public class DatabaseScheduledService {
@Autowired
private ScheduleConfigRepository configRepository;
private ScheduledTaskRegistrar taskRegistrar;
@PostConstruct
public void init() {
// 启动时加载配置
reloadSchedules();
}
public void reloadSchedules() {
// 获取最新配置
ScheduleConfig config = configRepository.findLatest();
// 清除现有任务
if (taskRegistrar != null) {
taskRegistrar.destroy();
}
// 重新注册任务
taskRegistrar = new ScheduledTaskRegistrar();
taskRegistrar.addCronTask(this::scheduledTask, config.getCronExpression());
taskRegistrar.afterPropertiesSet();
}
private void scheduledTask() {
System.out.println("Database Configured Task: " + new Date());
}
}三、Quartz 定时任务(复杂场景)
1. 何时使用 Quartz
适用场景:
- 需要任务持久化(重启后继续执行)
- 需要分布式集群支持
- 需要复杂的调度规则
- 需要任务依赖关系
- 需要任务执行历史记录
不适用场景:
- 简单的定时任务
- 无需持久化和集群
- 资源受限环境
2. 基本配置
添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>配置 Quartz:
spring:
quartz:
job-store-type: jdbc # 使用数据库存储任务(集群必备)
initialize-schema: always # 初始化quartz表结构
properties:
org:
quartz:
scheduler:
instanceName: MyScheduler
instanceId: AUTO
threadPool:
threadCount: 103. 核心概念与实现
Quartz 核心组件:
- Job:要执行的具体任务
- Trigger:触发器,定义何时执行任务
- Scheduler:调度器,管理任务和触发器
Job 实现:
@Component
public class SampleJob extends QuartzJobBean {
@Autowired
private SampleService sampleService;
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
// 执行业务逻辑
sampleService.process();
System.out.println("Quartz Job executed at: " + new Date());
}
}配置 JobDetail 和 Trigger:
@Configuration
public class QuartzConfig {
@Bean
public JobDetail sampleJobDetail() {
return JobBuilder.newJob(SampleJob.class)
.withIdentity("sampleJob")
.storeDurably()
.build();
}
@Bean
public Trigger sampleJobTrigger() {
// 每5秒执行一次
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(5)
.repeatForever();
return TriggerBuilder.newTrigger()
.forJob(sampleJobDetail())
.withIdentity("sampleTrigger")
.withSchedule(scheduleBuilder)
.build();
}
}4. 动态管理任务
动态添加任务:
@Service
public class QuartzService {
@Autowired
private Scheduler scheduler;
public void addJob(String jobName, String jobGroup, String cronExpression) throws SchedulerException {
// 1. 创建JobDetail
JobDetail jobDetail = JobBuilder.newJob(SampleJob.class)
.withIdentity(jobName, jobGroup)
.storeDurably()
.build();
// 2. 创建Trigger
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(jobName + "_trigger", jobGroup)
.withSchedule(scheduleBuilder)
.build();
// 3. 添加到调度器
scheduler.scheduleJob(jobDetail, trigger);
}
public void pauseJob(String jobName, String jobGroup) throws SchedulerException {
scheduler.pauseJob(new JobKey(jobName, jobGroup));
}
public void resumeJob(String jobName, String jobGroup) throws SchedulerException {
scheduler.resumeJob(new JobKey(jobName, jobGroup));
}
public void deleteJob(String jobName, String jobGroup) throws SchedulerException {
scheduler.deleteJob(new JobKey(jobName, jobGroup));
}
}四、实用技巧与最佳实践
1. 异步执行定时任务
使用场景:任务执行时间较长,避免阻塞其他定时任务
实现方式:
@Configuration
@EnableScheduling
@EnableAsync
public class SchedulerConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setTaskScheduler(taskScheduler());
}
@Bean(destroyMethod = "shutdown")
public Executor taskScheduler() {
return Executors.newScheduledThreadPool(10);
}
@Bean
public Executor asyncExecutor() {
return Executors.newFixedThreadPool(5);
}
}
@Service
public class AsyncScheduledService {
@Async
@Scheduled(fixedRate = 5000)
public void asyncTask() {
System.out.println("Async Task started: " + new Date());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Async Task completed: " + new Date());
}
}2. 错误处理与重试
实现任务执行失败重试:
@Service
public class RetryScheduledService {
private static final int MAX_RETRY_COUNT = 3;
@Scheduled(fixedRate = 5000)
public void retryTask() {
int retryCount = 0;
boolean success = false;
while (!success && retryCount < MAX_RETRY_COUNT) {
try {
// 执行业务逻辑
performTask();
success = true;
} catch (Exception e) {
retryCount++;
if (retryCount >= MAX_RETRY_COUNT) {
// 记录最终失败日志
System.err.println("Task failed after " + MAX_RETRY_COUNT + " retries");
// 可以发送告警
} else {
// 指数退避重试
long waitTime = (long) Math.pow(2, retryCount) * 1000;
try {
Thread.sleep(waitTime);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
}
}
}
private void performTask() {
// 业务逻辑
}
}3. 分布式环境下的定时任务
问题:在集群环境下,多个实例会同时执行定时任务
解决方案:
使用 Quartz JDBC 存储:
- 配置
spring.quartz.job-store-type=jdbc - Quartz 会自动处理集群环境下的任务分配
- 配置
使用分布式锁:
@Scheduled(fixedRate = 5000) public void distributedTask() { String lockKey = "scheduled:task:lock"; try { // 尝试获取分布式锁(例如Redis) boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", 30, TimeUnit.SECONDS); if (locked) { // 执行任务 System.out.println("Executing distributed task: " + new Date()); } } finally { // 释放锁 redisTemplate.delete(lockKey); } }指定单节点执行:
# 指定哪个节点执行定时任务 schedule: node: node1@Scheduled(fixedRate = 5000) public void singleNodeTask() { if (currentNode.equals(environment.getProperty("schedule.node"))) { System.out.println("Executing on designated node: " + new Date()); } }
4. 定时任务监控
使用 Micrometer 监控:
@Component
public class ScheduledMetrics {
private final Counter successCounter;
private final Counter failureCounter;
private final Timer executionTimer;
public ScheduledMetrics(MeterRegistry registry) {
successCounter = Counter.builder("scheduled.task.success")
.description("Total number of successful scheduled tasks")
.register(registry);
failureCounter = Counter.builder("scheduled.task.failure")
.description("Total number of failed scheduled tasks")
.register(registry);
executionTimer = Timer.builder("scheduled.task.execution")
.description("Scheduled task execution time")
.register(registry);
}
public void recordSuccess() {
successCounter.increment();
}
public void recordFailure() {
failureCounter.increment();
}
public <T> T recordExecution(Supplier<T> supplier) {
return executionTimer.record(supplier);
}
}在服务中使用:
@Service
public class MonitoredScheduledService {
@Autowired
private ScheduledMetrics metrics;
@Scheduled(fixedRate = 5000)
public void monitoredTask() {
try {
metrics.recordExecution(() -> {
// 执行业务逻辑
return null;
});
metrics.recordSuccess();
} catch (Exception e) {
metrics.recordFailure();
throw e;
}
}
}五、常见问题与解决方案
1. 定时任务不执行
| 问题 | 原因 | 解决方案 |
|---|---|---|
未添加@EnableScheduling | 忘记启用定时任务 | 在主类或配置类添加@EnableScheduling |
| 任务方法不是public | Spring AOP限制 | 确保定时任务方法是public |
| 任务执行时间过长 | 阻塞了任务线程 | 增加线程池大小或使用异步执行 |
| 时区问题 | 服务器时区与cron表达式不匹配 | 在cron表达式中指定时区或统一使用UTC |
2. 任务执行冲突
问题:任务执行时间超过调度间隔,导致任务堆积
解决方案:
- 使用
fixedDelay代替fixedRate - 增加调度间隔
- 实现任务互斥机制
private final AtomicBoolean isRunning = new AtomicBoolean(false); @Scheduled(fixedRate = 5000) public void safeTask() { if (isRunning.compareAndSet(false, true)) { try { // 执行任务 } finally { isRunning.set(false); } } }
3. 时区问题
解决方案:
// 在cron表达式中指定时区
@Scheduled(cron = "0 0 2 * * ? Asia/Shanghai")
// 或在配置文件中设置默认时区
spring:
quartz:
properties:
org:
quartz:
scheduler:
idleWaitTime: 1
overwriteExistingJobs: true
# 设置默认时区
timeZone: Asia/Shanghai六、实用技巧速查表
| 场景 | 推荐方案 | 注意事项 |
|---|---|---|
| 简单定时任务 | Spring Task | 无需额外依赖 |
| 复杂调度需求 | Quartz | 需要数据库支持集群 |
| 动态修改任务 | 数据库存储配置 | 需要实现动态调度逻辑 |
| 防止重复执行 | 任务互斥机制 | 使用AtomicBoolean或分布式锁 |
| 任务失败处理 | 指数退避重试 | 设置最大重试次数 |
| 分布式环境 | Quartz JDBC存储 | 配置spring.quartz.job-store-type=jdbc |
| 任务监控 | Micrometer + Prometheus | 结合Grafana可视化 |
