当前位置: 首页 > article >正文

SpringBoot的@Scheduled和@Schedules有什么区别

@Scheduled 的详细解析

参数详解
  • cron: 使用Cron表达式来指定复杂的调度模式。Cron表达式的格式如下:

    • 秒(0-59)
    • 分钟(0-59)
    • 小时(0-23)
    • 日(1-31)
    • 月(1-12 或 JAN-DEC)
    • 星期(0-7 或 SUN-SAT,其中0和7都表示星期日)
    • 年(可选,1970-2099)

    Cron表达式的每个字段可以是具体的值、范围、列表或通配符(*)。例如:

    • "0 0 12 * * ?" 表示每天中午12点。
    • "0 15 10 ? * MON-FRI" 表示周一至周五上午10:15执行。
    • "0 0/5 * * * ?" 表示每5分钟执行一次。
    • "0 0 12 1 * ?" 表示每月第一天中午12点执行。
  • fixedRate: 指定以固定的速率重复执行任务,从前一次任务开始时刻算起。它不会等待前一个任务完成,因此如果任务执行时间超过了设定的时间间隔,可能会有重叠的任务实例在运行。

  • fixedDelay: 类似于 fixedRate,但是它是以前一次任务的完成时刻作为下一次任务启动的时间基准。这种方式可以确保每次只有一个任务实例在运行,前提是任务的执行时间短于延迟时间。

  • initialDelay: 在第一次执行之前等待的时间(毫秒)。这个参数通常与 fixedRatefixedDelay 一起使用,用来设置首次执行前的延迟。

  • zone: 定义时区,默认是系统的默认时区。如果你的应用需要在全球不同地区运行,明确指定时区可能是很重要的。

示例代码
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class ScheduledTasks {

    // 每天中午12点执行(上海时区)
    @Scheduled(cron = "0 0 12 * * ?", zone = "Asia/Shanghai")
    public void scheduledTaskUsingCron() {
        System.out.println("Scheduled task using cron at Asia/Shanghai timezone.");
    }

    // 每5秒执行一次,首次执行前等待2秒
    @Scheduled(fixedRate = 5000, initialDelay = 2000)
    public void scheduledTaskWithFixedRate() {
        System.out.println("Scheduled task with fixed rate.");
    }

    // 上次任务完成后等待3秒再执行下一次
    @Scheduled(fixedDelay = 3000)
    public void scheduledTaskWithFixedDelay() {
        System.out.println("Scheduled task with fixed delay.");
    }
}

@Schedules 的详细解析

@Schedules 允许多个 @Scheduled 注解组合在一起,为同一个方法设定多种不同的调度策略。这对于那些需要在多个不同时间点或条件下触发的方法非常有用。

示例代码
import org.springframework.scheduling.annotation.Schedules;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class MultipleScheduledTasks {

    // 每天中午12点执行,并且每5秒也执行一次
    @Schedules({
        @Scheduled(cron = "0 0 12 * * ?"),
        @Scheduled(fixedRate = 5000)
    })
    public void multipleScheduledTasks() {
        System.out.println("Multiple scheduled tasks.");
    }
}

启用和管理定时任务

要使这些注解生效,你需要确保你的Spring应用已经启用了对它们的支持。这可以通过在配置类上添加 @EnableScheduling 来实现:

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;

@Configuration
@EnableScheduling
public class SchedulingConfig {
    // 配置类内容
}

自定义 TaskScheduler

对于更复杂的需求,比如调整线程池大小或者设置线程名称前缀等,你可以通过自定义 TaskScheduler 来进行配置。Spring提供了几种内置的调度器实现,如 ThreadPoolTaskSchedulerConcurrentTaskScheduler

示例代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

@Configuration
public class SchedulerConfig {

    @Bean
    public ThreadPoolTaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(10); // 设置线程池大小
        taskScheduler.setThreadNamePrefix("MyScheduledTask-");
        taskScheduler.setErrorHandler(t -> {
            System.err.println("Error occurred in scheduled task: " + t.getMessage());
        });
        taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
        taskScheduler.setAwaitTerminationSeconds(60);
        return taskScheduler;
    }
}

错误处理

当一个预定任务抛出异常时,默认情况下Spring会记录错误日志,但任务本身不会被取消。如果你想改变这种行为,可以使用 DelegatingErrorHandlingRunnable 或者直接在 ThreadPoolTaskScheduler 中设置错误处理器(如上面的示例所示)。

自定义错误处理逻辑

你可以创建自己的错误处理器来捕获并处理异常:

import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@Configuration
public class AsyncConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(5);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("Async-");
        executor.initialize();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new SimpleAsyncUncaughtExceptionHandler() {
            @Override
            public void handleUncaughtException(Throwable ex, Method method, Object... params) {
                // 自定义异常处理逻辑
                System.err.println("Exception in async task: " + ex.getMessage());
            }
        };
    }
}

最佳实践

  • 避免长时间运行的任务:尽量不要让预定任务执行过长的时间,因为它们可能会阻塞其他任务的执行。如果任务有可能运行很长时间,请考虑将其拆分为更小的部分,或者使用异步处理。

  • 任务冲突管理:当使用 fixedRate 时,要注意任务可能会重叠。如果任务执行时间可能超过间隔时间,应该选择 fixedDelay 来避免这种情况。

  • 资源清理:确保在任务结束时正确释放任何获取的资源,比如数据库连接或文件句柄。

  • 监控和报警:建立适当的监控和报警机制,以便在任务失败时能够及时收到通知并采取行动。可以利用Spring Boot Actuator提供的健康检查端点,或者集成第三方监控工具如Prometheus、Grafana等。

  • 幂等性设计:确保任务逻辑具有幂等性,即多次执行相同的任务不会导致不一致的结果。这在分布式环境中尤为重要。

  • 日志记录:为每个任务添加详细的日志记录,包括任务开始时间和结束时间,以便追踪任务执行情况。

  • 测试:编写单元测试和集成测试来验证定时任务的行为是否符合预期。可以使用Mockito或其他测试框架模拟依赖服务。

  • 多实例部署的问题:在多实例部署的情况下,所有的实例都会尝试执行相同的定时任务,这可能导致数据竞争或重复执行。一种解决方案是使用分布式锁,如Redisson提供的RedLock,来保证同一时间只有一个实例执行特定的任务。

  • 性能优化:对于高并发场景下的定时任务,应该评估线程池的大小和任务的执行频率,避免因过多的任务同时启动而导致资源耗尽。可以通过限流、队列管理和异步处理等方式提高系统的稳定性和响应速度。

处理定时任务中的常见问题

  • 任务未按预期执行:检查日志以确定是否有任何异常或错误信息。确保任务方法是非静态的,并且没有被final修饰。确认 @EnableScheduling 已经正确启用。另外,检查是否存在其他因素阻止任务执行,如网络延迟或依赖服务不可用。

  • 任务执行顺序混乱:如果你有多个任务几乎同时执行,可能会出现执行顺序混乱的情况。确保你理解了 fixedRatefixedDelay 的区别,并根据需要选择合适的方式。此外,可以通过增加任务之间的最小间隔时间来减少冲突的可能性。

  • 多实例部署的问题:在多实例部署的情况下,所有实例都会尝试执行相同的定时任务。为了解决这个问题,可以引入分布式锁机制,如基于Redis的锁或Zookeeper的临时节点,以确保同一时间只有一个实例执行任务。

  • 长时间运行的任务:尽量不要让预定任务执行过长的时间,因为它们可能会阻塞其他任务的执行。如果任务有可能运行很长时间,请考虑将其拆分为更小的部分,或者使用异步处理,如通过消息队列(MQ)来分发任务。

  • 时区问题:确保你的应用程序正确处理时区差异,特别是在全球范围内运行时。可以在 @Scheduled 注解中显式指定 zone 参数,或者在整个应用程序中统一配置默认时区。

案例分析

假设你正在开发一个电子商务平台,需要每天凌晨2点生成前一天的销售报告。你可以使用 @Scheduled 注解来安排这个任务:

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
public class DailyReportService {

    @Scheduled(cron = "0 0 2 * * ?", zone = "Asia/Shanghai")
    public void generateDailySalesReport() {
        // 执行生成销售报告的逻辑
        System.out.println("Generating daily sales report at Asia/Shanghai timezone.");
    }
}

此外,你还可以结合上述的最佳实践来增强任务的可靠性,例如:

  • 确保任务具有幂等性,即使由于某种原因任务重复执行也不会影响结果。
  • 添加详细的日志记录,帮助追踪任务的执行情况。
  • 实现错误处理逻辑,确保即使发生异常也能得到妥善处理。
  • 如果平台有多实例部署,考虑使用分布式锁来防止多个实例同时生成报告。

http://www.kler.cn/a/488550.html

相关文章:

  • 案例研究:UML用例图中的结账系统
  • 30天开发操作系统 第 12 天 -- 定时器 v1.0
  • 贪心算法(五)
  • C++ 常见面试题(二)
  • 【微服务】SpringBoot 整合Redis实现延时任务处理使用详解
  • 攻防世界 ics-07
  • 腾讯云AI代码助手编程挑战赛-可视化飞线图
  • priority_queue优先队列
  • 用AI技术提升Flutter开发效率:ScriptEcho的力量
  • NFC碰一碰发视频源码搭建,支持OEM
  • 0052.基于Springboot+vue社区团购系统+论文
  • Redis 全维度深度剖析:从基础架构到实战应用
  • Vue页面开发和脚手架开发 Vue2集成ElementUI Vue3集成Element Plus
  • 手机的ip地址是根据电话卡归属地定吗
  • 浅聊MySQL中的LBCC和MVCC
  • 《Spring Framework实战》11:4.1.4.2.详细的依赖和配置2
  • PHP语言的学习路线
  • nexus搭建maven私服
  • CI/CD 流水线
  • css小知识:如何使用字体包
  • 高通,联发科(MTK)等手机平台调优汇总
  • QT转到槽报错The class containing “Ui::MainWindow“ could not be found in...
  • 二、智能体强化学习——深度强化学习核心算法
  • 银河麒麟编译QXlsx,使用Qt5.14.2
  • C++并发编程之基于锁的数据结构的适用场合与需要考虑和注意的问题
  • 【深度学习】核心概念-特征学习(Feature Learning)