定时任务

返回 Spring Boot 基础

Spring Boot 内置 @Scheduled 注解,开箱即用;复杂场景(持久化、集群去重、动态调整)可集成 Quartz 或 XXL-Job。


启用定时任务

在启动类或任意配置类加 @EnableScheduling

@SpringBootApplication
@EnableScheduling
public class DemoApplication { ... }

@Scheduled 三种触发方式

fixedRate — 固定频率

@Scheduled(fixedRate = 5000)            // 每 5 秒执行一次(上次开始后计时)
public void fixedRateTask() {
    log.info("执行时间: {}", LocalDateTime.now());
}
 
// 首次延迟 10 秒后再按频率执行
@Scheduled(initialDelay = 10000, fixedRate = 5000)
public void delayedTask() { ... }

fixedDelay — 固定延迟

// 上次执行结束后,间隔 3 秒再执行(适合任务耗时不稳定的场景)
@Scheduled(fixedDelay = 3000)
public void fixedDelayTask() { ... }

cron — 表达式

@Scheduled(cron = "0 0 2 * * ?")       // 每天凌晨 2 点
public void dailyTask() { ... }
 
@Scheduled(cron = "0 */30 9-18 * * MON-FRI")  // 工作日 9-18 点每 30 分钟
public void workHourTask() { ... }
 
// 使用配置文件中的 cron 表达式(便于动态调整)
@Scheduled(cron = "${app.task.cron:0 0 3 * * ?}")
public void configurableTask() { ... }

Cron 表达式格式

Spring 使用 6 位 cron(秒 分 时 日 月 周):

┌─── 秒 (0-59)
│ ┌─── 分 (0-59)
│ │ ┌─── 时 (0-23)
│ │ │ ┌─── 日 (1-31)
│ │ │ │ ┌─── 月 (1-12 或 JAN-DEC)
│ │ │ │ │ ┌─── 周 (0-7 或 MON-SUN,0/7 均为周日)
│ │ │ │ │ │
* * * * * *
表达式含义
0 * * * * *每分钟第 0 秒
0 0 * * * *每小时整点
0 0 0 * * *每天 0 点
0 0 2 * * ?每天凌晨 2 点(? 表示不指定,用于日/周互斥)
0 0 10,14,16 * * ?每天 10、14、16 点
0 0/30 9-17 * * MON-FRI工作日 9-17 点每 30 分钟
0 0 12 1 * ?每月 1 日中午
0 0 0 L * ?每月最后一天

线程池配置

@Scheduled 默认单线程执行,多任务会相互阻塞。配置专用线程池:

@Configuration
public class SchedulerConfig implements SchedulingConfigurer {
 
    @Override
    public void configureTasks(ScheduledTaskRegistrar registrar) {
        registrar.setScheduler(taskScheduler());
    }
 
    @Bean(destroyMethod = "shutdown")
    public Executor taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(5);
        scheduler.setThreadNamePrefix("scheduler-");
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        scheduler.setAwaitTerminationSeconds(30);
        return scheduler;
    }
}

或直接在 application.yml 配置(Spring Boot 2.1+):

spring:
  task:
    scheduling:
      pool:
        size: 5
      thread-name-prefix: scheduler-

异步执行

结合 @Async 让每次调度在独立线程中运行,避免任务间阻塞,详见 异步与线程池

@Scheduled(fixedRate = 1000)
@Async("taskExecutor")              // 指定异步线程池
public void asyncTask() {
    // 每次触发都在新线程运行,上一次未完成也不影响
}

动态注册任务

SchedulingConfigurer 支持运行时动态添加/取消任务:

@Configuration
public class DynamicSchedulerConfig implements SchedulingConfigurer {
 
    @Override
    public void configureTasks(ScheduledTaskRegistrar registrar) {
        // 从数据库读取任务配置后动态注册
        taskConfigs.forEach(config ->
            registrar.addCronTask(
                () -> executeTask(config.getId()),
                config.getCronExpr()
            )
        );
    }
}

也可持有 ScheduledTaskRegistrar 引用,在运行时调用 scheduleCronTask() / cancel() 动态增删。


集群环境下的重复执行问题

单机 @Scheduled 在多实例部署时会每个实例都执行,常见解决方案:

方案一:分布式锁(轻量,推荐)

@Scheduled(cron = "0 0 3 * * ?")
public void cleanupTask() {
    boolean locked = redisLock.tryLock("task:cleanup", 10, TimeUnit.MINUTES);
    if (!locked) return;                    // 其他实例已在执行
    try {
        doCleanup();
    } finally {
        redisLock.unlock("task:cleanup");
    }
}

详见 分布式锁

方案二:ShedLock

专为定时任务设计的分布式锁框架,基于数据库或 Redis:

@Scheduled(cron = "0 0 3 * * ?")
@SchedulerLock(name = "cleanupTask",
    lockAtLeastFor = "PT5M",              // 锁持有最少 5 分钟
    lockAtMostFor = "PT1H")               // 锁持有最多 1 小时
public void cleanupTask() {
    doCleanup();
}
<dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-spring</artifactId>
</dependency>
<dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-provider-redis-spring</artifactId>
</dependency>

方案三:Quartz 集群模式

Quartz 通过共享数据库表协调集群,适合任务量大、需要持久化和失败重试的场景:

spring:
  quartz:
    job-store-type: jdbc            # 使用数据库存储
    jdbc:
      initialize-schema: always
    properties:
      org.quartz.jobStore.isClustered: true
      org.quartz.jobStore.clusterCheckinInterval: 20000

优雅停机

确保应用关闭时等待正在执行的任务完成:

spring:
  task:
    scheduling:
      shutdown:
        await-termination: true
        await-termination-period: 30s

详见 优雅停机


监控与告警

通过 Actuator 可查看定时任务状态:

management:
  endpoints:
    web:
      exposure:
        include: scheduledtasks    # 暴露 /actuator/scheduledtasks 端点

访问 /actuator/scheduledtasks 返回所有已注册的定时任务及其 cron/fixedRate 配置。


相关链接