你不知道的Scheduled定时任务骚操作
一、什么是定时任务
二、项目依赖
三、注解式定时任务
3.1 cron
3.2 fixedDelay
3.3 fixedDelayString
3.4 fixedRate
3.5 fixedRateString
3.6 initialDelay 和 initialDelayString
3.7 zone
四、可更改时间的定时任务
五、可启动停止改变定时任务
六、分布式集群注意事项
一、什么是定时任务
开发中经常会使用到定时任务,顾名思义,定时任务就是定时执行的方法,即定时执行的代码。比如,为了减少服务器或者数据库的压力,我们会将一些对服务器或者数据库等有压力的高频操作,改为定时去执行,例如每晚凌晨0点同步A系统的数据到B系统,每2小时统计用户的积分情况,每周一给支付宝用户推送上周收入支出数据报表等。一般情况下,很多业务处理会定时在凌晨处理,因为避开了用户使用高峰期,服务器资源充足,而且对用户影响小。
作为优秀的框架,SpringBoot自然为我们提供了定时任务,有三种使用的方式,第一种是使用注解的方式(比较常用),这种不能动态更改定时任务的时间;第二种是可以动态更改定时任务的时间;第三种是可以动态手动启动,停止以及更改定时任务时间的定时任务。
二、项目依赖
既然是SpringBoot提供的定时任务,那首先得引入Springboot相关的依赖,因为演示用到了接口调用,所以也引入web相关的依赖。然后演示项目采用Maven工程,最终依赖pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.10.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.nobody</groupId> <artifactId>scheduled-task</artifactId> <version>0.0.1-SNAPSHOT</version> <name>scheduled-task</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>
三、注解式定时任务
这种方式很简单,直接在需要定时执行的方法上加@Scheduled注解即可。如下表示每天凌晨0点执行test方法。
@Scheduled(cron = "0 0 0 * * ? ")public void test() { // doSomething}
@Scheduled注解有几个属性,我们一一讲解它的作用。
package org.springframework.scheduling.annotation;@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Repeatable(Schedules.class)public @interface Scheduled {String CRON_DISABLED = "-";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 "";}
3.1 cron
String CRON_DISABLED = "-";String cron() default "";
它的值是一个cron表达式字符串,指明定时任务的执行时机。如果它的值是一个特殊的"-"字符串,也就是CRON_DISABLED属性定义的值,代表定时任务无效,不会执行。此特殊值主要用于外部指定值,即占位符${...}时,可以通过配置文件灵活控制定时任务的开启停用。
此种方式最常用,而且cron的强大能让我们涵盖各种时间的配置。
cron表达式我就不细讲了,下面推荐一个方便生成cron的网站:https://cron.qqe2.com/
注意,定时任务所在的类,要将其交予Spring容器管理,最简单的是在类上添加@Component注解,如下所示:
package com.nobody.task;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;/** * @Description 定时任务类 * @Author Mr.nobody * @Date 2021/3/2 * @Version 1.0.0 */@Componentpublic class ScheduledTask { private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledTask.class);// 每5秒执行一次 @Scheduled(cron = "0/5 * * * * ? ") public void test() { LOGGER.info(">>> ScheduledTask test... "); }}
而且要通过@EnableScheduling注解激活,不然不生效。
package com.nobody;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.scheduling.annotation.EnableScheduling;@SpringBootApplication@EnableSchedulingpublic class ScheduledTaskApplication { public static void main(String[] args) { SpringApplication.run(ScheduledTaskApplication.class, args); }}
我们启动服务,可以在控制看到每隔5秒执行了定时任务。
2021-03-02 23:44:00.005 INFO 10852 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-02 23:44:05.001 INFO 10852 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-02 23:44:10.000 INFO 10852 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-02 23:44:15.002 INFO 10852 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-02 23:44:20.001 INFO 10852 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test...
前面说了cron的值可以通过外部配置文件的形式指定,如下:
package com.nobody.task;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;/** * @Description 定时任务类 * @Author Mr.nobody * @Date 2021/3/2 * @Version 1.0.0 */@Componentpublic class ScheduledTask { private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledTask.class);// 每5秒执行一次 @Scheduled(cron = "${cron.exp}") public void test() { LOGGER.info(">>> ScheduledTask test... "); }}
然后在配置文件application.properties中填写配置变量的值,此种方式比较灵活,不用修改代码即可更改时间。而且如果将值改为"-",代表定时任务无效。
cron.exp=0/5 * * * * ? #cron.exp=-
3.2 fixedDelay
long fixedDelay() default -1;
此属性表明,从上次定时任务执行完后,延迟多久再次执行定时任务。以毫秒为单位。
package com.nobody.task;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;/** * @Description 定时任务类 * @Author Mr.nobody * @Date 2021/3/2 * @Version 1.0.0 */@Componentpublic class ScheduledTask { private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledTask.class); // 延迟1秒@Scheduled(fixedDelay = 1000) public void test() { try { // 休眠2秒 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } LOGGER.info(">>> ScheduledTask test... "); }}
输出结果如下,刚好两次执行时间间隔3秒(2秒休眠+1秒延迟)。
2021-03-03 00:03:44.025 INFO 15612 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:03:47.027 INFO 15612 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:03:50.029 INFO 15612 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:03:53.031 INFO 15612 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test...
3.3 fixedDelayString
String fixedDelayString() default ""
此属性表明,从上次定时任务执行完后,延迟多久再次执行定时任务。以毫秒为单位。与fixedDelay作用相同,只不过值是字符串的形式。但是它支持占位符。
package com.nobody.task;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;/** * @Description 定时任务类 * @Author Mr.nobody * @Date 2021/3/2 * @Version 1.0.0 */@Componentpublic class ScheduledTask { private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledTask.class); // 延迟1秒@Scheduled(fixedDelayString = "1000") public void test() { try { // 休眠2秒 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } LOGGER.info(">>> ScheduledTask test... "); }}
输出结果如下,刚好两次执行时间间隔3秒(2秒休眠+1秒延迟)。
2021-03-03 00:09:58.234 INFO 9644 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:10:01.238 INFO 9644 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:10:04.262 INFO 9644 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:10:07.340 INFO 9644 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test...
使用占位符如下所示,并且在配置文件application.properties中指定配置变量的值。
@Scheduled(fixedDelayString = "${fixed.delay}")
fixed.delay=1000
3.4 fixedRate
long fixedRate() default -1;
此属性表明,两次定时任务调用之间间隔的毫秒数。即上一个调用开始后再次调用的延迟时间(不用等上一次调用完成)。
但是默认情况下是使用单线程是来执行所有定时任务的,所以即使前一个调用还未执行完,下一个调用已经开始了,那它也得等上一个调用执行完了,才能执行下一个。
@Scheduled(fixedRate = 1000)public void test() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } LOGGER.info(">>> ScheduledTask test... ");}
上述两次定时任务调用之间间隔为1秒,但是执行时间为5秒,但是发现它们间隔执行时间还是5秒,而且打印出的都是同一个线程名TaskScheduler-1,证明了默认情况下确实如此。
2021-03-03 00:20:35.307 INFO 16152 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:20:40.309 INFO 16152 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:20:45.309 INFO 16152 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:20:50.310 INFO 16152 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test...
但是我们可以自定义线程池,然后通过@Async注解使用自定义的线程池异步执行,这样就能达到多线程执行。但是如果是定时任务是执行相同业务操作,例如计算用户的积分数,可能会出现并发操作的问题,所以不建议使用。但如果执行时间小于两次调度的时间间隔还是可以考虑使用的。
@Scheduled(fixedRate = 1000)@Async("myExecutor")public void test() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } LOGGER.info(">>> ScheduledTask test... ");}
线程池配置类代码如下:
package com.nobody.config;import java.util.concurrent.Executor;import java.util.concurrent.ThreadPoolExecutor;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;@Configurationpublic class ExecutorConfig { public static final int CORE_POOL_SIZE = 5; public static final int MAX_POOL_SIZE = 15; public static final int QUEUE_CAPACITY = 100; @Bean("myExecutor") public Executor asyncServiceExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 核心线程数大小 executor.setCorePoolSize(CORE_POOL_SIZE); // 最大线程数大小 executor.setMaxPoolSize(MAX_POOL_SIZE); // 阻塞队列容量 executor.setQueueCapacity(QUEUE_CAPACITY); // 线程名前缀 executor.setThreadNamePrefix("myTask-"); // rejectionPolicy:当queue达到maxSize并且此时maxPoolSize也达到最大值的时候,对于新任务的处理策略 // CallerRunsPolicy:不在新线程中执行任务,而是交由调用者所在的线程来执行 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; }}
需要添加@EnableAsync注解激活。
package com.nobody;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.scheduling.annotation.EnableAsync;import org.springframework.scheduling.annotation.EnableScheduling;@SpringBootApplication@EnableScheduling@EnableAsyncpublic class ScheduledTaskApplication { public static void main(String[] args) { SpringApplication.run(ScheduledTaskApplication.class, args); }}
最终输出结果如下,发现间隔不是5秒,而是1秒了,而且不是单线程执行定时任务,是通过配置的线程池来执行的。
2021-03-03 00:36:41.010 INFO 5752 --- [ myTask-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:36:41.993 INFO 5752 --- [ myTask-2] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:36:42.998 INFO 5752 --- [ myTask-3] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:36:43.991 INFO 5752 --- [ myTask-4] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:36:44.993 INFO 5752 --- [ myTask-5] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:36:46.013 INFO 5752 --- [ myTask-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:36:47.023 INFO 5752 --- [ myTask-2] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:36:47.999 INFO 5752 --- [ myTask-3] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:36:48.992 INFO 5752 --- [ myTask-4] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:36:50.020 INFO 5752 --- [ myTask-5] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:36:51.013 INFO 5752 --- [ myTask-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:36:52.025 INFO 5752 --- [ myTask-2] com.nobody.task.ScheduledTask : >>> ScheduledTask test...
3.5 fixedRateString
String fixedRateString() default "";
此属性表明,两次定时任务调用之间间隔的毫秒数。即上一个调用开始后再次调用的延迟时间(不用等上一次调用完成)。与fixedRate相同,只不过值是字符串的形式。但是它支持占位符。
3.6 initialDelay 和 initialDelayString
long initialDelay() default -1;
initialDelay此属性表明,第一次执行fixedRate或fixedDelay任务之前要延迟的毫秒数。需配合fixedDelay或者fixedRate一起使用。而initialDelayString是字符串的形式,并且支持占位符。
// 延迟3秒才开始执行第一次任务@Scheduled(fixedDelayString = "1000", initialDelay = 3000)public void test() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } LOGGER.info(">>> ScheduledTask test... ");}
3.7 zone
String zone() default "";
时区,cron表达式会基于该时区解析。默认是一个空字符串,即取服务器所在地的时区。它的值是一个时区ID,我们一般使用的时区是Asia/Shanghai。此属性一般默认即可。
@Scheduled(cron = "0/5 * * * * ?", zone = "Asia/Shanghai")public void test() { TimeZone defaultTimeZone = TimeZone.getDefault(); LOGGER.info(">>> ScheduledTask test... " + defaultTimeZone.getID()); // 打印出可取得的所有时区ID String[] availableIDs = TimeZone.getAvailableIDs(); System.out.println(Arrays.toString(availableIDs)); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }}
四、可更改时间的定时任务
此种方式要实现SchedulingConfigurer接口,并且重写configureTasks方法。
package com.nobody.task;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.scheduling.Trigger;import org.springframework.scheduling.annotation.SchedulingConfigurer;import org.springframework.scheduling.config.ScheduledTaskRegistrar;import org.springframework.scheduling.support.CronTrigger;import org.springframework.stereotype.Component;/** * @Description 可动态更改时间的定时任务 * @Author Mr.nobody * @Date 2021/3/2 * @Version 1.0.0 */@Componentpublic class ChangeTimeScheduledTask implements SchedulingConfigurer { private static final Logger LOGGER = LoggerFactory.getLogger(ChangeTimeScheduledTask.class); // cron表达式,我们动态更改此属性的值即可更改定时任务的执行时间 private String expression = "0/5 * * * * *"; @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { // 定时任务要执行的方法 Runnable task = () -> LOGGER.info(">>> configureTasks ..."); // 调度实现的时间控制 Trigger trigger = triggerContext -> { CronTrigger cronTrigger = new CronTrigger(expression); return cronTrigger.nextExecutionTime(triggerContext); }; taskRegistrar.addTriggerTask(task, trigger); } public String getExpression() { return expression; } public void setExpression(String expression) { this.expression = expression; }}
然后我们编写一个接口进行调用,动态改变定时任务的时间。
package com.nobody.controller;import com.nobody.task.ChangeTimeScheduledTask;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/** * @Description * @Author Mr.nobody * @Date 2021/3/2 * @Version 1.0.0 */@RestController@RequestMapping("demo")public class DemoController { private ChangeTimeScheduledTask changeTimeScheduledTask; public DemoController(final ChangeTimeScheduledTask changeTimeScheduledTask) { this.changeTimeScheduledTask = changeTimeScheduledTask; } @GetMapping public String testChangeTimeScheduledTask() { changeTimeScheduledTask.setExpression("0/10 * * * * *"); return "ok"; }}
启动服务,没调用接口之前,定时任务是每5秒执行一次。
2021-03-03 13:56:20.001 INFO 6836 --- [TaskScheduler-1] com.nobody.task.ChangeTimeScheduledTask : >>> configureTasks ...2021-03-03 13:56:25.001 INFO 6836 --- [TaskScheduler-1] com.nobody.task.ChangeTimeScheduledTask : >>> configureTasks ...2021-03-03 13:56:30.002 INFO 6836 --- [TaskScheduler-1] com.nobody.task.ChangeTimeScheduledTask : >>> configureTasks ...2021-03-03 13:56:35.001 INFO 6836 --- [TaskScheduler-1] com.nobody.task.ChangeTimeScheduledTask : >>> configureTasks ...
然后我们调用接口,改变定时任务的时间,结果变为每10秒执行一次。
2021-03-03 13:56:40.005 INFO 6836 --- [TaskScheduler-1] com.nobody.task.ChangeTimeScheduledTask : >>> configureTasks ...2021-03-03 13:56:50.002 INFO 6836 --- [TaskScheduler-1] com.nobody.task.ChangeTimeScheduledTask : >>> configureTasks ...2021-03-03 13:57:00.001 INFO 6836 --- [TaskScheduler-1] com.nobody.task.ChangeTimeScheduledTask : >>> configureTasks ...
五、可启动停止改变定时任务
此种方式可以手动启动,停止定时任务,以及能更改定时任务的执行时间。
其原理是利用线程池实现任务调度,可以实现任务的调度和删除。借助ThreadPoolTaskScheduler线程池任务调度器,能够开启线程池进行任务调度。通过ThreadPoolTaskScheduler的schedule方法创建一个定时计划ScheduleFuture,ScheduleFuture中有一个cancel方法可以停止定时任务。schedule方法中有2个参数,一个是Runnable task,线程接口类,即我们要定时执行的方法,另一个参数是Trigger trigger,定时任务触发器,带有cron值。
package com.nobody.task;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;import org.springframework.scheduling.support.CronTrigger;import org.springframework.stereotype.Component;import java.util.concurrent.ScheduledFuture;/** * @Description * @Author Mr.nobody * @Date 2021/3/2 * @Version 1.0.0 */@Componentpublic class DynamicScheduledTask { private static final Logger LOGGER = LoggerFactory.getLogger(DynamicScheduledTask.class); private ThreadPoolTaskScheduler threadPoolTaskScheduler; public DynamicScheduledTask(final ThreadPoolTaskScheduler threadPoolTaskScheduler) { this.threadPoolTaskScheduler = threadPoolTaskScheduler; } private ScheduledFuture future; /** * 启动定时器 */ public void startTask() { // 第一个参数为定时任务要执行的方法,第二个参数为定时任务执行的时间 future = threadPoolTaskScheduler.schedule(this::test, new CronTrigger("0/5 * * * * *")); } /** * 停止定时器 */ public void endTask() { if (future != null) { future.cancel(true); } } /** * 改变调度的时间,先停止定时器再启动新的定时器 */ public void changeTask() { // 停止定时器 endTask(); // 定义新的执行时间,并启动 future = threadPoolTaskScheduler.schedule(this::test, new CronTrigger("0/10 * * * * *")); } /** * 定时任务执行的方法 */ public void test() { LOGGER.info(">>> DynamicScheduledTask ..."); }}
我们需要创建ThreadPoolTaskScheduler实例,并交给Spring容器管理。
package com.nobody.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;/** * @Description * @Author Mr.nobody * @Date 2021/3/2 * @Version 1.0.0 */@Configurationpublic class ThreadPoolTaskSchedulerConfig { @Bean public ThreadPoolTaskScheduler threadPoolTaskScheduler() { return new ThreadPoolTaskScheduler(); }}
最后编写接口,对启动,停止,更改时间进行调用即可。
package com.nobody.controller;import com.nobody.task.DynamicScheduledTask;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/** * @Description * @Author Mr.nobody * @Date 2021/3/2 * @Version 1.0.0 */@RestController@RequestMapping("demo")public class DemoController { private DynamicScheduledTask dynamicScheduledTask; public DemoController(final DynamicScheduledTask dynamicScheduledTask) { this.dynamicScheduledTask = dynamicScheduledTask; } @GetMapping("startDynamicScheduledTask") public String startDynamicScheduledTask() { dynamicScheduledTask.startTask(); return "ok"; } @GetMapping("endDynamicScheduledTask") public String endDynamicScheduledTask() { dynamicScheduledTask.endTask(); return "ok"; } @GetMapping("changeDynamicScheduledTask") public String changeDynamicScheduledTask() { dynamicScheduledTask.changeTask(); return "ok"; }}
启动服务,因为没有调用启动定时器接口,所以定时任务不会执行。只有调用了启动的接口,定时任务才开始执行。在服务运行期间,可任意进行定时任务的开启,停止和更改时间操作。
2021-03-03 14:11:35.000 INFO 8548 --- [TaskScheduler-1] com.nobody.task.DynamicScheduledTask : >>> DynamicScheduledTask ...2021-03-03 14:11:40.002 INFO 8548 --- [TaskScheduler-1] com.nobody.task.DynamicScheduledTask : >>> DynamicScheduledTask ...2021-03-03 14:11:45.001 INFO 8548 --- [TaskScheduler-1] com.nobody.task.DynamicScheduledTask : >>> DynamicScheduledTask ...2021-03-03 14:11:50.001 INFO 8548 --- [TaskScheduler-1] com.nobody.task.DynamicScheduledTask : >>> DynamicScheduledTask ...2021-03-03 14:11:55.002 INFO 8548 --- [TaskScheduler-1] com.nobody.task.DynamicScheduledTask : >>> DynamicScheduledTask ...// 以下是更改了执行时间为10秒2021-03-03 14:12:00.001 INFO 8548 --- [TaskScheduler-1] com.nobody.task.DynamicScheduledTask : >>> DynamicScheduledTask ...2021-03-03 14:12:10.001 INFO 8548 --- [TaskScheduler-1] com.nobody.task.DynamicScheduledTask : >>> DynamicScheduledTask ...2021-03-03 14:12:20.001 INFO 8548 --- [TaskScheduler-1] com.nobody.task.DynamicScheduledTask : >>> DynamicScheduledTask ...
六、分布式集群注意事项
虽然Scheduled Task是一种轻量级的任务定时调度器,相比于Quartz减少了很多的配置信息。但是Scheduled Task 的有个缺点是不适用于分布式集群的操作,因为集群的节点之间是不会共享任务信息的,会导致在多个服务器上执行相同重复的定时任务。
如果在多个服务器上执行相同的定时任务,对你的业务不影响那还好。但有些业务不允许重复执行,那我们其实可以通过分布式锁,只让其中一个拿到锁的节点来执行定时任务。
@Scheduled(cron = "${cron.exp}")public void test() { String lockKey = RedisKeyUtil.genKey(RedisKeyUtil.SCHEDULED_TASK_LOCK); boolean lockSuccess = redisUtils.getLock(lockKey, "1", 30000); if (!lockSuccess) { LOGGER.warn(">>> Scheduled is running on another server..."); return; } try { // doSomething(); } finally { redisUtils.releaseLock(lockKey, "1"); }}
此演示项目已上传到Github,如有需要可自行下载,欢迎 Star 。