Quartz

前言

  • Quartz 是一个完全由 Java 编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。

  • Quartz 可以 与 J2SE 应用程序相结合也可以单独使用。

  • Quartz 允许程序开发人员根据时间的间隔来调度作业。

  • Quartz 实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。

  • Quartz 官网

  • Quartz 官方文档

  • Quartz 快速入门

1、Quartz

  • Quartz 核心概念

    • JobDetail:表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。
    • Job:任务,表示一个工作,要执行的具体内容。
    • Trigger:触发器,代表一个调度参数的配置,什么时候去调。
    • Scheduler:调度器,代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。
  • Quartz 运行环境

    • Quartz 可以运行嵌入在另一个独立式应用程序。
    • Quartz 可以在应用程序服务器(或 servlet 容器)内被实例化,并且参与 XA 事务。
    • Quartz 可以作为一个独立的程序运行(其自己的 Java 虚拟机内),可以通过 RMI 使用。
    • Quartz 可以被实例化,作为独立的项目集群(负载平衡和故障转移功能),用于作业的执行。
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    import static org.quartz.JobBuilder.newJob;
    import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
    import static org.quartz.TriggerBuilder.newTrigger;

    import org.quartz.JobDetail;
    import org.quartz.Scheduler;
    import org.quartz.Trigger;
    import org.quartz.impl.StdSchedulerFactory;

    public class TestQuartz {
    public static void main(String[] args) throws Exception {

    // 创建调度器
    Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

    // 定义一个触发器
    Trigger trigger = newTrigger().withIdentity("trigger1", "group1") // 定义名称和所属的组
    .startNow()
    .withSchedule(simpleSchedule()
    .withIntervalInSeconds(2) // 每隔 2 秒执行一次
    .withRepeatCount(10)) // 总共执行 11 次(第一次执行不基数)
    .build();

    // 定义一个 JobDetail
    JobDetail job = newJob(MailJob.class) // 指定干活的类 MailJob
    .withIdentity("mailjob1", "mailgroup") // 定义任务名称和分组
    .usingJobData("email", "admin@10086.com") // 定义属性
    .build();

    // 调度加入这个 job
    scheduler.scheduleJob(job, trigger);

    // 启动
    scheduler.start();

    // 等待 20 秒,让前面的任务都执行完了之后,再关闭调度器
    Thread.sleep(20000);

    scheduler.shutdown(true);
    }
    }

2、任务

  • Job由 3 个部分组成

    • JobDetail: 用于描述这个 Job 是做什么的。
    • 实现 Job 的类: 具体干活的。
    • JobDataMap: 给 Job 提供参数用的。
  • Job 并发

    • 默认的情况下,无论上一次任务是否结束或者完成,只要规定的时间到了,那么下一次就开始。
    • 有时候会做长时间的任务,比如数据库备份,这个时候就希望上一次备份成功结束之后,才开始下一次备份,即便是规定时间到了,也不能开始,因为这样很有可能造成 数据库被锁死(几个线程同时备份数据库,引发无法预计的混乱)。
    • 那么在这种情况下,给数据库备份任务增加一个注解 @DisallowConcurrentExecution 就好了。
  • Job 异常

    • 任务里发生异常是很常见的。异常处理办法通常是两种:
      • 当异常发生,那么就通知所有管理这个 Job 的调度,停止运行它。
      • 当异常发生,修改一下参数,马上重新运行。
  • 中断 Job

    • 在业务上,有时候需要中断任务,那么这个 Job 需要实现 InterruptableJob 接口,然后就方便中断了。

3、触发器

  • Trigger 用来指定什么时间开始触发,触发多少次,每隔多久触发一次。

3.1 SimpleTrigger

  • SimpleTrigger 可以方便的实现一系列的触发机制。

  • 下一个 8 秒的倍数。

    1
    2
    Date startTime = DateBuilder.nextGivenSecondDate(null, 8);
    SimpleTrigger trigger = (SimpleTrigger) newTrigger().withIdentity("trigger1", "group1").startAt(startTime).build();
  • 10 秒后运行。

    1
    2
    Date startTime = DateBuilder.futureDate(10, IntervalUnit.SECOND);
    SimpleTrigger trigger = (SimpleTrigger) newTrigger().withIdentity("trigger1", "group1").startAt(startTime).build();
  • 累计 n 次,间隔 n 秒。

    1
    2
    3
    4
    5
    6
    7
    8
    Date startTime = DateBuilder.nextGivenSecondDate(null, 8);
    SimpleTrigger trigger = (SimpleTrigger) newTrigger()
    .withIdentity("trigger1", "group1")
    .startAt(startTime)
    .withSchedule(simpleSchedule()
    .withRepeatCount(3)
    .withIntervalInSeconds(1))
    .build();
  • 无限重复。

    1
    2
    3
    4
    5
    6
    7
    8
    Date startTime = DateBuilder.nextGivenSecondDate(null, 8);
    SimpleTrigger trigger = (SimpleTrigger) newTrigger()
    .withIdentity("trigger1", "group1")
    .startAt(startTime)
    .withSchedule(simpleSchedule()
    .repeatForever()
    .withIntervalInSeconds(1))
    .build();

3.2 CronTrigger

  • CronTrigger 就是用 Cron 表达式来安排触发时间和次数的。

  • Cron 是 Linux 下的一个定时器,功能很强大,但是表达式更为复杂。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    7 个占位符从左到右分别代表:秒、分、时、日、月、周几、年,* 表示通配符,匹配任意,当秒是 * 时,表示任意秒数都触发,其它类推。

    * * * * * * *
    ┬ ┬ ┬ ┬ ┬ ┬ ┬
    │ │ │ │ │ | └──── year (1 - 31, OPTIONAL)
    │ │ │ │ │ └────── day of week (0 - 7) (0 or 7 is Sun)
    │ │ │ │ └──────── month (1 - 12)
    │ │ │ └────────── day of month (1 - 31)
    │ │ └──────────── hour (0 - 23)
    │ └────────────── minute (0 - 59)
    └──────────────── second (0 - 59, OPTIONAL)
    • 星号(*):可用在所有字段中,表示对应时间域的每一个时刻,例如,在分钟字段时,表示 “每分钟”;
    • 问号(?):该字符只在日期和星期字段中使用,它通常指定为 “无意义的值”,相当于点位符;
    • 减号(-):表达一个范围,如在小时字段中使用 “10-12”,则表示从 10 到 12 点,即 10, 11, 12;
    • 逗号(,):表达一个列表值,如在星期字段中使用 “MON, WED, FRI”,则表示星期一,星期三和星期五;
  • 每隔 2 秒执行一次。

    1
    2
    3
    4
    CronTrigger trigger = newTrigger()
    .withIdentity("trigger1", "group1")
    .withSchedule(cronSchedule("0/2 * * * * ?"))
    .build();

4、监听器

  • Quartz 的 监听器有 Job 监听器,Trigger 监听器,Scheduler 监听器,对不同层面进行监控。

  • 实际业务用的较多的是 Job 监听器,用于监听是否执行了,其他的用的相对较少。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
    import org.quartz.JobListener;

    public class MailJobListener implements JobListener {

    @Override
    public String getName() {
    return "listener of mail job";
    }

    @Override
    public void jobExecutionVetoed(JobExecutionContext context) {
    System.out.println("取消执行:\t "+context.getJobDetail().getKey());
    }

    @Override
    public void jobToBeExecuted(JobExecutionContext context) {
    System.out.println("准备执行:\t "+context.getJobDetail().getKey());
    }

    @Override
    public void jobWasExecuted(JobExecutionContext context, JobExecutionException arg1) {
    System.out.println("执行结束:\t "+context.getJobDetail().getKey());
    System.out.println();
    }
    }

5、存储调度信息

  • 默认情况,Quartz 的触发器,调度,任务等信息都是放在内存中的,叫做 RAMJobStore。

    • 好处是快速。
    • 坏处是一旦系统重启,那么信息就丢失了,就得全部从头来过。
  • Quartz 还提供了另一个方式,可以把这些信息存放在数据库做,叫做 JobStoreTX。

    • 好处是就算系统重启了,目前运行到第几次了这些信息都是存放在数据库中的,那么就可以继续原来的步伐把计划任务无缝地继续做下去。
    • 坏处就是性能上比内存慢一些,毕竟数据库读取总是要慢一些的。

6、集群

  • Quartz 集群,是指在 基于数据库存储 Quartz 调度信息的基础上,有多个一模一样的 Quartz 应用在运行。

  • 当某一个 Quartz 应用重启或者发生问题的时候,其他的 Quartz 应用会 借助 数据库这个桥梁探知到它不行了,从而接手把该进行的 Job 调度工作进行下去。

  • 以这种方式保证任务调度的高可用性,即在发生异常重启等情况下,调度信息依然连贯性地进行下去,就好像 Quartz 应用从来没有中断过似的。

文章目录
  1. 1. 前言
  2. 2. 1、Quartz
  3. 3. 2、任务
  4. 4. 3、触发器
    1. 4.1. 3.1 SimpleTrigger
    2. 4.2. 3.2 CronTrigger
  5. 5. 4、监听器
  6. 6. 5、存储调度信息
  7. 7. 6、集群
隐藏目录