【Spring】定时任务

87 篇文章 4 订阅
34 篇文章 4 订阅

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个,分别为:

  1. JobStoreType:是一个枚举类,定义了jobsStore的类型,基于内存和数据库
  2. QuartzAutoConfiguration:自动配置类,配置了Quartz的主要组件,如SchedulerFactoryBean
  3. QuartzDataSource:是一个注解类。如果需要注入Quartz配置的数据库操作类,需要使用此注解标注。参考QuartzAutoConfiguration中的用法
  4. QuartzDataSourceInitializer:该类主要用于数据源初始化后的一些操作,根据不同平台类型的数据库进行选择不同的数据库脚本
  5. QuartzProperties:该类对应了在application.yml配置文件以spring.quartz开头的相关配置
  6. 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
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值