Quartz任務(wù)調(diào)度框架
Quartz任務(wù)調(diào)度框架
Quartz是一個(gè)任務(wù)調(diào)度框架,用于定時(shí)執(zhí)行任務(wù)。
任務(wù)調(diào)度:系統(tǒng)中有N的任務(wù),分別需要在不同的時(shí)刻執(zhí)行這些任務(wù),這種多任務(wù)的執(zhí)行策略就是任務(wù)調(diào)度
0 定時(shí)任務(wù)實(shí)現(xiàn)的方法
- spring schedule
- 優(yōu)點(diǎn):無需整合spring,作業(yè)類中就可以調(diào)用業(yè)務(wù)service
- 缺點(diǎn):默認(rèn)單線程執(zhí)行任務(wù),當(dāng)前一個(gè)任務(wù)執(zhí)行時(shí)間過長時(shí),會使后面的任務(wù)延期執(zhí)行,如果前一個(gè)任務(wù)的執(zhí)行時(shí)間過長,可能會導(dǎo)致后面的任務(wù)執(zhí)行時(shí)間不準(zhǔn)確,最差的情況可能會丟任務(wù);不能做數(shù)據(jù)存儲型的定時(shí)任務(wù);如果希望并發(fā)運(yùn)行,需要配置線程池
- spring-quartz
- 優(yōu)點(diǎn):支持持久化
- 缺點(diǎn):配置稍顯復(fù)雜
定時(shí)任務(wù)持久化:當(dāng)前定時(shí)任務(wù)保存在內(nèi)存中,每當(dāng)項(xiàng)目重啟的時(shí)候,定時(shí)任務(wù)就會清空并重寫加載,原來的一些執(zhí)行信息就丟失了,我們需要在保存下定時(shí)任務(wù)的執(zhí)行信息,下次重啟項(xiàng)目的時(shí)候我們的定時(shí)任務(wù)就可以根據(jù)上次執(zhí)行的狀態(tài)再執(zhí)行下一次的定時(shí)任務(wù)。
1.Spring schedule測試
使用默認(rèn)的單線程
啟動類 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 {
// 定時(shí)任務(wù)1 每1秒執(zhí)行一次,執(zhí)行時(shí)間為10秒
@Scheduled(cron = "*/1 * * * * ?")
public void task1() {
log.info("task1-------當(dāng)先線程的名字={}, id={}", Thread.currentThread().getName(),Thread.currentThread().getId());
try {
Thread.sleep(1000 * 10);
// int s = 2/0; // 模擬任務(wù)執(zhí)行中拋出異常
} catch (InterruptedException e) {
log.error("Thread sleep error {}", e.getMessage());
Thread.currentThread().interrupt();
}
}
// 定時(shí)任務(wù)2 每2秒執(zhí)行一次
@Scheduled(cron = "*/2 * * * * ?")
public void task2() {
log.info("task2-------當(dāng)先線程的名字={}, id={}", Thread.currentThread().getName(),Thread.currentThread().getId());
}
}
運(yùn)行啟動類,觀察輸出
2023-05-31 20:57:44.009 INFO 44860 --- [ scheduling-1] org.example.component.TaskComponent : task2-------當(dāng)先線程的名字=scheduling-1, id=36
2023-05-31 20:57:44.009 INFO 44860 --- [ scheduling-1] org.example.component.TaskComponent : task1-------當(dāng)先線程的名字=scheduling-1, id=36
2023-05-31 20:57:54.024 INFO 44860 --- [ scheduling-1] org.example.component.TaskComponent : task2-------當(dāng)先線程的名字=scheduling-1, id=36
2023-05-31 20:57:55.016 INFO 44860 --- [ scheduling-1] org.example.component.TaskComponent : task1-------當(dāng)先線程的名字=scheduling-1, id=36
2023-05-31 20:58:05.027 INFO 44860 --- [ scheduling-1] org.example.component.TaskComponent : task2-------當(dāng)先線程的名字=scheduling-1, id=36
2023-05-31 20:58:06.002 INFO 44860 --- [ scheduling-1] org.example.component.TaskComponent : task1-------當(dāng)先線程的名字=scheduling-1, id=36
task2的執(zhí)行時(shí)間為44秒,54秒,05秒
可以看到task2由于task1任務(wù)的阻塞,導(dǎo)致task2不能按照預(yù)想的每2秒執(zhí)行一次。
用的時(shí)候注意不要將定時(shí)任務(wù)的時(shí)間設(shè)置的過于接近。
使用多線程
需要做的事情:
- 啟動類上加上
@EnableAsync - task 上加上
@Async
運(yùn)行啟動類,觀察輸出
2023-05-31 21:10:37.025 INFO 28216 --- [ task-1] org.example.component.TaskComponent : task1-------當(dāng)先線程的名字=task-1, id=39
2023-05-31 21:10:38.007 INFO 28216 --- [ task-2] org.example.component.TaskComponent : task1-------當(dāng)先線程的名字=task-2, id=42
2023-05-31 21:10:38.008 INFO 28216 --- [ task-3] org.example.component.TaskComponent : task2-------當(dāng)先線程的名字=task-3, id=43
2023-05-31 21:10:39.005 INFO 28216 --- [ task-4] org.example.component.TaskComponent : task1-------當(dāng)先線程的名字=task-4, id=46
2023-05-31 21:10:40.002 INFO 28216 --- [ task-5] org.example.component.TaskComponent : task2-------當(dāng)先線程的名字=task-5, id=48
2023-05-31 21:10:40.002 INFO 28216 --- [ task-6] org.example.component.TaskComponent : task1-------當(dāng)先線程的名字=task-6, id=49
2023-05-31 21:10:41.014 INFO 28216 --- [ task-7] org.example.component.TaskComponent : task1-------當(dāng)先線程的名字=task-7, id=52
2023-05-31 21:10:42.014 INFO 28216 --- [ task-3] org.example.component.TaskComponent : task1-------當(dāng)先線程的名字=task-3, id=43
2023-05-31 21:10:42.014 INFO 28216 --- [ task-8] org.example.component.TaskComponent : task2-------當(dāng)先線程的名字=task-8, id=55
2023-05-31 21:10:43.015 INFO 28216 --- [ task-5] org.example.component.TaskComponent : task1-------當(dāng)先線程的名字=task-5, id=48
2023-05-31 21:10:44.009 INFO 28216 --- [ task-8] org.example.component.TaskComponent : task2-------當(dāng)先線程的名字=task-8, id=55
可以看到兩個(gè)任務(wù)的執(zhí)行時(shí)間互不影響,并且線程id也不相同,說明是多線程執(zhí)行任務(wù)。
但是,這種方式,會引發(fā)另外的問題,task1會多次執(zhí)行。我們希望上一個(gè)task1未執(zhí)行完成的時(shí)候,下一個(gè)task1不執(zhí)行。
使用線程池
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());
}
// 創(chuàng)建線程池
@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-------當(dāng)先線程的名字=pool-1-thread-1, id=36
2023-05-31 21:24:18.007 INFO 29676 --- [pool-1-thread-1] org.example.component.TaskComponent : task2 執(zhí)行完了,線程的名字=pool-1-thread-1, id=36
2023-05-31 21:24:18.008 INFO 29676 --- [pool-1-thread-2] org.example.component.TaskComponent : task1-------當(dāng)先線程的名字=pool-1-thread-2, id=37
2023-05-31 21:24:20.002 INFO 29676 --- [pool-1-thread-1] org.example.component.TaskComponent : task2-------當(dāng)先線程的名字=pool-1-thread-1, id=36
2023-05-31 21:24:20.002 INFO 29676 --- [pool-1-thread-1] org.example.component.TaskComponent : task2 執(zhí)行完了,線程的名字=pool-1-thread-1, id=36
2023-05-31 21:24:22.009 INFO 29676 --- [pool-1-thread-3] org.example.component.TaskComponent : task2-------當(dāng)先線程的名字=pool-1-thread-3, id=39
2023-05-31 21:24:22.009 INFO 29676 --- [pool-1-thread-3] org.example.component.TaskComponent : task2 執(zhí)行完了,線程的名字=pool-1-thread-3, id=39
2023-05-31 21:24:24.006 INFO 29676 --- [pool-1-thread-1] org.example.component.TaskComponent : task2-------當(dāng)先線程的名字=pool-1-thread-1, id=36
2023-05-31 21:24:24.006 INFO 29676 --- [pool-1-thread-1] org.example.component.TaskComponent : task2 執(zhí)行完了,線程的名字=pool-1-thread-1, id=36
2023-05-31 21:24:26.006 INFO 29676 --- [pool-1-thread-4] org.example.component.TaskComponent : task2-------當(dāng)先線程的名字=pool-1-thread-4, id=44
2023-05-31 21:24:26.006 INFO 29676 --- [pool-1-thread-4] org.example.component.TaskComponent : task2 執(zhí)行完了,線程的名字=pool-1-thread-4, id=44
2023-05-31 21:24:28.015 INFO 29676 --- [pool-1-thread-3] org.example.component.TaskComponent : task2-------當(dāng)先線程的名字=pool-1-thread-3, id=39
2023-05-31 21:24:28.015 INFO 29676 --- [pool-1-thread-2] org.example.component.TaskComponent : task1 執(zhí)行完了,線程的名字=pool-1-thread-2, id=37
2023-05-31 21:24:28.015 INFO 29676 --- [pool-1-thread-3] org.example.component.TaskComponent : task2 執(zhí)行完了,線程的名字=pool-1-thread-3, id=39
2023-05-31 21:24:29.011 INFO 29676 --- [pool-1-thread-5] org.example.component.TaskComponent : task1-------當(dāng)先線程的名字=pool-1-thread-5, id=49
2023-05-31 21:24:30.010 INFO 29676 --- [pool-1-thread-1] org.example.component.TaskComponent : task2-------當(dāng)先線程的名字=pool-1-thread-1, id=36
2023-05-31 21:24:30.010 INFO 29676 --- [pool-1-thread-1] org.example.component.TaskComponent : task2 執(zhí)行完了,線程的名字=pool-1-thread-1, id=36
2023-05-31 21:24:32.008 INFO 29676 --- [pool-1-thread-6] org.example.component.TaskComponent : task2-------當(dāng)先線程的名字=pool-1-thread-6, id=54
2023-05-31 21:24:32.008 INFO 29676 --- [pool-1-thread-6] org.example.component.TaskComponent : task2 執(zhí)行完了,線程的名字=pool-1-thread-6, id=54
2023-05-31 21:24:34.001 INFO 29676 --- [pool-1-thread-6] org.example.component.TaskComponent : task2-------當(dāng)先線程的名字=pool-1-thread-6, id=54
2023-05-31 21:24:34.001 INFO 29676 --- [pool-1-thread-6] org.example.component.TaskComponent : task2 執(zhí)行完了,線程的名字=pool-1-thread-6, id=54
觀察日志輸出,可以看出task1是上一個(gè)執(zhí)行結(jié)束之后,下一個(gè)task1才會繼續(xù)執(zhí)行
2.Spring-quartz框架
導(dǎo)入依賴
<!-- quartz 定時(shí)調(diào)度框架 -->
<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任務(wù)執(zhí)行開始,線程id={}", Thread.currentThread().getId());
log.info("task2任務(wù)執(zhí)行結(jié)束,線程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任務(wù)執(zhí)行開始,線程id={}", Thread.currentThread().getId());
log.info("task2任務(wù)執(zhí)行結(jié)束,線程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() 不然會報(bào)錯(cuò)
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任務(wù)執(zhí)行開始,線程id=25
2023-05-31 21:53:42.012 INFO 50100 --- [eduler_Worker-2] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行結(jié)束,線程id=25
2023-05-31 21:53:44.013 INFO 50100 --- [eduler_Worker-3] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行開始,線程id=26
2023-05-31 21:53:44.013 INFO 50100 --- [eduler_Worker-3] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行結(jié)束,線程id=26
2023-05-31 21:53:46.010 INFO 50100 --- [eduler_Worker-4] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行開始,線程id=27
2023-05-31 21:53:46.010 INFO 50100 --- [eduler_Worker-4] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行結(jié)束,線程id=27
2023-05-31 21:53:48.004 INFO 50100 --- [eduler_Worker-5] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行開始,線程id=28
2023-05-31 21:53:48.004 INFO 50100 --- [eduler_Worker-5] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行結(jié)束,線程id=28
2023-05-31 21:53:50.003 INFO 50100 --- [eduler_Worker-6] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行開始,線程id=29
2023-05-31 21:53:50.003 INFO 50100 --- [eduler_Worker-6] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行結(jié)束,線程id=29
2023-05-31 21:53:51.823 INFO 50100 --- [eduler_Worker-1] org.example.component.QuartzTask1 : task1任務(wù)執(zhí)行結(jié)束,線程id=24
2023-05-31 21:53:51.824 INFO 50100 --- [eduler_Worker-7] org.example.component.QuartzTask1 : task1任務(wù)執(zhí)行開始,線程id=30
2023-05-31 21:53:52.017 INFO 50100 --- [eduler_Worker-8] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行開始,線程id=31
2023-05-31 21:53:52.017 INFO 50100 --- [eduler_Worker-8] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行結(jié)束,線程id=31
2023-05-31 21:53:54.008 INFO 50100 --- [eduler_Worker-9] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行開始,線程id=32
2023-05-31 21:53:54.008 INFO 50100 --- [eduler_Worker-9] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行結(jié)束,線程id=32
2023-05-31 21:53:56.012 INFO 50100 --- [duler_Worker-10] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行開始,線程id=33
2023-05-31 21:53:56.012 INFO 50100 --- [duler_Worker-10] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行結(jié)束,線程id=33
2023-05-31 21:53:58.009 INFO 50100 --- [eduler_Worker-2] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行開始,線程id=25
2023-05-31 21:53:58.010 INFO 50100 --- [eduler_Worker-2] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行結(jié)束,線程id=25
2023-05-31 21:54:00.009 INFO 50100 --- [eduler_Worker-3] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行開始,線程id=26
2023-05-31 21:54:00.009 INFO 50100 --- [eduler_Worker-3] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行結(jié)束,線程id=26
2023-05-31 21:54:01.826 INFO 50100 --- [eduler_Worker-7] org.example.component.QuartzTask1 : task1任務(wù)執(zhí)行結(jié)束,線程id=30
2023-05-31 21:54:01.827 INFO 50100 --- [eduler_Worker-4] org.example.component.QuartzTask1 : task1任務(wù)執(zhí)行開始,線程id=27
2023-05-31 21:54:02.002 INFO 50100 --- [eduler_Worker-5] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行開始,線程id=28
2023-05-31 21:54:02.003 INFO 50100 --- [eduler_Worker-5] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行結(jié)束,線程id=28
2023-05-31 21:54:04.011 INFO 50100 --- [eduler_Worker-6] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行開始,線程id=29
2023-05-31 21:54:04.011 INFO 50100 --- [eduler_Worker-6] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行結(jié)束,線程id=29
2023-05-31 21:54:06.004 INFO 50100 --- [eduler_Worker-1] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行開始,線程id=24
2023-05-31 21:54:06.004 INFO 50100 --- [eduler_Worker-1] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行結(jié)束,線程id=24
2023-05-31 21:54:08.012 INFO 50100 --- [eduler_Worker-8] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行開始,線程id=31
2023-05-31 21:54:08.012 INFO 50100 --- [eduler_Worker-8] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行結(jié)束,線程id=31
2023-05-31 21:54:10.011 INFO 50100 --- [eduler_Worker-9] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行開始,線程id=32
2023-05-31 21:54:10.012 INFO 50100 --- [eduler_Worker-9] org.example.component.QuartzTask2 : task2任務(wù)執(zhí)行結(jié)束,線程id=32
2023-05-31 21:54:11.828 INFO 50100 --- [eduler_Worker-4] org.example.component.QuartzTask1 : task1任務(wù)執(zhí)行結(jié)束,線程id=27
2023-05-31 21:54:11.828 INFO 50100 --- [duler_Worker-10] org.example.component.QuartzTask1 : task1任務(wù)執(zhí)行開始,線程id=33
通過日志輸出,可以看到,quartz達(dá)到了,springtask使用線程池的效果。
單個(gè)任務(wù)的串行執(zhí)行,使用的注解
@DisallowConcurrentExecution不允許并行執(zhí)行
quartz的核心一共有三個(gè)類
- 任務(wù) Job :需要執(zhí)行的任務(wù)邏輯類,需要實(shí)現(xiàn) Job 接口的 execute 方法
- 觸發(fā)器 Trigger :執(zhí)行任務(wù)的調(diào)度器。例如,每天的00:00執(zhí)行任務(wù)A,那么就需要設(shè)置Trigger的屬性
- 調(diào)度器 Scheduler : 任務(wù)的調(diào)度器,將任務(wù) Job和觸發(fā)器Trigger整合起來,負(fù)責(zé)基于Trigger設(shè)定的時(shí)間執(zhí)行任務(wù)Job
本文來自博客園,作者:永恒&,轉(zhuǎn)載請注明原文鏈接:http://www.rzrgm.cn/Sun-yuan/p/17447497.html

浙公網(wǎng)安備 33010602011771號