使用quartz框架实现定时任务

quartz

在java的世界里, quartz绝对是总统山级别的王者的存在. 市面上大多数的开源的调度框架也基本都是直接或间接基于这个框架来开发的.

先来看通过一个最简单的quartz的例子, 来简单地认识一下它.

使用cron表达式来让quartz每10秒钟执行一个任务:

先引入maven依赖:

<!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.2</version>
        </dependency>

编写代码:

import com.alibaba.fastjson.JSON;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import static org.quartz.CronScheduleBuilder.cronSchedule;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;

public class QuartzTest implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("这里是你的定时任务: " + JSON.toJSONString( jobExecutionContext.getJobDetail()));
    }


    public static void main(String[] args) {
        try {
            // 获取到一个StdScheduler, StdScheduler其实是QuartzScheduler的一个代理
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

            // 启动Scheduler
            scheduler.start();
            // 新建一个Job, 指定执行类是QuartzTest(需实现Job), 指定一个K/V类型的数据, 指定job的name和group
            JobDetail job = newJob(QuartzTest.class)
                    .usingJobData("jobData", "test")
                    .withIdentity("myJob", "group1")
                    .build();
            // 新建一个Trigger, 表示JobDetail的调度计划, 这里的cron表达式是 每10秒执行一次
            Trigger trigger = newTrigger()
                    .withIdentity("myTrigger", "group1")
                    .startNow()
                    .withSchedule(cronSchedule("0/10 * * * * ?"))
                    .build();


            // 让scheduler开始调度这个job, 按trigger指定的计划
            scheduler.scheduleJob(job, trigger);


            // 保持进程不被销毁
           //  scheduler.shutdown();
            Thread.sleep(10000000);

        } catch (SchedulerException se) {
            se.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

上面这个简单的例子已经包含了quartz的几个核心组件:

Scheduler - 可以理解为是一个调度的实例,用来调度任务
Job - 这个是一个接口, 表示调度要执行的任务. 类似TimerTask.
JobDetail - 用于定义作业的实例。进一步封装和拓展Job的具体实例
Trigger(即触发器) - 定义JobDetail的调度计划。例如多久执行一次, 什么时候执行, 以什么频率执行等等
JobBuilder - 用于定义/构建JobDetail实例。
TriggerBuilder - 用于定义/构建触发器实例。
1. Scheduler

Scheduler是一个接口, 它一共有4个实现:

JBoss4RMIRemoteMBeanScheduler
RemoteMBeanScheduler
RemoteScheduler
StdScheduler

我们上面的例子使用的是StdScheduler, 表示的直接在本地进行调度(其他的都带有remote字样, 明显是跟远程调用有关).

来看一下StdScheduler的注释和构造方法

/**
 * <p>
 * An implementation of the <code>Scheduler</code> interface that directly
 * proxies all method calls to the equivalent call on a given <code>QuartzScheduler</code>
 * instance.
 * </p>
 * 
 * @see org.quartz.Scheduler
 * @see org.quartz.core.QuartzScheduler
 *
 * @author James House
 */
public class StdScheduler implements Scheduler {

    /**
     * <p>
     * Construct a <code>StdScheduler</code> instance to proxy the given
     * <code>QuartzScheduler</code> instance, and with the given <code>SchedulingContext</code>.
     * </p>
     */
    public StdScheduler(QuartzScheduler sched) {
        this.sched = sched;
    }
}
 

原来StdScheduler只不过是一个代理而已, 它最终都是调用org.quartz.core.QuartzScheduler类的方法.

查看RemoteScheduler等另外三个的实现, 也都是代理QuartzScheduler而已.

所以很明显, quartz的核心是QuartzScheduler类.

所以来看一下QuartzScheduler的javadoc注释:

/**
 * <p>
 * This is the heart of Quartz, an indirect implementation of the <code>{@link org.quartz.Scheduler}</code>
 * interface, containing methods to schedule <code>{@link org.quartz.Job}</code>s,
 * register <code>{@link org.quartz.JobListener}</code> instances, etc.
 * </p>
 * 
 * @see org.quartz.Scheduler
 * @see org.quartz.core.QuartzSchedulerThread
 * @see org.quartz.spi.JobStore
 * @see org.quartz.spi.ThreadPool
 * 
 * @author James House
 */
public class QuartzScheduler implements RemotableQuartzScheduler {
    ...
}

大概意思就是说: QuartzScheduler是quartz的心脏, 间接实现了org.quartz.Scheduler接口, 包含了调度Job和注册JobListener的方法等等

说是间接实现说Scheduler接口,但是来看一下它的继承图, 你会发现它跟Scheduler接口没有半毛钱关系(果然够间接的), 完全是自己独立搞了一套, 基本所有调度相关的逻辑都在里面实现了

使用quartz框架实现定时任务

另外从这个继承图中的RemotableQuartzScheduler也可以看出, QuartzScheduler是天生就可以支持远程调度的(通过rmi远程触发调度, 调度的管理和调度的执行可以分离).

当然, 实际应用中也大多数都是这么用, 只是我们这个最简单的例子是本地触发调度,本地执行任务而已.

2. Job, JobDetail

Job是一个接口, 它只定义了一个execute方法, 代表任务执行的逻辑.

public interface Job {
    void execute(JobExecutionContext context)
        throws JobExecutionException;
}

JobDetail其实也是一个接口, 它的默认实现是JobDetailImpl.JobDetail内部指定了JobDetail的实现类, 另外还新增了一些参数:

1. name和group, 会组合成一个JobKey对象, 作为这个JobDetail的唯一标识ID
2. jobDataMap, 可以给Job传递一些额外参数
3. durability, 是否需要持久化.这就是quartz跟一般的Timer之流不一样的地方了. 他的job是可以持久化到数据库的

可以看的出来, JobDetail其实是对Job类的一种增强. Job用来表示任务的执行逻辑, 而JobDetail更多的是跟Job管理相关.

3. Trigger

Trigger接口可以说才是quartz的核心功能. 因为quartz是一个定时任务调度框架, 而定时任务的调度逻辑, 就是在Trigger中实现的.

来看一下Trigger的实现类, 乍一看还挺多. 但是实际就图中红圈圈出来的那几个是真正的实现类, 其他的都是接口或实现类:

使用quartz框架实现定时任务

而实际上, 我们用得最多的也只是SimpleTriggerImpl和CronTriggerImpl, 前者表示简单的调度逻辑,例如每1分钟执行一次. 后者可以使用cron表达式来 指定更复杂的调度逻辑.

很明显, 上面简单的例子我们用的是CronTriggerImp

不过需要注意的是, quartz的cron表达式和linux下crontab的cron表达式是有一定区别的, 它可以直接到秒级别:

1. Seconds
2. Minutes
3. Hours
4. Day-of-Month
5. Month
6. Day-of-Week
7. Year (optional field)

例如: "0 0 12?* WED" - 这意味着"每个星期三下午12:00"。

使用CronTrigger的时候, 直接写cron表达式是比较容易出错的, 所以最好有个工具验证一下自己的cron表达式是否写正确, 以及验证触发的时间是否是我们期待的.

这个工作已经有人帮我们做好了, 例如下面这个网站:

tool.lu/crontab/

实际效果如下:

使用quartz框架实现定时任务

以上就算是quartz的一个入门教程了. 但是确实也只是一个入门教程而已.实际上quartz远比这个例子表现出来的复杂, 也同时也远比这个例子体现出来的强大.

例如:

1. quartz可以配置成集群模式,可以提供失败转移,负载均衡等功能, 在提升计算能力的同时,也提升了系统的可用性
2. quartz还支持JTA事务, 可以将一些job运行在一个事务中
3. 只要服务器资源上能支持, quartz理论上能运行成千上万的job
4. 等等等...

当然, quartz也不是没有缺点; 整个框架的重点都是在于"调度"上,而忽略了一些其他的方面, 例如交互和性能.

  1. 交互上, quartz只是提供了"scheduler.scheduleJob(job, trigger)" 这种api的方式. 没有提供任何的管理界面,这是非常的不人性化的.

  2. quartz并没有原生地支持分片的功能.这会导致运行一个大的任务时, 运行时间会非常的长. 例如要跑一亿个会员的数据时, 有可能一天都跑不完.如果是支持分片的那就好办很多了.可以把一亿会员拆分到多个实例上跑, 性能更高.

在这两点上, 一些其他的框架做得就更好了.

原文链接:https://juejin.im/post/6844904002606350343
上一篇:Hadoop2之YARN介绍


下一篇:Quartz-定时任务框架