Springboot 整合 Quartz(定时任务框架)

一、java 定时任务调度的实现方式
1、Timer

特点是:简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务;能实现简单的定时任务,稍微复杂点(或要求高一些)的定时任务却不好实现。

2、ScheduledExecutor

鉴于 Timer 的缺陷,Java 5 推出了基于线程池设计的 ScheduledExecutor;

特点:每一个被调度的任务都会由线程池中一个线程去执行,因此任务是并发执行的,相互之间不会受到干扰。需要注意的是,只有当任务的执行时间到来时,ScheduedExecutor 才会真正启动一个线程,其余时间 ScheduledExecutor 都是在轮询任务的状态。

虽然用 ScheduledExecutor 和 Calendar 能够实现复杂任务调度,但实现起来还是比较麻烦,对开发还是不够友善。

3、Spring Scheduler

Spring 对任务调度的实现支持,可以指定任务的执行时间,但对任务队列和线程池的管控较弱;一般集成于项目中,小任务很方便。

4、开源工具包 JCronTab

JCronTab 则是一款完全按照 crontab 语法编写的 java 任务调度工具。

特点:

  • 可指定任务的执行时间;
  • 提供完全按照 Unix 的 UNIX-POSIX crontab 的格式来规定时间;
  • 支持多种任务调度的持久化方法,包括普通文件、数据库以及 XML 文件进行持久化;
  • JCronTab 内置了发邮件功能,可以将任务执行结果方便地发送给需要被通知的人;
  • 设计和部署是高性能并可扩展。
5、开源工具包 Quartz
  • 具有强大的调度功能,很容易与 Spring 集成,形成灵活可配置的调度功能;
  • 调度环境的持久化机制:可以保存并恢复调度现场,即使系统因为故障关闭,任务调度现场的数据并不会丢失;timer 没有这些特点;
  • 灵活的应用方式:可以灵活的定义触发器调度的时间表,并可以对触发器与任务进行关联映射;
  • 分布式与集群能力;
二、什么是 Quartz?

Quartz是 OpenSymphony 开源组织在 Job scheduling 领域又一个开源项目,完全由 Java 开发,可以用来执行定时任务,类似于 java.util.Timer。但是相较于 Timer,Quartz 增加了很多功能:

  • 持久性作业 - 就是保持调度定时的状态;
  • 作业管理 - 对调度作业进行有效的管理;
三、Quartz的相关概念
  • Scheduler:调度器,进行任务调度;quartz的大脑。
  • Job:业务job,亦可称业务组件;定时任务的具体执行业务需要实现此接口,调度器会调用此接口的execute方法完成我们的定时业务。
  • JobDetail:用来定义业务Job的实例,我们可以称之为quartz job,很多时候我们谈到的job指的是JobDetail。
  • Trigger:触发器,用来定义一个指定的Job何时被执行。
  • JobBuilder:Job构建器,用来定义或创建JobDetail的实例;JobDetail限定了只能是Job的实例。
  • TriggerBuilder:触发器构建器,用来定义或创建触发器的实例。
四、springboot整合Quartz
1、pom.xml中引入依赖
复制代码
    <!--quartz依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>
2、application.xml 中添加配置项

spring:

datasource:

druid:

url: jdbc:mysql://127.0.0.1:3306/my_testallowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai

username: root

password: admin_123

异步初始化策略,可加快启动速度

async-init: true

初始化时建立物理连接的个数,同最小连接池数量

initial-size: 5

最小连接池数量(按需配置)

min-idle: 5

最大连接池数量(按需配置)

max-active: 50

获取连接超时, -1表示可一直等待

max-wait: 6000

是否缓存preparedStatement,缓存prepared-statements,开启的情况下增加字段可能会报错

pool-prepared-statements: false

缓存preparedStatement cache大小

max-open-prepared-statements: 20

检测连接是否有效的sql

validation-query: select 1

申请连接时执行validationQuery检测连接是否有效

test-on-borrow: false

归还连接时执行validationQuery检测连接是否有效

test-on-return: false

如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。

test-while-idle: true

两个含义:1.Destroy线程运行周期 2.testWhileIdle判断依据

time-between-eviction-runs-millis: 60000

连接保持空闲而不被驱逐的最小时间:5分钟

min-evictable-idle-time-millis: 300000

连接保持空闲而不被驱逐的最大时间: 2天,根据生产mysql配置的wait_time配置=2天

max-evictable-idle-time-millis: 172800000

是否keep-alive:

即当最小空闲连接空闲了min-evictable-idle-time-millis,执行validationQuery进行keepAlive

keep-alive: true

#打印druid统计信息:每天打印一次统计信息日志,后续根据日志帮助优化连接池配置和SQL(按需配置, -1表示关闭)

time-between-log-stats-millis: 86400000

filter:

统计filter,druid默认开启

stat:

enabled: true

打印慢SQL(如需)

log-slow-sql: true

耗时多久为慢SQL(按需配置)

slow-sql-millis: 3000

driver-class-name: com.mysql.jdbc.Driver

type: com.alibaba.druid.pool.DruidDataSource

quartz:

任务存储类型

job-store-type: "jdbc"

关闭时等待任务完成

wait-for-jobs-to-complete-on-shutdown: false

是否覆盖已有的任务

overwrite-existing-jobs: true

是否自动启动计划程序

auto-startup: true

延迟启动

startup-delay: 0s

jdbc:

数据库架构初始化模式(never:从不进行初始化;always:每次都清空数据库进行初始化;embedded:只初始化内存数据库(默认值))

initialize-schema: "always"

相关属性配置

properties:

org:

quartz:

scheduler:

调度器实例名称

instanceName: QuartzScheduler

分布式节点ID自动生成

instanceId: AUTO

jobStore:

class: org.springframework.scheduling.quartz.LocalDataSourceJobStore

driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate

表前缀

tablePrefix: QRTZ_

是否开启集群

isClustered: true

数据源别名(自定义)

dataSource: quartz

分布式节点有效性检查时间间隔(毫秒)

clusterCheckinInterval: 10000

useProperties: false

线程池配置

threadPool:

class: org.quartz.simpl.SimpleThreadPool

threadCount: 10

threadPriority: 5

threadsInheritContextClassLoaderOfInitializingThread: true

3、创建Quartz 框架使用的 11 张表

**方法一:**若在配置项中initialize-schema: "always"

项目启动后,在数据库中可以看到自动生成了所有以"qrtz_"开头的表。后面initialize-schema 改成"never" 就行。

**方法二:**在压缩包该路径下找到对应的数据库 SQL 执行脚本,拷贝出来去 Navicat 执行;

4、11张表说明

表名

说明

qrtz_blob_triggers

以Blob 类型存储的触发器

qrtz calendars

存放日历信息,quartz可配置一个日历来指定一个时间范围

qrtz_cron triggers

存放cron类型的触发器

qrtz fired triggers

存放已触发的触发器

qrtz job _details

存放一个jobDetail信息

qrtz job listeners

job监听器

qrtz_locks

存储程序的悲观锁的信息(假如使用了悲观锁)

qrtz_paused trigger_graps

存放暂停掉的触发器

qrtz scheduler state

调度器状态

qrtz simple triggers

简单触发器的信息

qrtz_trigger_listeners

触发器监听器

5、简单的 demo 演示

(1)Service 接口

public interface QuartzService {

复制代码
/\*\*
 \* 新增
 \*
 \* @param jobName
 \* @param cron
 \* @param jobClassName
 \* @return
 \*/
String addCronJob(String jobName, String cron, String jobClassName);

/\*\*
 \* 停止
 \*
 \* @param jobName
 \* @param jobGroup
 \* @param triggerName
 \* @param triggerGroup
 \* @return
 \*/
String deleteCronJob(String jobName, String jobGroup, String triggerName, String triggerGroup);

/\*\*
 \* 立即执行,不定时
 \*
 \* @param jobName
 \* @param jobClassName
 \* @return
 \*/
String executeImmediately(String jobName, String jobClassName);

}

(2)Service 接口实现类

@Service

@Slf4j

public class QuartzServiceImpl implements QuartzService {

复制代码
@Autowired
private Scheduler scheduler;

private static final String DEFAULT\_JOB\_GROUP = "default\_job\_group";

private static final String DEFAULT\_TRIGGER\_GROUP = "default\_trigger\_group";

private static final String TRIGGER\_PRE = "Trigger\_";

@Override
public String addCronJob(String jobName, String cron, String jobClassName) {
    try {
        // 当前任务不存在才进行添加
        JobKey jobKey = JobKey.jobKey(jobName, DEFAULT\_JOB\_GROUP);
        if (scheduler.checkExists(jobKey)) {
            log.info("\[添加定时任务\]已存在该作业,jobkey为:{}", jobKey);
            return "已存在该作业";
        }

        // 构建 Job
        JobDetail job = JobBuilder.newJob(getClass(jobClassName).getClass())
                .withIdentity(jobKey).build();

        // cron表达式定时构造器
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cron);

        // 构建 Trigger
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(TriggerKey.triggerKey(TRIGGER\_PRE + jobName, DEFAULT\_TRIGGER\_GROUP))

// .startAt(DateUtil.parseDate(start))

// .endAt(DateUtil.parseDate(end))

.withSchedule(cronScheduleBuilder).build();

复制代码
        // 启动调度器
        scheduler.scheduleJob(job, trigger);
        scheduler.start();
        return "SUCCESS";
    } catch (Exception e) {
        log.error("\[新增定时任务\]失败,报错:", e);
        return "FAIL";
    }

}

@Override
public String deleteCronJob(String jobName, String jobGroup, String triggerName, String triggerGroup) {
    try {

        JobKey jobKey = JobKey.jobKey(jobName, jobGroup);

        TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroup);

        Trigger trigger = scheduler.getTrigger(triggerKey);

        if (null == trigger) {
            log.info("\[停止定时任务\]根据triggerName:{}和triggerGroup:{}未查询到相应的trigger!");
            return "SUCCESS";
        }
        //暂停触发器
        scheduler.pauseTrigger(triggerKey);
        // 移除触发器
        scheduler.unscheduleJob(triggerKey);
        // 删除任务
        scheduler.deleteJob(jobKey);

        log.info("\[停止定时任务\]jobName:{},jobGroup:{}, triggerName:{}, triggerGroup:{},停止--------------", jobName, jobGroup, triggerName, triggerGroup);

        return "SUCCESS";

    } catch (SchedulerException e) {
        log.error("\[停止定时任务\]失败,报错:", e);
        return "FAIL";
    }
}


public static Job getClass(String className) throws Exception {
    Class<> classTemp = Class.forName(className);
    return (Job) classTemp.newInstance();
}

@Override
public String executeImmediately(String jobName, String jobClassName) {
    try {
        JobKey jobKey = JobKey.jobKey(jobName, DEFAULT\_JOB\_GROUP);
        JobDetail job = JobBuilder.newJob(getClass(jobClassName).getClass())
                .withIdentity(jobKey).build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(TriggerKey.triggerKey(TRIGGER\_PRE + jobName, DEFAULT\_TRIGGER\_GROUP))
                .build();

        // 启动调度器
        scheduler.scheduleJob(job, trigger);
        scheduler.start();
        return "SUCCESS";
    } catch (Exception e) {
        log.error("\[立即执行一次任务,不定时\]失败,报错:", e);
        return "FAIL";
    }
}

}

(3)具体的业务类

@Component

@Slf4j

public class TaskJob implements Job {

@Override

public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {

log.info("=====业务逻辑");

log.info("jobName:{}", jobExecutionContext.getJobDetail().getKey().getName());

log.info("jobGroup:{}", jobExecutionContext.getJobDetail().getKey().getGroup());

log.info("triggerName:{}", jobExecutionContext.getTrigger().getKey().getName());

log.info("triggerGroup:{}", jobExecutionContext.getTrigger().getKey().getGroup());

log.info("上次触发时间:{}", DateUtil.formatDateTime(jobExecutionContext.getPreviousFireTime()));

log.info("本次触发时间:{}", DateUtil.formatDateTime(jobExecutionContext.getFireTime()));

log.info("下次触发时间:{}", DateUtil.formatDateTime(jobExecutionContext.getNextFireTime()));

log.info("调度时间:{}", DateUtil.formatDateTime(jobExecutionContext.getScheduledFireTime()));

}

}

(4)入参对象

@Data

public class JobInfo {

复制代码
private String jobName;

private String cron;

private String jobGroup;

private String triggerName;

private String triggerGroup;

}

(5)暴露接口层

@RestController

@RequestMapping("/quartz")

public class QuartzController {

复制代码
@Autowired
private QuartzService quartzService;

@PostMapping("/createJob")
public String createJob(@RequestBody JobInfo jobInfo) {
    return quartzService.addCronJob(jobInfo.getJobName(), jobInfo.getCron(), "com.example.springbootzy.quartz.config.TaskJob");
}

@PostMapping("/deleteJob")
public String deleteJob(@RequestBody JobInfo jobInfo) {
    return quartzService.deleteCronJob(jobInfo.getJobName(), jobInfo.getJobGroup(), jobInfo.getTriggerName(), jobInfo.getTriggerGroup());
}

@PostMapping("/executeImmediately")
public String executeImmediately(@RequestBody JobInfo jobInfo) {
    return quartzService.executeImmediately(jobInfo.getJobName(), "com.example.springbootzy.quartz.config.TaskJob");
}

}

(6)测试:新增一个"每十秒钟执行一次"的定时任务

(7)测试:删除上述已创建的定时任务

(8)测试:只执行一次

(9)其他

// 任务暂停

scheduler.pauseTrigger(TriggerKey.triggerKey("Trigger的name","Trigger的group"));

// 任务恢复

scheduler.resumeTrigger(TriggerKey.triggerKey("Trigger的name","Trigger的group"));

相关推荐
pshdhx_albert33 分钟前
AI agent实现打字机效果
java·http·ai编程
沉鱼.441 小时前
第十二届题目
java·前端·算法
努力的小郑2 小时前
Canal 不难,难的是用好:从接入到治理
后端·mysql·性能优化
赫瑞2 小时前
数据结构中的排列组合 —— Java实现
java·开发语言·数据结构
Victor3562 小时前
MongoDB(87)如何使用GridFS?
后端
Victor3562 小时前
MongoDB(88)如何进行数据迁移?
后端
小红的布丁3 小时前
单线程 Redis 的高性能之道
redis·后端
GetcharZp3 小时前
Go 语言只能写后端?这款 2D 游戏引擎刷新你的认知!
后端
周末也要写八哥3 小时前
多进程和多线程的特点和区别
java·开发语言·jvm
惜茶4 小时前
vue+SpringBoot(前后端交互)
java·vue.js·spring boot