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并发执行多个任务配置

 

posted @ 2024-01-15 10:21  雨后观山色  阅读(901)  评论(0编辑  收藏  举报