分布式定时任务调度Quartz


Quartz 是 OpenSymphony 开源组织在任务调度领域的一个开源项目,完全基于 Java 实现。该项目于 2009 年被 Terracotta 收购,目前是 Terracotta 旗下的一个项目。

【定时任务中间件】分布式定时任务调度Quartz

概述

Quartz支持集群和分布式,但是没有友好的管理界面,功能单一,对于管理调用的任务比较困难。

Quartz使用数据库锁。采用了悲观锁的方式对triggers表进行行加锁,以保证任务同步的正确性。Quartz的分布式只是解决了高可用的问题,并没有解决任务分片的问题,还是会有单机处理的极限。

gitHub地址:源码地址

下载地址

稳定版下载

手动创建数据表SQL脚本

Job与JobDetail

Job - 你想要调度器执行的任务组件需要实现的接口

一个 job 就是一个实现了 Job 接口的类,该接口只有一个方法

package org.quartz;

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

}

job的一个 trigger 被触发后,execute() 方法会被 scheduler 的一个工作线程调用;JobDetail 对象是在将 job 加入 scheduler 时,由客户端程序(你的程序)创建的。它包含 job 的各种属性设置,以及用于存储 job 实例状态信息的 JobDataMap。

@DisallowConcurrentExecution

Quartz定时任务默认都是并发执行的。不会等待上一次任务执行完毕,只要间隔时间到了就会执行,如果定时任务执行太长,会长时间占用资源。导致其它任务堵塞。(quartz是用一个线程池去执行的。线程池有大小。如果同一任务并发执行过多。影响线程池其它任务执行)

禁止并发执行的意思并不是不能同时执行多个Job,而是不能并发执行同一个Job Definition(由JobDetail定义),但是可以同时执行多个不同的JobDetail。

@PersistJobDataAfterExecution注解

代表有状态的任务,改接口是一个没有方法的标签接口,其目的是让Quartz知道任务的类型,以便采用不同的执行方案。

  • 无状态任务在执行时拥有自己的 JobDataMap 拷贝,对 JobDataMap 的更改不会影响下次的执行。
  • 有状态任务共享同一个 JobDataMap 实例,每次任务执行对 JobDataMap 所做的更改会保存下来,后面的执行可以看到这个更改,也即每次执行任务后都会对后面的执行发生影响。

正因为这个原因,无状态的Job能并发执行,而有状态的StatefulJob不能并发执行。

JobDetail - 用于定义作业的实例

JobDetail实例是通过JobBuilder类创建的。

// define the job and tie it to our HelloJob class
        JobDetail job = newJob(HelloJob.class)
                .withIdentity("myJob", "group1") // name "myJob", group "group1"
                .build();

JobDataMap

JobDataMap中可以包含不限量的(序列化的)数据对象,在job实例执行的时候,可以使用其中的数据;JobDataMap是Java Map接口的一个实现,额外增加了一些便于存取基本类型的数据的方法。

// 设置JobDataMap
  JobDetail job = newJob(DumbJob.class)
      .withIdentity("myJob", "group1") // name "myJob", group "group1"
      .usingJobData("jobSays", "Hello World!")
      .usingJobData("myFloatValue", 3.141f)
      .build();

// 放入参数,运行时的方法可以获取
jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, qualityInspectionRule.getId());


// 获取JobDataMap
public class HelloJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDataMap dataMap = context.getJobDetail().getJobDataMap();

        String jobString = dataMap.getString("jobString");
        Integer jobInteger = dataMap.getInt("jobInteger");
    }
}

触发器(SimpleTrigger && CronTirgger)

SimpleTrigger:执行N次,重复N次
CronTrigger:几秒 几分 几时 哪日 哪月 哪周 哪年,执行

SimpleTrigger

SimpleTrigger可以满足的调度需求是:在具体的时间点执行一次,或者在具体的时间点执行,并且以指定的间隔重复执行若干次。比如,你有一个trigger,你可以设置它在2015年1月13日的上午11:23:54准时触发,或者在这个时间点触发,并且每隔2秒触发一次,一共重复5次。

重复次数withRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY))与repeatForever()等价。

SimpleTrigger实例通过TriggerBuilder设置主要的属性,通过SimpleScheduleBuilder设置与SimpleTrigger相关的属性。要使用这些builder的静态方法,需要静态导入:

CronTirgger

相对于SimpleTrigger,CornTrigger更加的灵活和实用,它用一个Corn表达式进行任务的触发,可以适应更为复杂的场景。

Listeners

JobListener接收与jobs相关的事件

public interface JobListener {

    public String getName();
	// job即将执行的通知
    public void jobToBeExecuted(JobExecutionContext context);

    public void jobExecutionVetoed(JobExecutionContext context);
	// job完成执行时的通知
    public void jobWasExecuted(JobExecutionContext context,
            JobExecutionException jobException);

}

添加对特定job感兴趣的JobListener:

scheduler.getListenerManager().addJobListener(myJobListener,KeyMatcher.jobKeyEquals(new JobKey("myJobName","myJobGroup")));

TriggerListeners接收到与触发器(trigger)相关的事件

public interface TriggerListener {
	// Get the name of the TriggerListener
    public String getName();
	// 当与监听器相关联的 Trigger 被触发,Job 上的 execute() 方法将要被执行时,Scheduler 就调用这个方法
    public void triggerFired(Trigger trigger, JobExecutionContext context);
	// 在 Trigger 触发后,Job 将要被执行时由 Scheduler 调用这个方法。TriggerListener 给了一个选择去否决 Job 的执行。假如这个方法返回 true,这个 Job 将不会为此次 Trigger 触发而得到执行。
    public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);
	//Scheduler 调用这个方法是在 Trigger 错过触发时。如这个方法的 JavaDoc 所指出的,你应该关注此方法中持续时间长的逻辑:在出现许多错过触发的 Trigger 时,长逻辑会导致骨牌效应。你应当保持这上方法尽量的小
    public void triggerMisfired(Trigger trigger);
	// Trigger 被触发并且完成了 Job 的执行时,Scheduler 调用这个方法。这不是说这个 Trigger 将不再触发了,而仅仅是当前 Trigger 的触发(并且紧接着的 Job 执行) 结束时。这个 Trigger 也许还要在将来触发多次的。
    public void triggerComplete(Trigger trigger, JobExecutionContext context,
            int triggerInstructionCode);
}

SchedulerListeners非常类似于TriggerListeners和JobListeners,除了它们在Scheduler本身中接收到事件的通知 - 不一定与特定触发器(trigger)或job相关的事件。

与计划程序相关的事件包括:添加job/触发器,删除job/触发器,调度程序中的严重错误,关闭调度程序的通知等。

Job Stores

JobStore负责跟踪您提供给调度程序的所有“工作数据”:jobs,triggers,日历等。

RAMJobStore

RAMJobStore是使用最简单的JobStore,它也是性能最高的(在CPU时间方面)。RAMJobStore以其明显的方式获取其名称:它将其所有数据保存在RAM中。这就是为什么它是闪电般快的,也是为什么这么简单的配置。缺点是当您的应用程序结束(或崩溃)时,所有调度信息都将丢失 - 这意味着RAMJobStore无法履行作业和triggers上的“非易失性”设置。对于某些应用程序,这是可以接受的 - 甚至是所需的行为,但对于其他应用程序,这可能是灾难性的。

配置Quartz以使用RAMJobStore

org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

JDBC JobStore

JDBCJobStore也被恰当地命名 - 它通过JDBC将其所有数据保存在数据库中。

引用

Quartz官方文档

Spring Boot 定时任务的技术选型对比

Quartz Cron在线测试校验工具

Quartz定时任务数据库表字段说明

quartz-misfire 错失、补偿执行

https://stackoverflow.com/questions/4408858/quartz-retry-when-failure


文章作者: WangQingLei
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 WangQingLei !
 上一篇
分布式共识协议与算法 - 理论篇、协议 分布式共识协议与算法 - 理论篇、协议
我们为什么需要了解分布式协议和算法呢?在我看来,它其实就是决定分布式系统如何运行的核心规则和关键步骤。 如果一个人想真正搞懂分布式技术,开发出一个分布式系统,最先需要掌握的就是这部分知识。 分布式算法是分布式技术中的核心,在分布式系统里,最
2023-06-04
下一篇 
docker-Dockerfile2 docker-Dockerfile2
我们已经介绍了 FROM,RUN,还提及了 COPY, ADD,其实 Dockerfile 功能很强大,它提供了十多个指令。下面我们继续讲解其他的指令。
2023-04-17
  目录