Quartz任务调度框架

Quartz任务调度框架

Quartz是一个任务调度框架,用于定时执行任务。

任务调度:系统中有N的任务,分别需要在不同的时刻执行这些任务,这种多任务的执行策略就是任务调度

0 定时任务实现的方法

  1. spring schedule
    • 优点:无需整合spring,作业类中就可以调用业务service
    • 缺点:默认单线程执行任务,当前一个任务执行时间过长时,会使后面的任务延期执行,如果前一个任务的执行时间过长,可能会导致后面的任务执行时间不准确,最差的情况可能会丢任务;不能做数据存储型的定时任务;如果希望并发运行,需要配置线程池
  2. spring-quartz
    • 优点:支持持久化
    • 缺点:配置稍显复杂

定时任务持久化:当前定时任务保存在内存中,每当项目重启的时候,定时任务就会清空并重写加载,原来的一些执行信息就丢失了,我们需要在保存下定时任务的执行信息,下次重启项目的时候我们的定时任务就可以根据上次执行的状态再执行下一次的定时任务。

1.Spring schedule测试

使用默认的单线程

启动类 App.java

package org.example;


import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@Slf4j
@SpringBootApplication
@EnableScheduling  // 启动 spring task
public class App
{
    public static void main ( String[] args )  {
        SpringApplication.run(App.class,args);
    }
}

TaskComponent.java

package org.example.component;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class TaskComponent {
    
    // 定时任务1 每1秒执行一次,执行时间为10秒
    @Scheduled(cron = "*/1 * * * * ?")
    public void task1() {
        log.info("task1-------当先线程的名字={}, id={}", Thread.currentThread().getName(),Thread.currentThread().getId());
        try {
            Thread.sleep(1000 * 10);
            // int s = 2/0; 				// 模拟任务执行中抛出异常
        } catch (InterruptedException e) {
            log.error("Thread sleep error {}", e.getMessage());
            Thread.currentThread().interrupt();
        }
    }
    
    // 定时任务2 每2秒执行一次
    @Scheduled(cron = "*/2 * * * * ?")
    public void task2() {
        log.info("task2-------当先线程的名字={}, id={}", Thread.currentThread().getName(),Thread.currentThread().getId());
    }
}

运行启动类,观察输出

2023-05-31 20:57:44.009  INFO 44860 --- [   scheduling-1] org.example.component.TaskComponent      : task2-------当先线程的名字=scheduling-1, id=36
2023-05-31 20:57:44.009  INFO 44860 --- [   scheduling-1] org.example.component.TaskComponent      : task1-------当先线程的名字=scheduling-1, id=36
2023-05-31 20:57:54.024  INFO 44860 --- [   scheduling-1] org.example.component.TaskComponent      : task2-------当先线程的名字=scheduling-1, id=36
2023-05-31 20:57:55.016  INFO 44860 --- [   scheduling-1] org.example.component.TaskComponent      : task1-------当先线程的名字=scheduling-1, id=36
2023-05-31 20:58:05.027  INFO 44860 --- [   scheduling-1] org.example.component.TaskComponent      : task2-------当先线程的名字=scheduling-1, id=36
2023-05-31 20:58:06.002  INFO 44860 --- [   scheduling-1] org.example.component.TaskComponent      : task1-------当先线程的名字=scheduling-1, id=36

task2的执行时间为44秒,54秒,05秒

可以看到task2由于task1任务的阻塞,导致task2不能按照预想的每2秒执行一次。

用的时候注意不要将定时任务的时间设置的过于接近。

使用多线程

需要做的事情:

  1. 启动类上加上 @EnableAsync
  2. task 上加上 @Async

运行启动类,观察输出

2023-05-31 21:10:37.025  INFO 28216 --- [         task-1] org.example.component.TaskComponent      : task1-------当先线程的名字=task-1, id=39
2023-05-31 21:10:38.007  INFO 28216 --- [         task-2] org.example.component.TaskComponent      : task1-------当先线程的名字=task-2, id=42
2023-05-31 21:10:38.008  INFO 28216 --- [         task-3] org.example.component.TaskComponent      : task2-------当先线程的名字=task-3, id=43
2023-05-31 21:10:39.005  INFO 28216 --- [         task-4] org.example.component.TaskComponent      : task1-------当先线程的名字=task-4, id=46
2023-05-31 21:10:40.002  INFO 28216 --- [         task-5] org.example.component.TaskComponent      : task2-------当先线程的名字=task-5, id=48
2023-05-31 21:10:40.002  INFO 28216 --- [         task-6] org.example.component.TaskComponent      : task1-------当先线程的名字=task-6, id=49
2023-05-31 21:10:41.014  INFO 28216 --- [         task-7] org.example.component.TaskComponent      : task1-------当先线程的名字=task-7, id=52
2023-05-31 21:10:42.014  INFO 28216 --- [         task-3] org.example.component.TaskComponent      : task1-------当先线程的名字=task-3, id=43
2023-05-31 21:10:42.014  INFO 28216 --- [         task-8] org.example.component.TaskComponent      : task2-------当先线程的名字=task-8, id=55
2023-05-31 21:10:43.015  INFO 28216 --- [         task-5] org.example.component.TaskComponent      : task1-------当先线程的名字=task-5, id=48
2023-05-31 21:10:44.009  INFO 28216 --- [         task-8] org.example.component.TaskComponent      : task2-------当先线程的名字=task-8, id=55

可以看到两个任务的执行时间互不影响,并且线程id也不相同,说明是多线程执行任务。

但是,这种方式,会引发另外的问题,task1会多次执行。我们希望上一个task1未执行完成的时候,下一个task1不执行。

使用线程池

task类注释掉 @Async,并且配置如下的配置类

package org.example.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

@Configuration
@Slf4j
public class TaskConfig implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        // 为springtask配置线程池
        taskRegistrar.setScheduler(taskExecutor());
    }
    // 创建线程池
    @Bean
    public Executor taskExecutor(){
        return Executors.newScheduledThreadPool(10);
    }
}

2023-05-31 21:24:18.007  INFO 29676 --- [pool-1-thread-1] org.example.component.TaskComponent      : task2-------当先线程的名字=pool-1-thread-1, id=36
2023-05-31 21:24:18.007  INFO 29676 --- [pool-1-thread-1] org.example.component.TaskComponent      : task2 执行完了,线程的名字=pool-1-thread-1, id=36
2023-05-31 21:24:18.008  INFO 29676 --- [pool-1-thread-2] org.example.component.TaskComponent      : task1-------当先线程的名字=pool-1-thread-2, id=37
2023-05-31 21:24:20.002  INFO 29676 --- [pool-1-thread-1] org.example.component.TaskComponent      : task2-------当先线程的名字=pool-1-thread-1, id=36
2023-05-31 21:24:20.002  INFO 29676 --- [pool-1-thread-1] org.example.component.TaskComponent      : task2 执行完了,线程的名字=pool-1-thread-1, id=36
2023-05-31 21:24:22.009  INFO 29676 --- [pool-1-thread-3] org.example.component.TaskComponent      : task2-------当先线程的名字=pool-1-thread-3, id=39
2023-05-31 21:24:22.009  INFO 29676 --- [pool-1-thread-3] org.example.component.TaskComponent      : task2 执行完了,线程的名字=pool-1-thread-3, id=39
2023-05-31 21:24:24.006  INFO 29676 --- [pool-1-thread-1] org.example.component.TaskComponent      : task2-------当先线程的名字=pool-1-thread-1, id=36
2023-05-31 21:24:24.006  INFO 29676 --- [pool-1-thread-1] org.example.component.TaskComponent      : task2 执行完了,线程的名字=pool-1-thread-1, id=36
2023-05-31 21:24:26.006  INFO 29676 --- [pool-1-thread-4] org.example.component.TaskComponent      : task2-------当先线程的名字=pool-1-thread-4, id=44
2023-05-31 21:24:26.006  INFO 29676 --- [pool-1-thread-4] org.example.component.TaskComponent      : task2 执行完了,线程的名字=pool-1-thread-4, id=44
2023-05-31 21:24:28.015  INFO 29676 --- [pool-1-thread-3] org.example.component.TaskComponent      : task2-------当先线程的名字=pool-1-thread-3, id=39
2023-05-31 21:24:28.015  INFO 29676 --- [pool-1-thread-2] org.example.component.TaskComponent      : task1 执行完了,线程的名字=pool-1-thread-2, id=37
2023-05-31 21:24:28.015  INFO 29676 --- [pool-1-thread-3] org.example.component.TaskComponent      : task2 执行完了,线程的名字=pool-1-thread-3, id=39
2023-05-31 21:24:29.011  INFO 29676 --- [pool-1-thread-5] org.example.component.TaskComponent      : task1-------当先线程的名字=pool-1-thread-5, id=49
2023-05-31 21:24:30.010  INFO 29676 --- [pool-1-thread-1] org.example.component.TaskComponent      : task2-------当先线程的名字=pool-1-thread-1, id=36
2023-05-31 21:24:30.010  INFO 29676 --- [pool-1-thread-1] org.example.component.TaskComponent      : task2 执行完了,线程的名字=pool-1-thread-1, id=36
2023-05-31 21:24:32.008  INFO 29676 --- [pool-1-thread-6] org.example.component.TaskComponent      : task2-------当先线程的名字=pool-1-thread-6, id=54
2023-05-31 21:24:32.008  INFO 29676 --- [pool-1-thread-6] org.example.component.TaskComponent      : task2 执行完了,线程的名字=pool-1-thread-6, id=54
2023-05-31 21:24:34.001  INFO 29676 --- [pool-1-thread-6] org.example.component.TaskComponent      : task2-------当先线程的名字=pool-1-thread-6, id=54
2023-05-31 21:24:34.001  INFO 29676 --- [pool-1-thread-6] org.example.component.TaskComponent      : task2 执行完了,线程的名字=pool-1-thread-6, id=54

观察日志输出,可以看出task1是上一个执行结束之后,下一个task1才会继续执行

2.Spring-quartz框架

导入依赖

<!--    quartz 定时调度框架 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>

QuartzTask1.java

package org.example.component;

import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@DisallowConcurrentExecution
public class QuartzTask2  extends QuartzJobBean {
    @Override
    protected  void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        log.info("task2任务执行开始,线程id={}", Thread.currentThread().getId());

        log.info("task2任务执行结束,线程id={}", Thread.currentThread().getId());
    }
}

QuartzTask2.java

package org.example.component;

import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@DisallowConcurrentExecution
public class QuartzTask2  extends QuartzJobBean {
    @Override
    protected  void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        log.info("task2任务执行开始,线程id={}", Thread.currentThread().getId());

        log.info("task2任务执行结束,线程id={}", Thread.currentThread().getId());
    }
}

QuartzConfig.java

package org.example.config;

import lombok.extern.slf4j.Slf4j;
import org.example.component.QuartzTask1;
import org.example.component.QuartzTask2;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Slf4j
@Configuration
public class QuartzConfig {
    @Bean
    public JobDetail task1_job() {
        // JobDetail 需要加入 storeDurably() 不然会报错
        return JobBuilder.newJob(QuartzTask1.class).storeDurably().build();
    }
    @Bean
    public JobDetail task2_job() {
        return JobBuilder.newJob(QuartzTask2.class).storeDurably().build();
    }

    @Bean
    public Trigger task1_trigger() {
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("*/1 * * * * ?");
        return TriggerBuilder.newTrigger()
                .forJob(task1_job())
                .withIdentity("task1")
                .withSchedule(scheduleBuilder)
                .build();
    }

    @Bean
    public Trigger task2_trigger() {
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("*/2 * * * * ?");
        return TriggerBuilder.newTrigger()
                .forJob(task2_job())
                .withIdentity("task2")
                .withSchedule(scheduleBuilder)
                .build();
    }
}
2023-05-31 21:53:42.012  INFO 50100 --- [eduler_Worker-2] org.example.component.QuartzTask2        : task2任务执行开始,线程id=25
2023-05-31 21:53:42.012  INFO 50100 --- [eduler_Worker-2] org.example.component.QuartzTask2        : task2任务执行结束,线程id=25
2023-05-31 21:53:44.013  INFO 50100 --- [eduler_Worker-3] org.example.component.QuartzTask2        : task2任务执行开始,线程id=26
2023-05-31 21:53:44.013  INFO 50100 --- [eduler_Worker-3] org.example.component.QuartzTask2        : task2任务执行结束,线程id=26
2023-05-31 21:53:46.010  INFO 50100 --- [eduler_Worker-4] org.example.component.QuartzTask2        : task2任务执行开始,线程id=27
2023-05-31 21:53:46.010  INFO 50100 --- [eduler_Worker-4] org.example.component.QuartzTask2        : task2任务执行结束,线程id=27
2023-05-31 21:53:48.004  INFO 50100 --- [eduler_Worker-5] org.example.component.QuartzTask2        : task2任务执行开始,线程id=28
2023-05-31 21:53:48.004  INFO 50100 --- [eduler_Worker-5] org.example.component.QuartzTask2        : task2任务执行结束,线程id=28
2023-05-31 21:53:50.003  INFO 50100 --- [eduler_Worker-6] org.example.component.QuartzTask2        : task2任务执行开始,线程id=29
2023-05-31 21:53:50.003  INFO 50100 --- [eduler_Worker-6] org.example.component.QuartzTask2        : task2任务执行结束,线程id=29
2023-05-31 21:53:51.823  INFO 50100 --- [eduler_Worker-1] org.example.component.QuartzTask1        : task1任务执行结束,线程id=24
2023-05-31 21:53:51.824  INFO 50100 --- [eduler_Worker-7] org.example.component.QuartzTask1        : task1任务执行开始,线程id=30
2023-05-31 21:53:52.017  INFO 50100 --- [eduler_Worker-8] org.example.component.QuartzTask2        : task2任务执行开始,线程id=31
2023-05-31 21:53:52.017  INFO 50100 --- [eduler_Worker-8] org.example.component.QuartzTask2        : task2任务执行结束,线程id=31
2023-05-31 21:53:54.008  INFO 50100 --- [eduler_Worker-9] org.example.component.QuartzTask2        : task2任务执行开始,线程id=32
2023-05-31 21:53:54.008  INFO 50100 --- [eduler_Worker-9] org.example.component.QuartzTask2        : task2任务执行结束,线程id=32
2023-05-31 21:53:56.012  INFO 50100 --- [duler_Worker-10] org.example.component.QuartzTask2        : task2任务执行开始,线程id=33
2023-05-31 21:53:56.012  INFO 50100 --- [duler_Worker-10] org.example.component.QuartzTask2        : task2任务执行结束,线程id=33
2023-05-31 21:53:58.009  INFO 50100 --- [eduler_Worker-2] org.example.component.QuartzTask2        : task2任务执行开始,线程id=25
2023-05-31 21:53:58.010  INFO 50100 --- [eduler_Worker-2] org.example.component.QuartzTask2        : task2任务执行结束,线程id=25
2023-05-31 21:54:00.009  INFO 50100 --- [eduler_Worker-3] org.example.component.QuartzTask2        : task2任务执行开始,线程id=26
2023-05-31 21:54:00.009  INFO 50100 --- [eduler_Worker-3] org.example.component.QuartzTask2        : task2任务执行结束,线程id=26
2023-05-31 21:54:01.826  INFO 50100 --- [eduler_Worker-7] org.example.component.QuartzTask1        : task1任务执行结束,线程id=30
2023-05-31 21:54:01.827  INFO 50100 --- [eduler_Worker-4] org.example.component.QuartzTask1        : task1任务执行开始,线程id=27
2023-05-31 21:54:02.002  INFO 50100 --- [eduler_Worker-5] org.example.component.QuartzTask2        : task2任务执行开始,线程id=28
2023-05-31 21:54:02.003  INFO 50100 --- [eduler_Worker-5] org.example.component.QuartzTask2        : task2任务执行结束,线程id=28
2023-05-31 21:54:04.011  INFO 50100 --- [eduler_Worker-6] org.example.component.QuartzTask2        : task2任务执行开始,线程id=29
2023-05-31 21:54:04.011  INFO 50100 --- [eduler_Worker-6] org.example.component.QuartzTask2        : task2任务执行结束,线程id=29
2023-05-31 21:54:06.004  INFO 50100 --- [eduler_Worker-1] org.example.component.QuartzTask2        : task2任务执行开始,线程id=24
2023-05-31 21:54:06.004  INFO 50100 --- [eduler_Worker-1] org.example.component.QuartzTask2        : task2任务执行结束,线程id=24
2023-05-31 21:54:08.012  INFO 50100 --- [eduler_Worker-8] org.example.component.QuartzTask2        : task2任务执行开始,线程id=31
2023-05-31 21:54:08.012  INFO 50100 --- [eduler_Worker-8] org.example.component.QuartzTask2        : task2任务执行结束,线程id=31
2023-05-31 21:54:10.011  INFO 50100 --- [eduler_Worker-9] org.example.component.QuartzTask2        : task2任务执行开始,线程id=32
2023-05-31 21:54:10.012  INFO 50100 --- [eduler_Worker-9] org.example.component.QuartzTask2        : task2任务执行结束,线程id=32
2023-05-31 21:54:11.828  INFO 50100 --- [eduler_Worker-4] org.example.component.QuartzTask1        : task1任务执行结束,线程id=27
2023-05-31 21:54:11.828  INFO 50100 --- [duler_Worker-10] org.example.component.QuartzTask1        : task1任务执行开始,线程id=33

通过日志输出,可以看到,quartz达到了,springtask使用线程池的效果。

单个任务的串行执行,使用的注解 @DisallowConcurrentExecution 不允许并行执行

quartz的核心一共有三个类

  • 任务 Job :需要执行的任务逻辑类,需要实现 Job 接口的 execute 方法
  • 触发器 Trigger :执行任务的调度器。例如,每天的00:00执行任务A,那么就需要设置Trigger的属性
  • 调度器 Scheduler : 任务的调度器,将任务 Job和触发器Trigger整合起来,负责基于Trigger设定的时间执行任务Job
posted @ 2023-05-31 22:11  永恒&  阅读(105)  评论(0编辑  收藏  举报