Quartz应用

Quartz简介

Quartz是一个功能丰富的开源作业调度库,几乎可以集成在任何Java应用程序中 - 从最小的独立应用程序到最大的电子商务系统。Quartz可用于创建简单或复杂的计划,以执行数十,数百甚至数万个作业; 将任务定义为标准Java组件的作业,这些组件可以执行几乎任何可以编程的程序。Quartz Scheduler包含许多企业级功能,例如支持JTA事务和集群。 

为什么不简单的使用java.util.Timer就行了呢?

JDK1.3开始,Java通过java.util.Timerjava.util.TimerTask可以实现定时器。列举其中部分原因:

Timers没有持久化机制。

Timers不灵活 (只可以设置开始时间和重复间隔,不是基于时间、日期、天等(秒、分、时)的)

Timers 不能利用线程池,一个timer一个线程。

Timers没有真正的管理计划

Quartz Spring Task使用比较

Spring Quartz 特点: 1. 默认多线程异步执行

2. 单任务同步:配置属性,可以使一个任务的一次调度在未完成时,而不会开启下一次调度 3. 多个任务同时运行,任务之间没有直接的影响,触发器和任务解耦

Spring Task特点:

1. 默认单线程同步执行

2. 一个任务执行完上一次之后,才会执行下一次调度

3. 多任务之间按顺序执行,一个任务执行完成之后才会执行另一个任务

4. 多任务并行执行需要设置线程池

5. 全程可以通过注解配置

 Quartz体系结构

组件结构图

Quartz的工作原理: 

Quartz是通过SchedulerFactory工厂生成Scheduler来进行任务的操作,Scheduler可以把任务JobDetail和触发器Trigger加入任务池中,可以删除或暂停任务等,Scheduler把这些任务和触发器放到一个JobStore中,这里JobStore有内存形式的也有持久化形式的,当然也可以自定义扩展成独立的服务。

Scheduler内部会通过一个调度线程QuartzSchedulerThread,不断到JobStore中找出下次需要执行的任务,并把这些任务封装放到一个线程池ThreadPool中运行。

Scheduler 调度线程主要有两个: 执行常规调度的线程(QuartzSchedulerThread),和执行 misfired trigger 的线程(MisfireHandler类,是JobStoreSupport内部线程类)。常规调度线程轮询存储的所有 trigger,如果有需要触发的 trigger,即到达了下一次触发的时间,则从任务执行线程池获取一个空闲线程,执行与该 trigger 关联的任务。Misfire 线程是扫描所有的 trigger,查看是否有 misfired trigger,如果有的话根据 misfire 的策略分别处理。

Quartz的线程视图

 Quartz有调度器、任务和触发器这三个核心概念,其中的核心接口和类如下:

Scheduler :这是Quartz Scheduler的主要接口,代表一个独立运行容器。调度程序维护JobDetailTrigger的注册表。 一旦在Scheduler注册过了,当定时任务触发时间一到,调度程序就会负责执行预先定义的Job

SchedulerFactory :调度程序Scheduler实例是通过SchedulerFactory工厂来创建的。

Trigger :具有所有触发器通用属性的基本接口,描述了job执行的时间出发规则。 - 使用TriggerBuilder实例化实际触发器。

Job:要由表示要执行的“作业”的类实现的接口。只有一个方法 void execute(jobExecutionContext context) (jobExecutionContext 提供调度上下文各种信息,运行时数据保存在jobDataMap) Job有个子接口StatefulJob ,代表有状态任务。 有状态任务不可并发,前次任务没有执行完,后面任务处于阻塞等到。

JobDetail :传递给定任务实例的详细信息属性。 JobDetail将使用JobBuilder创建/定义。

JobStore: 为org.quartz.core.QuartzScheduler的使用提供一个org.quartz.Joborg.quartz.Trigger存储机制。作业和触发器的存储应该以其名称和组的组合为唯一性。

QuartzScheduler :这是Quartz的核心,它是org.quartz.Scheduler接口的间接实现,包含调度org.quartz.Jobs,注册org.quartz.JobListener实例等的方法。

QuartzSchedulerThread :负责执行向QuartzScheduler注册的触发Trigger的工作的线程。

ThreadPoolScheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提供运行效率。

QuartzSchedulerResources:包含创建QuartzScheduler实例所需的所有资源(JobStoreThreadPool等)。

Quartz的应用场景

简捷地实现简单或复杂的计划,完成一系列定时任务,周期性任务的管理,可以选择持久化,记录任务的执行情况,根据任务无状态或有状态接口的不同实现定制计划。无状态任务一般指可以并发的任务,即任务之间是独立的,不会互相干扰,有状态任务来说,是不能并发执行的,同一时间只能有一个任务在执行。对此如果需要 misfired jobQuartz 会查看其 misfire 策略是如何设定的,如果是立刻执行,则会马上启动一次执行,如果是等待下次执行,则会忽略错过的任务,而等待下次触发执行。Quartz也支持集群和分布式任务。 

Quartz集群基于数据库锁,一个调度器实例在执行涉及到分布式问题的数据库操作前,首先要获取QUARTZ_LOCKS表中对应的行级锁,获取锁后即可执行其他表中的数据库操作,随着操作事务的提交,行级锁被释放,供其他调度实例获取。集群中的每一个调度器实例都遵循这样一种严格的操作规程。

QuartzLTS的分布式集群对比

1. Quartz不能很好的解决故障转移

2. Quartz 是通过数据库行级锁的方式实现多线程之间任务争用的问题。行锁开销大,加锁慢, 会出现死锁,并发度相比表级锁,页级锁高一点。但是在任务量比较大的时候,并发度较大的时候,行级锁就显得比较吃力了,而且很容易发生死锁。LTS采用预加载和乐观锁的方式,批量的将部分要执行的任务预加载到内存中。

3.  LTS 支持Failstore ,任务提交失败了,自动帮你本地持久化, Quartz当前节点挂了,内存中的数据就会全部丢失了

 项目案例

项目通过springboot +quartz + mysql 实现动态任务添加,暂停,删除,完成一个简单的入门案例。

开发环境:JDK1.8,MAVENIDEAMysql 5.7

项目结构图

 到官网下载对应版本的Quartz源码包,运行对应数据库的sql语句

数据库表详解:

1. pom.xml引入quartz依赖 

        <!--spring quartz依赖-->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.2.3</version>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>2.2.3</version>
        </dependency>

2. 简单配置quartz.properties文件

#默认或是自己改名字都行
org.quartz.scheduler.instanceName: DefaultQuartzScheduler

#如果使用集群,instanceId必须唯一,设置成AUTO
org.quartz.scheduler.instanceId = AUTO
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


#============================================================================
# Configure JobStore
#============================================================================
#
#org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
#存储方式使用JobStoreTX,也就是数据库
org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#使用自己的配置文件
org.quartz.jobStore.useProperties:true
#数据库中quartz表的表名前缀
org.quartz.jobStore.tablePrefix:QRTZ_
org.quartz.jobStore.dataSource:qzDS
#是否使用集群(如果项目只部署到 一台服务器,就不用了)
#org.quartz.jobStore.isClustered = true

#============================================================================
# Configure Datasources
#============================================================================
#配置数据源
org.quartz.dataSource.qzDS.driver:com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL:jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=utf8
org.quartz.dataSource.qzDS.user:root
org.quartz.dataSource.qzDS.password:123456
org.quartz.dataSource.qzDS.validationQuery=select 0 from dual

 配置文件设置调度程序scheduler的名称和示例id(id也可以设置AUTO)

设置工作线程线程数

指定所有Quartz的数据的存储方式。通过使用一个数据库,你可以打开一个全新的维度。

3. 创建job 实例工厂

@Component
public class MyJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {

    private transient AutowireCapableBeanFactory beanFactory;

    @Override
    public void setApplicationContext(final ApplicationContext context) {
        beanFactory = context.getAutowireCapableBeanFactory();
    }

    @Override
    protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
        final Object job = super.createJobInstance(bundle);
        beanFactory.autowireBean(job);
        return job;
    }

}

4. quartz的初始化配置 

@Configuration
public class SchedulerConfiguration {

    @Autowired
    private MyJobFactory myJobFactory;

    @Bean(name = "schedulerFactoryBean")
    public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
        //获取配置属性
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("quartz.properties"));
        //在quartz.properties中的属性被读取并注入后再初始化对象
        propertiesFactoryBean.afterPropertiesSet();
        //创建SchedulerFactoryBean
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        Properties pro = propertiesFactoryBean.getObject();
        factory.setOverwriteExistingJobs(true);
        factory.setAutoStartup(true);
        factory.setQuartzProperties(pro);
        factory.setJobFactory(myJobFactory);
        return factory;
    }

}

 4. 新建service接口和实现类

/**
     * 添加一个定时任务
     * @param jobName
     * @param jobGroup
     */
    void addCronJob(String jobName, String jobGroup);

    /**
     * 添加异步任务
     * @param jobName
     * @param jobGroup
     */
    void addAsyncJob(String jobName, String jobGroup);

    /**
     * 暂停任务
     * @param jobName
     * @param jobGroup
     */
    void pauseJob(String jobName, String jobGroup);

    /**
     * 恢复任务
     * @param triggerName
     * @param triggerGroup
     */
    void resumeJob(String triggerName, String triggerGroup);

    /**
     * 删除job
     * @param jobName
     * @param jobGroup
     */
    void deleteJob(String jobName, String jobGroup);

 5. 新建控制器

RestController
@RequestMapping("/quartztest")
public class JobController {
    @Autowired
    private JobService jobService;


    /**
     * 创建cron任务
     * @param jobName
     * @param jobGroup
     * @return
     */
    @RequestMapping(value = "/cron",method = RequestMethod.POST)
    public String startCronJob(@RequestParam("jobName") String jobName, @RequestParam("jobGroup") String jobGroup){
        jobService.addCronJob(jobName,jobGroup);
        return "create cron task success";
    }

    /**
     * 创建异步任务
     * @param jobName
     * @param jobGroup
     * @return
     */
    @RequestMapping(value = "/async",method = RequestMethod.POST)
    public String startAsyncJob(@RequestParam("jobName") String jobName, @RequestParam("jobGroup") String jobGroup){
        jobService.addAsyncJob(jobName,jobGroup);
        return "create async task success";
    }

    /**
     * 暂停任务
     * @param jobName
     * @param jobGroup
     * @return
     */
    @RequestMapping(value = "/pause",method = RequestMethod.POST)
    public String pauseJob(@RequestParam("jobName") String jobName, @RequestParam("jobGroup") String jobGroup){
        jobService.pauseJob(jobName,jobGroup);
        return "pause job success";
    }

    /**
     * 恢复任务
     * @param jobName
     * @param jobGroup
     * @return
     */
    @RequestMapping(value = "/resume",method = RequestMethod.POST)
    public String resumeJob(@RequestParam("jobName") String jobName, @RequestParam("jobGroup") String jobGroup){
        jobService.resumeJob(jobName,jobGroup);
        return "resume job success";
    }

    /**
     * 删除务
     * @param jobName
     * @param jobGroup
     * @return
     */
    @RequestMapping(value = "/delete",method = RequestMethod.PUT)
    public String deleteJob(@RequestParam("jobName") String jobName, @RequestParam("jobGroup") String jobGroup){
        jobService.deleteJob(jobName,jobGroup);
        return "delete job success";
    }
}

6.运行结果

猜你喜欢

转载自blog.csdn.net/qq_34462387/article/details/83066407