SpringBoot官方支持的任务调度框架Quartz入门与实战

一、简介

Quartz是一款功能强大的开源任务调度框架,几乎可以集成到任何Java应用程序中(小到单机应用,大到分布式应用)。Quartz可用于创建简单或复杂的任务调度,用以执行数以万计的任务,并且支持任务持久化。任务被定义为标准化的Java组件,Java编写的任务都可以被执行。

二、核心概念
  • Scheduler(调度器):Quartz中的任务调度器,通过Trigger和JobDetail可以用来调度、暂停和删除任务。
  • Trigger(触发器):Quartz中的触发器,可以通过CRON表达式来指定任务执行的时间,时间到了会自动触发任务执行。
  • JobDetail(任务详情):Quartz中需要执行的任务详情,包括了任务的唯一标识和具体要执行的任务,可以通过JobDataMap往任务中传递数据。
  • Job(任务):Quartz中具体的任务,包含了执行任务的具体方法。
  • Job Stores(持久化):
    RAMJobStore:存内存RAM、快、缺点是当您的应用程序结束(或崩溃)时,所有调度信息都将丢失。
    JDBC JobStore:存数据库、比RAM慢一点,加索引与机器性能OK那速度还好。
    TerracottaJobStore:数据存储在Terracotta服务器中,速度介于RAM与DB之间,可以运行群集或非群集。
    在这里插入图片描述
三、CRON表达式

1 Cron表达式是一个字符串,包括6~7个时间元素,在Quartz中可以用于指定任务的执行时间。
2 CRON的语法格式:Seconds Minutes Hours DayofMonth Month DayofWeek
在这里插入图片描述在这里插入图片描述
3 在线CRON表达式生成器:https://cron.qqe2.com/
在线时间戳转换:https://tool.lu/timestamp/

四、自定义quartz框架API对接前端

1 初始化表:
SQL详见官网或者https://gitee.com/qiugantao/java-spring-quick-start/blob/master/document/sql/quartz.sql
表信息解析:
1.1.qrtz_blob_triggers : 以Blob 类型存储的触发器。
1.2.qrtz_calendars:存放日历信息, quartz可配置一个日历来指定一个时间范围。
1.3.qrtz_cron_triggers:存放cron类型的触发器。
1.4.qrtz_fired_triggers:存放已触发的触发器。
1.5.qrtz_job_details:存放一个jobDetail信息。
1.6.qrtz_job_listeners:job监听器。
1.7.qrtz_locks: 存储程序的悲观锁的信息(假如使用了悲观锁)。
1.8.qrtz_paused_trigger_graps:存放暂停掉的触发器。
1.9.qrtz_scheduler_state:调度器状态。
1.10.qrtz_simple_triggers:简单触发器的信息。
1.11.qrtz_trigger_listeners:触发器监听器。
1.12.qrtz_triggers:触发器的基本信息。

2 pom.xml添加依赖:

<!--SpringBoot集成QuartZ-推荐-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

3 yml配置:

server:
  port: 8087

spring:
  datasource:
    url: jdbc:mysql://localhost:3307/qgt?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
    username: root
    password: 123456
  quartz:
    jdbc:
      initialize-schema: never  # 设置是否初始化表(该配置目前版本没有生效,设置成always也不会自动建表,根据官网文档查看,其目的是自动将quartz需要的数据表通过配置方式进行初始化)
    job-store-type: jdbc # quartz任务存储类型:jdbc或memory
    wait-for-jobs-to-complete-on-shutdown: true # 关闭是等待任务完成
    overwrite-existing-jobs: true # 可以覆盖已有的任务
    properties: # quartz原生配置
      org:
        quartz:
          scheduler:
            instanceName: scheduler # 调度器实例名称
            instanceId: AUTO # 调度器实例ID自动生成
          jobStore:
            class: org.quartz.impl.jdbcjobstore.JobStoreTX # 调度信息存储处理类
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate # 使用完全兼容JDBC的驱动
            tablePrefix: QRTZ_ # quartz相关表前缀
            useProperties: true # 是否将JobDataMap中的属性转为字符串存储
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool # 指定线程池实现类,对调度器提供固定大小的线程池
            threadCount: 10 # 设置并发线程数量
            threadPriority: 5 # 指定线程优先级

mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath*:mapper/*Mapper.xml

4 编写job:

@Component("helloJob")
public class HelloJob implements BaseJob {

    private static Logger log = LoggerFactory.getLogger(HelloJob.class);

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDetail jobDetail = context.getJobDetail();
        JobDataMap jobDataMap = jobDetail.getJobDataMap();
        log.info("Hello Job执行时间: " + DateFormatUtils.format(new Date(),"yyyy-MM-dd HH:mm:ss"));
    }

    public HelloJob() {}
}

5 编写service接口:

public interface ScheduleService {
    /**
     * 添加任务
     */
    Boolean scheduleJob(JobInfo jobInfo);

    /**
     * 更新任务
     */
    Boolean reScheduleJob(String jobName, String jobGroup, String cronExpression) throws Exception;

    /**
     * 暂停任务
     */
    Boolean pausejob(String jobName, String jobGroup) throws Exception;

    /**
     * 恢复任务
     */
    Boolean resumejob(String jobName, String jobGroup) throws Exception;

    /**
     * 取消任务
     */
    Boolean cancelScheduleJob(String jobName);

    /**
     * 查询任务
     */
    List<JobAndTrigger> getJobAndTriggerDetails(int pageNum, int pageSize);
}

6 编写serviceImpl定时任务实现类:

@Slf4j
@Service
public class ScheduleServiceImpl implements ScheduleService {
    @Autowired
    private Scheduler scheduler;

    @Autowired
    private JobAndTriggerMapper jobAndTriggerMapper;

    @Override
    public Boolean scheduleJob(JobInfo jobInfo) {
        boolean success = false;
        // 创建需要执行的任务
        JobDetail jobDetail = null;
        try {
            jobDetail = JobBuilder.newJob(getClass(jobInfo.getJobClassName()).getClass())
                    .withIdentity(jobInfo.getJobClassName(), jobInfo.getJobGroupName())
    //                .usingJobData("data", data)
                    .build();
        } catch (Exception e) {
            e.printStackTrace();
        }
        //创建触发器,指定任务执行时间
        CronTrigger cronTrigger = TriggerBuilder.newTrigger()
                .withIdentity(jobInfo.getJobClassName(), jobInfo.getJobGroupName())
                .withSchedule(CronScheduleBuilder.cronSchedule(jobInfo.getCronExpression()))
                .build();
        //使用调度器进行任务调度
        try {
            scheduler.scheduleJob(jobDetail, cronTrigger);
            success = true;
        } catch (SchedulerException e) {
            e.printStackTrace();
            log.info("创建定时任务失败!");
        } catch (Exception e) {
            e.printStackTrace();
            log.info("创建定时任务失败1!");
        }
        return success;
    }

    /**
     * 根据类名称,通过反射得到该类,然后创建一个BaseJob的实例。
     * 由于NewJob和HelloJob都实现了BaseJob,
     * 所以这里不需要我们手动去判断。这里涉及到了一些java多态调用的机制
     * 2019 12/24更新不再使用反射,使用SpringUtil工具类;
     * 前提是具体的Job类必须交给Spring来管理;
     * 例如:HelloJob 在其类上加上@Component("helloJob")注解,并指定传入Bean的名称;
     * @param classname
     * @return
     * @throws Exception
     */
    public BaseJob getClass(String classname) throws Exception {
        //Class<?> class1 = Class.forName(classname);
        //BaseJob baseJob = (BaseJob) class1.newInstance();
        BaseJob baseJob = (BaseJob) SpringUtil.getBean(classname);
        return baseJob;
    }

    @Override
    public Boolean reScheduleJob(String jobName, String jobGroup, String cronExpression) throws Exception{
        boolean success = false;
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
            // 表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);

            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);

            // 按新的cronExpression表达式重新构建trigger
            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();

            // 按新的trigger重新设置job执行
            scheduler.rescheduleJob(triggerKey, trigger);
            success = true;
        } catch (SchedulerException e) {
            log.info("更新定时任务失败" + e);
            throw new Exception("更新定时任务失败");
        } catch (Exception e) {
            log.info("更新定时任务失败1" + e);
            throw new Exception("更新定时任务失败1");
        }
        return success;
    }

    @Override
    public Boolean pausejob(String jobName, String jobGroup) throws Exception {
        boolean success = false;
        try {
            scheduler.pauseJob(JobKey.jobKey(jobName, jobGroup));
            success = true;
        } catch (SchedulerException e) {
            log.info("暂停定时任务失败" + e);
            throw new Exception("暂停定时任务失败");
        } catch (Exception e) {
            log.info("暂停定时任务失败1" + e);
            throw new Exception("暂停定时任务失败1");
        }
        return success;
    }

    @Override
    public Boolean resumejob(String jobName, String jobGroup) throws Exception {
        boolean success = false;
        try {
            scheduler.resumeJob(JobKey.jobKey(jobName, jobGroup));
            success = true;
        } catch (SchedulerException e) {
            log.info("恢复定时任务失败" + e);
            throw new Exception("恢复定时任务失败");
        } catch (Exception e) {
            log.info("恢复定时任务失败1" + e);
            throw new Exception("恢复定时任务失败1");
        }
        return success;
    }

    @Override
    public Boolean cancelScheduleJob(String jobName) {
        boolean success = false;
        try {
            // 暂停触发器
            scheduler.pauseTrigger(new TriggerKey(jobName, defaultGroup));
            // 移除触发器中的任务
            scheduler.unscheduleJob(new TriggerKey(jobName, defaultGroup));
            // 删除任务
            scheduler.deleteJob(new JobKey(jobName, defaultGroup));
            success = true;
        } catch (SchedulerException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return success;
    }

    @Override
    public List<JobAndTrigger> getJobAndTriggerDetails(int pageNow, int pageSize){
        int pageNum = (pageNow-1)*pageSize;
        return jobAndTriggerMapper.getJobAndTriggerDetails(pageNum,pageSize);
    }
}

7 编写定时任务调度相关接口:

@Api(tags = "ScheduleController", description = "定时任务调度相关接口")
@RestController
@RequestMapping("/schedule")
public class ScheduleController {
    @Autowired
    private ScheduleService scheduleService;

    @ApiOperation("新增定时任务")
    @PostMapping("/scheduleJob")
    public CommonResult scheduleJob(JobInfo jobInfo) throws Exception{
        Boolean success = scheduleService.scheduleJob(jobInfo);
        return CommonResult.success(success);
    }

    /**
     *
     * @param jobName  查询条件,查不到会返回500
     * @param jobGroup 查询条件,查不到会返回500
     * @param cronExpression 需要更新的Cron表达式
     * @return
     * @throws Exception
     */
    @ApiOperation("更新定时任务")
    @PostMapping(value = "/reschedulejob")
    public CommonResult rescheduleJob(@RequestParam(value = "jobName") String jobName,
                              @RequestParam(value = "jobGroup") String jobGroup,
                              @RequestParam(value = "cronExpression") String cronExpression) throws Exception {
        Boolean success = scheduleService.reScheduleJob(jobName, jobGroup, cronExpression);
        return CommonResult.success(success);
    }

    @ApiOperation("暂停定时任务")
    @PostMapping(value = "/pausejob")
    public CommonResult pausejob(@RequestParam(value = "jobName") String jobName, @RequestParam(value = "jobGroup") String jobGroup) throws Exception {
        Boolean success = scheduleService.pausejob(jobName, jobGroup);
        return CommonResult.success(success);
    }

    @ApiOperation("恢复定时任务")
    @PostMapping(value = "/resumejob")
    public CommonResult resumejob(@RequestParam(value = "jobName") String jobName, @RequestParam(value = "jobGroup") String jobGroup) throws Exception {
        Boolean success = scheduleService.resumejob(jobName, jobGroup);
        return CommonResult.success(success);
    }

    @ApiOperation("取消定时任务")
    @PostMapping("/cancelScheduleJob")
    public CommonResult cancelScheduleJob(@RequestParam String jobName) {
        Boolean success = scheduleService.cancelScheduleJob(jobName);
        return CommonResult.success(success);
    }

    @ApiOperation("查询定时任务")
    @GetMapping(value = "/queryScheduleJob")
    public CommonResult queryScheduleJob(@ApiParam(name = "pageNow", value = "页码", required = true, defaultValue = "1")
                                        @RequestParam int pageNow,
                                        @ApiParam(name = "pageSize", value = "每页数量", required = true, defaultValue = "3")
                                        @RequestParam int pageSize) {
        List<JobAndTrigger> jobs = scheduleService.getJobAndTriggerDetails(pageNow,pageSize);
        return CommonResult.success(jobs);
    }
}

8 效果:
在这里插入图片描述在这里插入图片描述demo源码:https://gitee.com/qiugantao/java-spring-quick-start/tree/master/java-quartz1

基于xxl-job改造,支持1.6jdk。改分布式任务调度特性如下: 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手; 2、动态:支持动态修改任务状态、暂停/恢复任务,以及终止运行中任务,即时生效; 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现,可保证调度中心HA; 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA; 5、任务Failover:执行器集群部署时,任务路由策略选择"故障转移"情况下调度失败时将会平滑切换执行器进行Failover; 6、一致性:“调度中心”通过DB锁保证集群分布式调度的一致性, 一次任务调度只会触发一次执行; 7、自定义任务参数:支持在线配置调度任务入参,即时生效; 8、调度线程池:调度系统多线程触发调度运行,确保调度精确执行,不被堵塞; 9、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务; 10、邮件报警:任务失败时支持邮件报警,支持配置多邮件地址群发报警邮件; 11、状态监控:支持实时监控任务进度; 12、Rolling执行日志:支持在线查看调度结果,并且支持以Rolling方式实时查看执行器输出的完整的执行日志; 13、GLUE:提供Web IDE,支持在线开发任务逻辑代码,动态发布,实时编译生效,省略部署上线的过程。支持30个版本的历史版本回溯。 14、数据加密:调度中心和执行器之间的通讯进行数据加密,提升调度信息安全性; 15、任务依赖:支持配置子任务依赖,当父任务执行结束且执行成功后将会主动触发一次子任务的执行, 多个子任务用逗号分隔;
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值