当前位置:   article > 正文

Spring Boot 中使用 Spring Task 实现定时任务_springboot task.execution

springboot task.execution

在日常项目开发中我们经常要使用定时任务。比如定时获取信息,发布任务等等。今天我们就来看看如何在 Spring Boot 中使用 Spring 内置的定时任务。

一、开启定时任务

Spring Boot 默认在无任何第三方依赖的情况下使用 spring-context 模块下提供的定时任务工具 Spring Task。我们只需要使用 @EnableScheduling 注解就可以开启相关的定时任务功能。如:

 然后我们就可以通过注解的方式实现自定义定时任务。

二、@Scheduled 注解实现定时任务

 只需要定义一个 Spring Bean ,然后定义具体的定时任务逻辑方法并使用 @Scheduled 注解标记该方法即可。

@Scheduled 注解中一定要声明定时任务的执行策略 cronfixedDelayfixedRate 三选一。

三、执行策略

1、cron表达式

该参数接收一个cron表达式,cron表达式是一个字符串,字符串以5或6个空格隔开,分开共6或7个域,每一个域代表一个含义。

cron 表达式语法:

格式:[秒] [分] [小时] [日] [月] [周] [年]

序号说明  是否必填    允许填写的值    允许的通配符
10-59, - * /
20-59, - * /
30-23, - * /
41-31, - * ? / L W
51-12 or JAN-DEC, - * /
61-7 or SUN-SAT, - * ? / L #
7empty 或 1970-2099, - * /

可通过在线生成Cron表达式的工具:在线Cron表达式生成器 来生成自己想要的表达式。


通配符说明:

* 表示所有值. 例如:在分的字段上设置 "*",表示每一分钟都会触发。

? 表示不指定值。使用的场景为不需要关心当前设置这个字段的值。例如:要在每月的10号触发一个操作,但不关心是周几,所以需要周位置的那个字段设置为"?" 具体设置为 0 0 0 10 * ?

- 表示区间。例如 在小时上设置 "10-12",表示 10,11,12点都会触发。

, 表示指定多个值,例如在周字段上设置 "MON,WED,FRI" 表示周一,周三和周五触发

/ 用于递增触发。如在秒上面设置"5/15" 表示从5秒开始,每增15秒触发(5,20,35,50)。在月字段上设置'1/3'所示每月1号开始,每隔三天触发一次。

L 表示最后的意思。在日字段设置上,表示当月的最后一天(依据当前月份,如果是二月还会依据是否是润年[leap]), 在周字段上表示星期六,相当于"7"或"SAT"。如果在"L"前加上数字,则表示该数据的最后一个。例如在周字段上设置"6L"这样的格式,则表示“本月最后一个星期五"

W 表示离指定日期的最近那个工作日(周一至周五). 例如在日字段上设置"15W",表示离每月15号最近的那个工作日触发。如果15号正好是周六,则找最近的周五(14号)触发, 如果15号是周未,则找最近的下周一(16号)触发.如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 "1W",它则表示每月1号往后最近的工作日触发。如果1号正是周六,则将在3号下周一触发。(注,"W"前只能设置具体的数字,不允许区间"-").

小提示:'L'和 'W'可以一组合使用。如果在日字段上设置"LW",则表示在本月的最后一个工作日触发(一般指发工资) 。

# 序号(表示每月的第几个周几),例如在周字段上设置"6#3"表示在每月的第三个周六.注意如果指定"#5",正好第五周没有周六,则不会触发该配置

2、fixedDelay

fixedDelay。它的间隔时间是根据上次的任务结束的时候开始计时的,只要盯紧上一次执行结束的时间即可,跟任务逻辑的执行时间无关,两个轮次的间隔距离是固定的。

3、fixedRate

 fixedRate。这个相对难以理解一些。在理想情况下,下一次开始和上一次开始之间的时间间隔是一定的。但是默认情况下 Spring Boot 定时任务是单线程执行的。当下一轮的任务满足时间策略后任务就会加入队列,也就是说当本次任务开始执行时下一次任务的时间就已经确定了,由于本次任务的“超时”执行,下一次任务的等待时间就会被压缩甚至阻塞。

 4、initialDelay

initialDelay 初始化延迟时间,也就是第一次延迟执行的时间。这个参数对 cron 属性无效,只能配合 fixedDelayfixedRate 使用。如 @Scheduled(initialDelay=5000,fixedDelay = 1000) 表示第一次延迟 5000 毫秒执行,下一次任务在上一次任务结束后 1000 毫秒后执行。

四、Spring Task 的弊端

Spring Task 在实际应用中如果不明白一些机制会出现一些问题的,所以下面的一些要点十分重要。

1、单线程阻塞执行

Spring 的定时任务默认是单线程执行,多任务情况下,如果使用多线程会影响定时策略。我们来演示一下:

  1. import org.springframework.scheduling.annotation.Scheduled;
  2. import org.springframework.stereotype.Component;
  3. import java.time.LocalDateTime;
  4. import java.time.format.DateTimeFormatter;
  5. /**
  6. * The type Task service.
  7. *
  8. * @author felord.cn
  9. * @since 11 :02
  10. */
  11. @Component
  12. public class TaskService {
  13. /**
  14. * 上一次任务结束后 1 秒,执行下一次任务,任务消耗 5秒
  15. *
  16. * @throws InterruptedException the interrupted exception
  17. */
  18. @Scheduled(fixedDelay = 1000)
  19. public void task() throws InterruptedException {
  20. System.out.println("Thread Name : "
  21. + Thread.currentThread().getName()
  22. + " i am a task : date -> "
  23. + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
  24. Thread.sleep(5000);
  25. }
  26. /**
  27. * 下轮任务在本轮任务开始2秒后执行. 执行时间可忽略不计
  28. */
  29. @Scheduled(fixedRate = 2000)
  30. public void task2() {
  31. System.out.println("Thread Name : "
  32. + Thread.currentThread().getName()
  33. + " i am a task2 : date -> "
  34. + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
  35. }
  36. }

 

 也就是说因为单线程阻塞发生了“连锁反应”,导致了任务执行的错乱。

@EnableScheduling 注解引入了 ScheduledAnnotationBeanPostProcessor ,其 setScheduler(Object scheduler) 有以下的注释:

如果 TaskScheduler 或者 ScheduledExecutorService 没有定义为该方法的参数,该方法将在 Spring IoC 中寻找唯一的 TaskScheduler 或者 名称为 taskScheduler 的 Bean 作为参数,当然你按照查找 TaskScheduler 的方法找一个ScheduledExecutorService 也可以。要是都找不到那么只能使用本地单线程调度器了。

Spring Task 的调用顺序关系为:任务调度线程 调度 任务执行线程 执行 定时任务 所以我们按照上面定义一个 TaskSchedulerSpring Boot 自动配置中提供了 TaskScheduler 的自动配置:

  1. @ConditionalOnClass(ThreadPoolTaskScheduler.class)
  2. @Configuration(proxyBeanMethods = false)
  3. @EnableConfigurationProperties(TaskSchedulingProperties.class)
  4. @AutoConfigureAfter(TaskExecutionAutoConfiguration.class)
  5. public class TaskSchedulingAutoConfiguration {
  6. @Bean
  7. @ConditionalOnBean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
  8. @ConditionalOnMissingBean({ SchedulingConfigurer.class, TaskScheduler.class, ScheduledExecutorService.class })
  9. public ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder builder) {
  10. return builder.build();
  11. }
  12. @Bean
  13. @ConditionalOnMissingBean
  14. public TaskSchedulerBuilder taskSchedulerBuilder(TaskSchedulingProperties properties,
  15. ObjectProvider<TaskSchedulerCustomizer> taskSchedulerCustomizers) {
  16. TaskSchedulerBuilder builder = new TaskSchedulerBuilder();
  17. builder = builder.poolSize(properties.getPool().getSize());
  18. Shutdown shutdown = properties.getShutdown();
  19. builder = builder.awaitTermination(shutdown.isAwaitTermination());
  20. builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
  21. builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
  22. builder = builder.customizers(taskSchedulerCustomizers);
  23. return builder;
  24. }
  25. }

该配置的自定义配置以 spring.task.scheduling 开头。同时它需要在任务执行器配置 TaskExecutionAutoConfiguration 配置后才生效。我们只需要在中对其配置属性 spring.task.execution 相关属性配置即可。

Spring Bootapplication.properties 中相关的配置说明:

  1. # 任务调度线程池
  2. # 任务调度线程池大小 默认 1 建议根据任务加大
  3. spring.task.scheduling.pool.size=1
  4. # 调度线程名称前缀 默认 scheduling-
  5. spring.task.scheduling.thread-name-prefix=scheduling-
  6. # 线程池关闭时等待所有任务完成
  7. spring.task.scheduling.shutdown.await-termination=
  8. # 调度线程关闭前最大等待时间,确保最后一定关闭
  9. spring.task.scheduling.shutdown.await-termination-period=
  10. # 任务执行线程池配置
  11. # 是否允许核心线程超时。这样可以动态增加和缩小线程池
  12. spring.task.execution.pool.allow-core-thread-timeout=true
  13. # 核心线程池大小 默认 8
  14. spring.task.execution.pool.core-size=8
  15. # 线程空闲等待时间 默认 60s
  16. spring.task.execution.pool.keep-alive=60s
  17. # 线程池最大数 根据任务定制
  18. spring.task.execution.pool.max-size=
  19. # 线程池 队列容量大小
  20. spring.task.execution.pool.queue-capacity=
  21. # 线程池关闭时等待所有任务完成
  22. spring.task.execution.shutdown.await-termination=true
  23. # 执行线程关闭前最大等待时间,确保最后一定关闭
  24. spring.task.execution.shutdown.await-termination-period=
  25. # 线程名称前缀
  26. spring.task.execution.thread-name-prefix=task-

配置完后你就会发现定时任务可以并行异步执行了。

2、默认不支持分布式

Spring Task 并不是为分布式环境设计的,在分布式环境下,这种定时任务是不支持集群配置的,如果部署到多个节点上,各个节点之间并没有任何协调通讯机制,集群的节点之间是不会共享任务信息的,每个节点上的任务都会按时执行,导致任务的重复执行。我们可以使用支持分布式的定时任务调度框架,比如 Quartz、XXL-Job、Elastic Job。当然你可以借助 zookeeper 、 redis 等实现分布式锁来处理各个节点的协调问题。或者把所有的定时任务抽成单独的服务单独部署。

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号