Springcloud学习笔记59--SpringBoot 基于@Scheduled注解的定时任务
1 @Scheduled 基础使用
1.1 同一任务的同步执行(下次任务执行将在本次任务执行完毕后的下一次配置时间开始)
(1) 首先,要想使用@Scheduled注解,首先要在启动类上添加注解@EnableScheduling,开启定时任务;重点,不加@EnableScheduling,定时任务将无法执行;
package com.example.task; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableScheduling public class TaskApplication { public static void main(String[] args) { SpringApplication.run(TaskApplication.class, args); } }
(2) 使用@Component和@Scheduled(cron=“0/5 * * * * ?”) , 启动定时任务。
SpringBoot 默认就是定时任务同步执行的,只要将@Scheduled添加到需要配置的任务方法上,下次任务执行开始将在本次任务执行完毕后才开始;
package com.example.task.timing; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @Component public class TimingTask { /** * 每五秒执行一次 */ @Scheduled(cron="0/5 * * * * ?") public void executeFileDownLoadTask() { System.out.println("定时任务启动"); } }
补充说明一下,cron表达式;具体见:https://www.cnblogs.com/luckyplj/p/15608853.html
cron表达式从左到右(用空格隔开):秒 分 小时 日期 月份 星期 年份;其中第七域年可不填;
1.2 同一任务的异步执行(下次任务将在下一个配置时间开始,不等待当前任务执行完毕)
需要在方法体上添加@Async注解;
spring为了简化程序员的代码,内置了@Async注解,程序员只需编写纯业务代码和设置自己的线程池即可。
基于@Async标注的⽅法,称之为异步⽅法;这些⽅法将在执⾏的时候,将会在独⽴的线程中被执⾏,调⽤者⽆需等待它的完成,即可继续其他的操作。
@Async @Scheduled(cron = "*/30 * * * * ?") public void ipWriter() throws InterruptedException { for(int i=0;i<20;i++){ System.out.println("1:"+i); Thread.sleep(5000); } }
1.3 多任务并发执行(划重点,这是一个坑)
@Scheduled并发执行多个任务配置的解决方案:
默认所有的@Scheduled方法由单线程调度,没有同时执行的任务。例如:方法a和b,a的执行卡住了,即使时间到了b也不会执行,也是串行
我们只需要自定义一个TaskScheduler,设置线程数即可。这里使用ThreadPoolTaskScheduler ,ThreadPoolTaskScheduler 的默认线程数也是1。
我在使用SpringBoot配置定时任务的过程中,使用@Scheduled配置了多个定时任务,但是在项目启动的时候每次只会启动一个定时任务,只好搜索一波,直到看到了 ThreadPoolTaskScheduler的源码,才发现默认开启的线程数是 1 ,怪不得每次只能执行一个定时任务,以下是部分源码
public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport implements AsyncListenableTaskExecutor, SchedulingTaskExecutor, TaskScheduler { private volatile int poolSize = 1; public void setPoolSize(int poolSize) { Assert.isTrue(poolSize > 0, "'poolSize' must be 1 or higher"); this.poolSize = poolSize; if (this.scheduledExecutor instanceof ScheduledThreadPoolExecutor) { ((ScheduledThreadPoolExecutor)this.scheduledExecutor).setCorePoolSize(poolSize); } } }
可以看到poolSize的默认值是1,那现在就好办了,在启动的时候重新配置一番即可
创建BeanConfig类,注意,需要在类上添加@Component注解,项目启动的时候类中的@Bean注解才会被扫描到,使配置生效
package com.liufei.beanConfig; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.stereotype.Component; /** * @Author DemoLiu * @Date 2018/12/29 10:18 * @description */ @Component public class BeanConfig { @Bean public TaskScheduler taskScheduler() { ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); taskScheduler.setPoolSize(2);//我这里设置的线程数是2,可以根据需求调整 return taskScheduler; } }
2.分布式定时任务锁shedlock
shedlock只做一件事,就是确保计划任务最多同时执行一次;如果正在一个节点上执行任务,它将获取一个锁,以防止从另一个节点(或线程)执行相同任务。请注意,过去一个任务已在一个节点上执行,则其他节点上的执行不会等待,只会跳过它;
目前支持Mongo、JDBC数据库、redis、hazelcast或zookeeper协调的spring scheduled task。
shedlock不是分布式调度框架,它是只是一个锁!!
2.1 @SchedulerLock注解
@SchedulerLock注解支持的五个参数配置:
- name 用来标注一个定时服务的名字,被用于写入数据库作为区分不同服务的标识,如果有多个同名定时任务则同一时间点只有一个执行成功
- lockAtMostFor 成功执行任务的节点所能拥有独占锁的最长时间,单位是毫秒ms
- lockAtMostForString 成功执行任务的节点所能拥有的独占锁的最长时间的字符串表达,例如“PT5M”表示为5分钟
- lockAtLeastFor 成功执行任务的节点所能拥有独占锁的最短时间,单位是毫秒ms
- lockAtLeastForString 成功执行任务的节点所能拥有的独占锁的最短时间的字符串表达,例如“PT5M”表示为5分钟
2.2 实际使用案例
(1) pom依赖文件:
<dependency> <groupId>net.javacrumbs.shedlock</groupId> <artifactId>shedlock-spring</artifactId> <version>2.5.0</version> </dependency> <dependency> <groupId>net.javacrumbs.shedlock</groupId> <artifactId>shedlock-provider-jdbc-template</artifactId> <version>2.5.0</version> </dependency>
(2) 接下来开始写配置类:
实现配置类,以提供LockProvider
@EnableSchedulerLock(defaultLockAtMostFor = "PT5M") @Configuration @EnableScheduling public class ShedlockConfig { @Bean public LockProvider lockProvider(DataSource dataSource) { return new JdbcTemplateLockProvider(dataSource); } @Bean public ScheduledLockConfiguration scheduledLockConfiguration(LockProvider lockProvider) { return ScheduledLockConfigurationBuilder .withLockProvider(lockProvider) .withPoolSize(10) .withDefaultLockAtMostFor(Duration.ofMinutes(10)) .build(); } }
(3) 在数据库里加上创建提供锁的外部存储表(shedlock):
CREATE TABLE shedlock( name VARCHAR(64), lock_until TIMESTAMP(3) NULL, locked_at TIMESTAMP(3) NULL, locked_by VARCHAR(255), PRIMARY KEY (name) )
(4) 实际使用:
@Component public class FileScheduledTask { //每30秒执行一次 @Scheduled(cron = "0/30 * * * * ? ") @SchedulerLock(name = "test", lockAtMostForString = "PT40S", lockAtLeastForString = "PT40S") public void test() { System.out.println("定时任务"); } }
参考文献:
https://blog.csdn.net/qq_45593609/article/details/116128648
https://blog.csdn.net/qq_34279574/article/details/120776854
https://blog.csdn.net/Demo_Liu/article/details/85335414
https://blog.csdn.net/m0_37897396/article/details/81772742 ---shedlock
https://blog.csdn.net/qq_34279574/article/details/120776854 ---shedlock
https://blog.csdn.net/qq_41203483/article/details/121300684 ---@Scheduled并发执行多个任务配置