spring的定时任务:
1)简单的有Java自带的Timer、 ScheduledExecutorService, Spring自带的Task。
2)相较复杂的分布式定时任务中间件有XXL-JOB、ElasticJob等。
选Quartz理由:
1)任务Tigger能够被持久化,这样即使在发布后,任务依然能够执行,不需要重新设定。
2)能够轻松暂停恢复触发器(即下次不会被调度)。
3)支持Calander,Cron表达式等复杂的触发器,可以灵活的编写复杂触发器。
一:使用Timer创建简单的定时任务
有两点问题需要注意:
1.scheduleAtFixedRate和schedule的区别:scheduleAtFixedRate会尽量减少漏掉调度的情况,如果前一次执行时间过长,导致一个或几个任务漏掉了,那么会补回来,而schedule过去的不会补,直接加上间隔时间执行下一次任务。
public class TimerDemo {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("TimerTask1 run" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
}
},1000,5000);//延时1s,之后每隔5s运行一次
}
}
有两点问题需要注意:
1.scheduleAtFixedRate和schedule的区别:scheduleAtFixedRate会尽量减少漏掉调度的情况,如果前一次执行时间过长,导致一个或几个任务漏掉了,那么会补回来,而schedule过去的不会补,直接加上间隔时间执行下一次任务。
2.同一个Timer下添加多个TimerTask,如果其中一个没有捕获抛出的异常,则全部任务都会终止运行。但是多个Timer是互不影响
二:使用ScheduledThreadPoolExecutor创建定时任务
public class SchedulerDemo {
public static void main(String[] args) {
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(5);
executorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
String now = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
System.out.println("ScheduledThreadPoolExecutor1 run:"+now);
}
},1,2,TimeUnit.SECONDS);
}
}
scheduleWithFixedDelay跟schedule类似,而scheduleAtFixedRate与scheduleAtFixedRate一样会尽量减少漏掉调度的情况
三:在springboot环境下使用@Scheduled创建定时任务
1、启动类添加@EnableScheduling
2、定时任务方法上添加@Scheduled
@Component
public class springScheduledDemo {
@Scheduled(cron = "1/5 * * * * ?")
public void testScheduled(){
System.out.println("springScheduled run:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
}
}
这里的cron表达式我们可以参考网上的配置:
但是spring的@Scheduled只支持6位,年份是不支持的,带年份的7位格式会报错:Cron expression must consist of 6 fields (found 7 in “1/5 * * * * ? 2018”):cron表达式生成器:http://cron.ciding.cc/
@Scheduled 默认是单线程执行的,所以在需要的时候,我们可以设置一个线程池去执行定时任务。
通过实现SchedulingConfigurer接口来将定时线程池放入
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Configuration
public class TaskConfig implements SchedulingConfigurer {
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
executor.setPoolSize(10);
executor.setThreadNamePrefix("task-thread");
//设置饱和策略
//CallerRunsPolicy:线程池的饱和策略之一,当线程池使用饱和后,直接使用调用者所在的线程来执行任务;如果执行程序已关闭,则会丢弃该任务
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
//配置@Scheduled 定时器所使用的线程池
//配置任务注册器:ScheduledTaskRegistrar 的任务调度器
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
//可配置两种类型:TaskScheduler、ScheduledExecutorService
//scheduledTaskRegistrar.setScheduler(taskScheduler());
//只可配置一种类型:taskScheduler
scheduledTaskRegistrar.setTaskScheduler(taskScheduler());
}
}
使用Quartz定时任务框架:
1、使用SpringBoot2.x 版本集成 Quartz
2、Quartz 的任务动态实现:
3、调度任务可以通过页面进行新增、删除、启动、暂定等操作
4、任务数据使用数据库保存
5、任务之间实现简单的依赖
6、Quartz 实现分布式调度,使用其本身提供的基于数据库的实现
SpringBoot2 集成 Quartz
1、SpringBoot不同的版本对于Quartz 的集成有一定的差别,本文使用 2.1.2.RELEASE 版本。其实通过分析SpringBoot对于Quartz的自动化配置源码,也有助于我们理解Quartz的使用
2、SpringBoot-2.1.2.RELEASE 版本已经集成了对于Quartz的自动化配置,其源码路径为org.springframework.boot.autoconfigure.quartz
Pom依赖
# Web工程
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
# quartz
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
# 数据库JDBC
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
# 使用MySql
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
编码功能实现
由于Springboot2的自动化配置,不需要做任何配置,直接写JobDetail、Trigger、Job 即可实现
# Job 实现
@DisallowConcurrentExecution
public class DemoJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
System.out.println("~~ DemoJob 启动运行汇总~~");
}
}
# JobDetail、Trigger Bean配置
@Configuration
public class QuartzJobConfig {
@Bean
public JobDetailFactoryBean jobDetailFactoryBean() {
JobDetailFactoryBean jobDetail = new JobDetailFactoryBean();
jobDetail.setName("DemoJob");
jobDetail.setGroup("DemoJob_Group");
jobDetail.setJobClass(DemoJob.class);
jobDetail.setDurability(true);
return jobDetail;
}
@Bean
public CronTriggerFactoryBean cronTriggerFactoryBean() {
CronTriggerFactoryBean trigger = new CronTriggerFactoryBean();
trigger.setJobDetail(jobDetailFactoryBean().getObject());
trigger.setCronExpression("*/10 * * * * ?");
trigger.setName("DemoJob");
trigger.setMisfireInstruction(0);
return trigger;
}
}
这样就实现了 SpringBoot2.x 版本集成 Quartz 功能,在进行下一步之前,我们先对自动化配置的源码简单分析一下。
SpringBoot关于Quartz的自动配置的类一共有6个,分别为:
- JobStoreType:是一个枚举类,定义了jobsStore的类型,基于内存和数据库
- QuartzAutoConfiguration:自动配置类,配置了Quartz的主要组件,如SchedulerFactoryBean
- QuartzDataSource:是一个注解类。如果需要注入Quartz配置的数据库操作类,需要使用此注解标注。参考QuartzAutoConfiguration中的用法
- QuartzDataSourceInitializer:该类主要用于数据源初始化后的一些操作,根据不同平台类型的数据库进行选择不同的数据库脚本
- QuartzProperties:该类对应了在application.yml配置文件以spring.quartz开头的相关配置
- SchedulerFactoryBeanCustomizer:在自动配置的基础上自定义配置需要实现的此接口。
QuartzAutoConfiguration
- 初始化注入任务以及配置:构造函数实现
- 注入了属性配置文件类:QuartzProperties
- 注入了自定义扩展配置:SchedulerFactoryBeanCustomizer
- 注入了Quartz的任务组件:JobDetail、Trigger、Calendar。所以我们只- 需要进行 JobDetail、Trigger Bean配置,会自动注入进QuartzAutoConfiguration类中,这边是通过ObjectProvider的使用实现的。
public QuartzAutoConfiguration(QuartzProperties properties,
ObjectProvider<SchedulerFactoryBeanCustomizer> customizers,
ObjectProvider<JobDetail[]> jobDetails,
ObjectProvider<Map<String, Calendar>> calendars,
ObjectProvider<Trigger[]> triggers, ApplicationContext applicationContext) {
this.properties = properties;
this.customizers = customizers;
this.jobDetails = jobDetails.getIfAvailable();
this.calendars = calendars.getIfAvailable();
this.triggers = triggers.getIfAvailable();
this.applicationContext = applicationContext;
}
2、配置 SchedulerFactoryBean 的详细信息。这个类是一个 FactoryBean。
- 配置 JobFactory,内部设置了applicationContext与spring容器结合
- 配置各种属性,是通过QuartzProperties类实现
- 配置注入进来的 JobDetail、Trigger、Calendar
- 配置自定配置,是通过SchedulerFactoryBeanCustomizer实现。这边包括自定义,也包括基于数据库实现的JobStore配置。
# 注释掉内存存储
# org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
#持久化
org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX
@Bean
@ConditionalOnMissingBean
public SchedulerFactoryBean quartzScheduler() {
# 配置 `JobFactory`
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
SpringBeanJobFactory jobFactory = new SpringBeanJobFactory();
jobFactory.setApplicationContext(this.applicationContext);
schedulerFactoryBean.setJobFactory(jobFactory);
# 开始配置各种属性
if (this.properties.getSchedulerName() != null) {
schedulerFactoryBean.setSchedulerName(this.properties.getSchedulerName());
}
schedulerFactoryBean.setAutoStartup(this.properties.isAutoStartup());
schedulerFactoryBean
.setStartupDelay((int) this.properties.getStartupDelay().getSeconds());
schedulerFactoryBean.setWaitForJobsToCompleteOnShutdown(
this.properties.isWaitForJobsToCompleteOnShutdown());
schedulerFactoryBean
.setOverwriteExistingJobs(this.properties.isOverwriteExistingJobs());
if (!this.properties.getProperties().isEmpty()) {
schedulerFactoryBean
.setQuartzProperties(asProperties(this.properties.getProperties()));
}
# 配置 jobDetails、triggers等
if (this.jobDetails != null && this.jobDetails.length > 0) {
schedulerFactoryBean.setJobDetails(this.jobDetails);
}
if (this.calendars != null && !this.calendars.isEmpty()) {
schedulerFactoryBean.setCalendars(this.calendars);
}
if (this.triggers != null && this.triggers.length > 0) {
schedulerFactoryBean.setTriggers(this.triggers);
}
# 自定义配置
customize(schedulerFactoryBean);
return schedulerFactoryBean;
}
3、基于数据库实现的 JobStore 配置,内部类JdbcStoreTypeConfiguration
- @ConditionalOnSingleCandidate(DataSource.class) 指定pring容器中有且只有一个指明的DataSourceBean时生效
- 通过dataSourceCustomizer方法实现schedulerFactoryBean的数据库相关配置,该方法返回一个 SchedulerFactoryBeanCustomizer。
- 配置QuartzDataSourceInitializer 数据库初始化 Bean
- 通过后置工厂处理器 DataSourceInitializerSchedulerDependencyPostProcessor 实现对于QuartzDataSourceInitializer这个Bean的依赖关系(dependsOn)
@Bean
@Order(0)
public SchedulerFactoryBeanCustomizer dataSourceCustomizer(
QuartzProperties properties, DataSource dataSource,
@QuartzDataSource ObjectProvider<DataSource> quartzDataSource,
ObjectProvider<PlatformTransactionManager> transactionManager) {
return (schedulerFactoryBean) -> {
# 判断是否为 JobStore
if (properties.getJobStoreType() == JobStoreType.JDBC) {
# 获取 DataSource
DataSource dataSourceToUse = getDataSource(dataSource, quartzDataSource);
# 配置 DataSource 和 TransactionManager管理
schedulerFactoryBean.setDataSource(dataSourceToUse);
PlatformTransactionManager txManager = transactionManager.getIfUnique();
if (txManager != null) {
schedulerFactoryBean.setTransactionManager(txManager);
}
}
};
}
支持功能配置 QuartzProperties
1、@ConfigurationProperties(“spring.quartz”) 以spring.quartz开头的配置
2、SpringBoot 已经做了相应的默认值处理,即使不做任何配置,也是没有问题的。
3、比较简单,直接贴码。属性的具体含义,任务调度
spring:
quartz:
scheduler-name: springboot-quartz-jdbc-dynamic
auto-startup: false
startup-delay: 5s
overwrite-existing-jobs: false
wait-for-jobs-to-complete-on-shutdown: true
job-store-type: memory
# jdbc:
# initialize-schema: embedded
# schema: classpath:org/quartz/impl/jdbcjobstore/tables_@@platform@@.sql
# comment-prefix: --
properties: {
org.quartz.scheduler.instanceName: springboot-quartz-jdbc-dynamic,
org.quartz.scheduler.instanceId: AUTO,
org.quartz.threadPool.class: org.springframework.scheduling.quartz.SimpleThreadPoolTaskExecutor,
org.quartz.threadPool.threadCount: 25,
org.quartz.threadPool.threadPriority: 5,
org.quartz.jobStore.misfireThreshold: 60000,
# org.quartz.jobStore.tablePrefix: QRTZ_,
# org.quartz.jobStore.isClustered: true,
# org.quartz.jobStore.clusterCheckinInterval: 20000,
# org.quartz.jobStore.maxMisfiresToHandleAtATime: 1,
# org.quartz.jobStore.txIsolationLevelSerializable: false
}
Quartz 实现分布式调度
配置简单实现
上述完成的 SpringBoot与Quartz的集成,可以看到有几个先关的配置:
1、job-store-type 可以选择JDBC完成分布式JdbcJobStore切换
2、jdbc.XXX 主要是对于初始化SQL的配置。
3、对于JdbcJobStore 的一些特殊配置,如表前缀、集群指定、数据库检查等,基于RamJobStore时,这些是不允许配置的。
将类型转换成 JobStoreTX
JobStoreTX 全称是: org.quartz.impl.jdbcjobstore.JobStoreTX
# 注释掉内存存储
# org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
#持久化
org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX
三、 配置驱动代理
##驱动代理为 标准的jdbc
org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate
StdJDBCDelegate 为标准的jdbc。
四、配置数据库表前缀和数据源
#数据库表前缀
org.quartz.jobStore.tablePrefix:qrtz_
#数据源
org.quartz.jobStore.dataSource:yjlDB
#JDBC驱动
org.quartz.dataSource.yjlDB.driver:com.mysql.jdbc.Driver
org.quartz.dataSource.yjlDB.URL:jdbc:mysql://localhost:3306/quartz
org.quartz.dataSource.yjlDB.user:root
org.quartz.dataSource.yjlDB.password:您的数据库密码
五、最终 quartz.properties 配置文件内容
org.quartz.scheduler.instanceName: DefaultQuartzScheduler
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
org.quartz.jobStore.misfireThreshold: 60000
# 注释掉内存存储
# org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
#持久化
org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX
##驱动代理为 标准的jdbc
org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#数据库表前缀
org.quartz.jobStore.tablePrefix:qrtz_
#数据
org.quartz.jobStore.dataSource:yjlDB
#JDBC驱动
org.quartz.dataSource.yjlDB.driver:com.mysql.jdbc.Driver
org.quartz.dataSource.yjlDB.URL:jdbc:mysql://localhost:3306/quartz
org.quartz.dataSource.yjlDB.user:root
org.quartz.dataSource.yjlDB.password:abc123
持久化演示
编写任务 MyJobStoreTX
package com.yjl.jdbc;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
/**
*
* @author 两个蝴蝶飞
* 简单的 JobStoreTx 存储时的任务
*
*/
public class MyJobStoreTX implements Job{
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String nowDate=sdf.format(new Date());
System.out.println("MyJobStoreTX 备份数据库的时间是:"+nowDate);
}
}
编写主程序 MyJobStoreTxDemo
//测试 JobStoreTX 存储
public class MyJobStoreTxDemo {
public static void main(String[] args) throws Exception {
// 获取Scheduler
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
JobDetail jobDetail = JobBuilder.newJob(MyJobStoreTX.class)
.withIdentity("jobTX1", "groupTX1")
.storeDurably(true)
.build();
// 创建 Trigger
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("triggerTX1", "groupTX1") // 设置标识
.startNow()
// 每隔3秒执行一次
.withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?"))
.build();
// 关联 job和 trigger
scheduler.scheduleJob(jobDetail, trigger);
// 启动 scheduler
scheduler.start();
}
}
每隔两秒,运行一次。
查看数据库的 qrtz_cron_triggers 表内容:
查看数据库的 qrtz_job_details 表内容:
查看数据库的 qrtz_triggers 表内容:
将 JobDetail, Trigger 的内容放置到了数据库里面,进行了持久性的保存。
- https://blog.csdn.net/yjltx1234csdn/article/details/105871616
- https://www.w3cschool.cn/quartz_doc/quartz_doc-i7oc2d9l.html