【Springboot知识】Springboot计划任务Schedule详解

发布于:2025-05-07 ⋅ 阅读:(13) ⋅ 点赞:(0)

Spring Boot 定时任务从原理到实现详解

一、核心原理分析

1. 架构分层

graph TD
    A[Application] --> B[@EnableScheduling]
    B --> C[ScheduledAnnotationBeanPostProcessor]
    C --> D[TaskScheduler]
    D --> E[ThreadPoolTaskScheduler]
    D --> F[ConcurrentTaskScheduler]

2. 核心组件

  • @EnableScheduling:启用定时任务自动配置
  • ScheduledAnnotationBeanPostProcessor:解析@Scheduled注解
  • TaskScheduler:任务调度接口
  • CronTrigger:处理cron表达式

3. 线程模型

// 默认线程池配置
public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
    implements TaskScheduler, SchedulingTaskExecutor {
    
    private volatile int poolSize = 1; // 默认单线程
    private ThreadFactory threadFactory = new CustomizableThreadFactory("task-scheduler-");
}

二、基础实现步骤

1. 添加依赖

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>

2. 主类配置

@SpringBootApplication
@EnableScheduling
public class SchedulingApplication {
    public static void main(String[] args) {
        SpringApplication.run(SchedulingApplication.class, args);
    }
}

3. 定时任务类

@Component
public class ScheduledTasks {
    private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);

    // 固定频率(任务开始时间间隔)
    @Scheduled(fixedRate = 5000)
    public void fixedRateTask() {
        log.info("Fixed Rate Task :: Execution Time - {}", LocalDateTime.now());
    }

    // 固定延迟(任务结束时间间隔)
    @Scheduled(fixedDelay = 7000, initialDelay = 2000)
    public void fixedDelayTask() {
        log.info("Fixed Delay Task :: Execution Time - {}", LocalDateTime.now());
    }

    // Cron表达式
    @Scheduled(cron = "0 0/15 9-17 * * MON-FRI")
    public void cronTask() {
        log.info("Cron Task :: Execution Time - {}", LocalDateTime.now());
    }
}

三、高级配置技巧

1. 自定义线程池

@Configuration
public class SchedulerConfig implements SchedulingConfigurer {
    
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(5);
        taskScheduler.setThreadNamePrefix("custom-scheduler-");
        taskScheduler.initialize();
        
        taskRegistrar.setTaskScheduler(taskScheduler);
    }
}

2. 动态配置参数

application.properties
schedule.rate=10000
schedule.delay=5000
schedule.cron=0 0 8 * * *
@Scheduled(fixedRateString = "${schedule.rate}")
public void dynamicRateTask() {
    // ...
}

@Scheduled(cron = "${schedule.cron}")
public void dynamicCronTask() {
    // ...
}

3. 分布式锁集成(Redis示例)

@Scheduled(fixedRate = 10000)
public void distributedTask() {
    String lockKey = "scheduledTaskLock";
    String requestId = UUID.randomUUID().toString();
    
    try {
        if (redisLockUtil.tryGetLock(lockKey, requestId, 30)) {
            log.info("Acquired lock, executing task...");
            // 业务逻辑
        }
    } finally {
        redisLockUtil.releaseLock(lockKey, requestId);
    }
}

四、异常处理机制

1. 统一异常处理器

@ControllerAdvice
public class SchedulingExceptionHandler {
    
    @ExceptionHandler(TaskExecutionException.class)
    public void handleTaskException(TaskExecutionException ex) {
        log.error("Scheduled task failed: {}", ex.getMessage());
        // 发送告警通知
    }
}

2. 任务重试机制

@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 1000))
@Scheduled(fixedRate = 5000)
public void retryableTask() {
    // 可能失败的业务逻辑
    if (Math.random() > 0.5) {
        throw new RuntimeException("Simulated error");
    }
}

五、监控与调试

1. Actuator 端点

management.endpoints.web.exposure.include=scheduledtasks

访问 /actuator/scheduledtasks 查看任务列表:

{
    "cron": [
        {
            "runnable": {
                "target": "com.example.ScheduledTasks.cronTask"
            },
            "expression": "0 0/15 9-17 * * MON-FRI"
        }
    ]
}

2. 性能监控

@Scheduled(fixedRate = 5000)
@Timed(value = "scheduled.task", description = "监控任务执行时间")
public void monitoredTask() {
    // 业务逻辑
}

六、完整示例项目结构

scheduling-demo/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/example/
│   │   │       ├── config/
│   │   │       │   └── SchedulerConfig.java
│   │   │       ├── ScheduledTasks.java
│   │   │       └── SchedulingApplication.java
│   │   └── resources/
│   │       ├── application.properties
│   │       └── logback-spring.xml
│   └── test/
└── pom.xml

七、最佳实践建议

  1. 线程池配置原则

    # 推荐线程数 = CPU核心数 * 2(IO密集型)
    # 推荐线程数 = CPU核心数 + 1(计算密集型)
    
  2. 任务设计规范

    • 单任务执行时间 < 调度间隔时间
    • 添加事务边界控制
    • 避免任务间状态共享
  3. 生产环境注意事项

    // 添加健康检查
    @Component
    public class ScheduleHealthIndicator implements HealthIndicator {
        
        @Override
        public Health health() {
            // 检查任务最后执行时间
            return Health.up().build();
        }
    }
    

通过以上配置和实现,可以构建出高可靠、易维护的定时任务系统。定时任务的执行频率需要根据实际业务需求进行合理设置,同时要特别注意在分布式环境下的任务协调问题。

Schedule注解参数详细说明

一、注解基础参数详解

1. 核心参数配置
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
    String cron() default "";
    String zone() default "";
    long fixedDelay() default -1;
    String fixedDelayString() default "";
    long fixedRate() default -1;
    String fixedRateString() default "";
    long initialDelay() default -1;
    String initialDelayString() default "";
}
2. 参数对照表
参数名称 类型 必填 默认值 说明
cron String “” Unix风格的cron表达式
zone String “” 时区ID(如"Asia/Shanghai")
fixedDelay long -1 上次执行结束到下次执行的间隔(毫秒)
fixedDelayString String “” 支持占位符的字符串形式(如"${schedule.delay}")
fixedRate long -1 固定频率执行(毫秒)
fixedRateString String “” 支持占位符的字符串形式
initialDelay long -1 首次执行的延迟时间(毫秒)
initialDelayString String “” 支持占位符的字符串形式

二、Cron表达式详解

1. 标准格式
秒(0-59) 分(0-59) 时(0-23) 日(1-31) 月(1-12) 周(0-7) 年(可选)
2. 特殊字符说明
字符 含义 示例 说明
* 任意值 0 * * * * * 每分钟0秒执行
? 不指定(仅日/周字段) 0 0 0 ? * MON 每周一0点执行
- 范围 0 0 9-17 * * * 每天9点到17点整点执行
, 多个值 0 0 8,12,18 * * * 每天8、12、18点执行
/ 间隔频率 0 0/15 * * * * 每15分钟执行一次
L 最后一天/最后一周 0 0 0 L * ? 每月最后一天0点执行
W 最近工作日 0 0 0 LW * ? 每月最后一个工作日执行
# 第几个周几 0 0 0 ? * 5#2 每月第2个周四执行
3. 常用表达式示例
@Scheduled(cron = "0 0 3 * * ?")      // 每天凌晨3点执行
@Scheduled(cron = "0 0/5 9-17 * * MON-FRI") // 工作日9-17点每5分钟执行
@Scheduled(cron = "0 0 12 1 * ?")    // 每月1号中午12点执行
@Scheduled(cron = "0 0 8 L * ?")      // 每月最后一天上午8点执行

三、多模式配置示例

1. 基础模式组合
// 初始延迟3秒,之后每5秒执行
@Scheduled(initialDelay = 3000, fixedRate = 5000) 

// 每天8:30执行(使用属性配置)
@Scheduled(cron = "${app.schedule.daily-report}") 
2. 动态参数注入
application.properties
schedule.interval=5000
schedule.initial.delay=10000
schedule.cron.expression=0 0/15 * * * *
@Scheduled(
    fixedRateString = "${schedule.interval}",
    initialDelayString = "${schedule.initial.delay}"
)
public void dynamicScheduleTask() {
    // 业务逻辑
}

@Scheduled(cron = "${schedule.cron.expression}")
public void cronTask() {
    // 定时任务
}

四、高级使用技巧

1. 多任务并行执行
@Configuration
@EnableScheduling
public class ScheduleConfig implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(5);
        scheduler.setThreadNamePrefix("schedule-pool-");
        scheduler.initialize();
        taskRegistrar.setTaskScheduler(scheduler);
    }
}
2. 条件化调度
@Profile("production")  // 仅生产环境生效
@ConditionalOnProperty(name = "scheduler.enabled", havingValue = "true")
@Scheduled(fixedRate = 10000)
public void conditionalTask() {
    // 生产环境专用任务
}
3. 分布式锁集成
@Scheduled(cron = "0 0/30 * * * ?")
public void distributedTask() {
    String lockKey = "reportGenerationLock";
    try {
        if (redisLock.tryLock(lockKey, 300)) { // 获取30秒锁
            generateReport();
        }
    } finally {
        redisLock.release(lockKey);
    }
}

五、异常处理机制

1. 自定义异常处理器
@ControllerAdvice
public class ScheduleExceptionHandler {

    @ExceptionHandler(ScheduleExecutionException.class)
    public void handleScheduleException(ScheduleExecutionException ex) {
        log.error("定时任务执行失败: {}", ex.getMessage());
        // 发送告警通知
        alertService.sendAlert("Schedule Failure", ex.getMessage());
    }
}
2. 重试机制
@Retryable(
    value = {DataAccessException.class},
    maxAttempts = 3,
    backoff = @Backoff(delay = 1000, multiplier = 2)
)
@Scheduled(fixedDelay = 5000)
public void retryableTask() {
    // 可能失败的数据操作
    databaseService.batchUpdate();
}

六、常见问题解决方案

1. 任务不执行排查
1. 检查主类是否添加`@EnableScheduling`
2. 确认任务方法为`public`修饰
3. 验证cron表达式有效性(可用在线验证工具)
4. 检查线程池是否被占满(默认单线程)
2. 多实例重复执行
// 使用数据库锁方案示例
@Transactional
@Scheduled(cron = "0 0 3 * * ?")
public void exclusiveTask() {
    LocalDateTime now = LocalDateTime.now();
    ScheduleLock lock = lockRepo.findByTaskName("dailyCleanup");
    
    if (lock == null || lock.getLockUntil().isBefore(now)) {
        // 获取锁(设置30分钟有效期)
        lockRepo.save(new ScheduleLock("dailyCleanup", now.plusMinutes(30)));
        // 执行任务
        dataCleanupService.cleanup();
        // 释放锁
        lockRepo.deleteById("dailyCleanup");
    }
}

七、最佳实践建议

  1. 线程池配置原则

    # 推荐配置公式
    IO密集型任务:线程数 = CPU核心数 * 2
    计算密集型任务:线程数 = CPU核心数 + 1
    
  2. 执行时间监控

    @Scheduled(fixedRate = 60000)
    public void monitoredTask() {
        StopWatch watch = new StopWatch();
        try {
            watch.start();
            // 业务逻辑
        } finally {
            watch.stop();
            if (watch.getTotalTimeMillis() > 5000) {
                log.warn("任务执行超时: {}ms", watch.getTotalTimeMillis());
            }
        }
    }
    
  3. 重要任务日志规范

    @Scheduled(cron = "0 0 2 * * ?")
    public void criticalTask() {
        log.info("==== 开始执行数据归档任务 ====");
        try {
            archiveService.archiveData();
            log.info("数据归档成功,归档数量: {}", count);
        } catch (Exception e) {
            log.error("数据归档失败", e);
            throw e;
        } finally {
            log.info("==== 结束数据归档任务 ====");
        }
    }
    

通过合理配置和遵循最佳实践,可以构建出高可靠、易维护的定时任务系统。特别注意在分布式环境下做好任务协调,避免重复执行导致的数据不一致问题。


网站公告

今日签到

点亮在社区的每一天
去签到