Quartz基本概念和与Spring Boot集成


1 基本概念

以下基本概念均对应Quartz的API。

1.1 任务

JobKey

包括jobNamejobGroup。同一调度器中,任务的JobKey是唯一的,被Scheduler用来管理Job

Job

代表定时任务要执行的内容,只包含一个execute方法,执行的内容写在方法里:

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

JobDetail

存储Job的基本属性,包括JobKeyJobClassJob的实现类):

public interface JobDetail extends Serializable, Cloneable {
    JobKey getKey();
    String getDescription();
    Class<? extends Job> getJobClass();
    JobDataMap getJobDataMap();
    boolean isDurable();
    boolean isPersistJobDataAfterExecution();
    boolean isConcurrentExectionDisallowed();
    boolean requestsRecovery();
    Object clone();
    JobBuilder getJobBuilder();
}

重要属性:

  • JobClass:Job的实现类
  • ConcurrentExectionDisallowed:表明任务不能有多个节点同时执行。
  • PersistJobDataAfterExecution:告诉Quartz在成功执行execution方法后,更新JobDetail。
  • Durability:Job的信息会被持久化。
  • RequestsRecovery:如果Job在某个节点执行失败,在下次Scheduler重启时任务会被重新执行。

JobBuilder

构建任务的工具类。

1.2 Trigger

代表定时任务的触发规则。

Simple Trigger

适合简单的规则,如在具体的时间点执行一次。

Cron Trigger

适合较复杂的规则。通常使用Cron Trigger。

TriggerBuilder

构建Trigger的工具类。

1.3 监听器

监听器注册到Scheduler,监听TriggerJob或者Scheduler的操作。

JobListener

TriggerListener

SchedulerListener

1.4 Scheduler

代表调度器,是Quartz的核心组件,可以增删Job、触发Trigger等。

public interface Scheduler {
    String getSchedulerName() throws SchedulerException;
    String getSchedulerInstanceId() throws SchedulerException;
    SchedulerContext getContext() throws SchedulerException;
    void start() throws SchedulerException;
    void startDelayed(int var1) throws SchedulerException;
    boolean isStarted() throws SchedulerException;
    void standby() throws SchedulerException;
    boolean isInStandbyMode() throws SchedulerException;
    void shutdown() throws SchedulerException;
    void shutdown(boolean var1) throws SchedulerException;
    boolean isShutdown() throws SchedulerException;
    SchedulerMetaData getMetaData() throws SchedulerException;
    List<JobExecutionContext> getCurrentlyExecutingJobs() throws SchedulerException;
    void setJobFactory(JobFactory var1) throws SchedulerException;
    ListenerManager getListenerManager() throws SchedulerException;
    Date scheduleJob(JobDetail var1, Trigger var2) throws SchedulerException;
    Date scheduleJob(Trigger var1) throws SchedulerException;
    void scheduleJobs(Map<JobDetail, Set<? extends Trigger>> var1, boolean var2) throws SchedulerException;
    void scheduleJob(JobDetail var1, Set<? extends Trigger> var2, boolean var3) throws SchedulerException;
    boolean unscheduleJob(TriggerKey var1) throws SchedulerException;
    boolean unscheduleJobs(List<TriggerKey> var1) throws SchedulerException;
    Date rescheduleJob(TriggerKey var1, Trigger var2) throws SchedulerException;
    void addJob(JobDetail var1, boolean var2) throws SchedulerException;
    void addJob(JobDetail var1, boolean var2, boolean var3) throws SchedulerException;
    boolean deleteJob(JobKey var1) throws SchedulerException;
    boolean deleteJobs(List<JobKey> var1) throws SchedulerException;
    void triggerJob(JobKey var1) throws SchedulerException;
    void triggerJob(JobKey var1, JobDataMap var2) throws SchedulerException;
    void pauseJob(JobKey var1) throws SchedulerException;
    void pauseJobs(GroupMatcher<JobKey> var1) throws SchedulerException;
    void pauseTrigger(TriggerKey var1) throws SchedulerException;
    void pauseTriggers(GroupMatcher<TriggerKey> var1) throws SchedulerException;
    void resumeJob(JobKey var1) throws SchedulerException;
    void resumeJobs(GroupMatcher<JobKey> var1) throws SchedulerException;
    void resumeTrigger(TriggerKey var1) throws SchedulerException;
    void resumeTriggers(GroupMatcher<TriggerKey> var1) throws SchedulerException;
    void pauseAll() throws SchedulerException;
    void resumeAll() throws SchedulerException;
    List<String> getJobGroupNames() throws SchedulerException;
    Set<JobKey> getJobKeys(GroupMatcher<JobKey> var1) throws SchedulerException;
    List<? extends Trigger> getTriggersOfJob(JobKey var1) throws SchedulerException;
    List<String> getTriggerGroupNames() throws SchedulerException;
    Set<TriggerKey> getTriggerKeys(GroupMatcher<TriggerKey> var1) throws SchedulerException;
    Set<String> getPausedTriggerGroups() throws SchedulerException;
    JobDetail getJobDetail(JobKey var1) throws SchedulerException;
    Trigger getTrigger(TriggerKey var1) throws SchedulerException;
    TriggerState getTriggerState(TriggerKey var1) throws SchedulerException;
    void addCalendar(String var1, Calendar var2, boolean var3, boolean var4) throws SchedulerException;
    boolean deleteCalendar(String var1) throws SchedulerException;
    Calendar getCalendar(String var1) throws SchedulerException;
    List<String> getCalendarNames() throws SchedulerException;
    boolean interrupt(JobKey var1) throws UnableToInterruptJobException;
    boolean interrupt(String var1) throws UnableToInterruptJobException;
    boolean checkExists(JobKey var1) throws SchedulerException;
    boolean checkExists(TriggerKey var1) throws SchedulerException;
    void clear() throws SchedulerException;
}

1.5 JobStore

持久化JobDetailTriggerScheduler等数据的地方,可以是数据库(JDBCJobStore)、RAM(RAMJobStore)等。JobStore不需要使用者直接调用,只需要在使用时指定。

2 集成Spring Boot

2.1 集成

Spring Boot已经集成了Quartz(通过QuartzAutoConfiguration自动配置),要使用时只要在application.yml中加入Quartz的配置,配置以spring.quartz.properties开头:

spring:
  quartz:
    job-store-type: jdbc
    properties:
      org:
        quartz:
          scheduler:
            # 调度器名称
            instanceName: DefaultQuartzScheduler
            # 如果使用集群,instanceId要唯一,必须设置成AUTO
            instanceId:AUTO
          threadPool:
            # 线程池实现类
            class: org.quartz.impl.SimpleThreadPool
            threadCount: 10
            threadPriority: 5
            # 线程名称前缀
            threadNamePrefix: quartz
            threadInheritContextClassLoaderOfInitializingThread: true
          jobStore:
            misfireThreshold: 60000
            # 持久化方法:数据库
            class: org.quartz.impl.jdbcJobStore.JobStoreTX
            dirverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
            # 数据库表名前缀
            tablePrefix: qrtz_
            # 是否使用集群
            isClustered: true
            

2.2 实现Job

实现JobInterruptedJob接口。

2.3 Spring Boot管理Job

Spring Boot会自动管理JobDetailTrigger类型的Bean,并根据JobStore持久化,以下为一个例子:

@Configuration
public class QuartzConfig {
  @Bean
  public JobDetail testJobDetail() {
    return JobBuilder.newJob(TestJob.class)
            .withIdentity("testJob", "group")
            .storeDurably()
            .build();
  }
  
  @Bean
  public Trigger testJobTrigger() {
    return TriggerBuilder.newTrigger()
              .withIdentity("testJobTrigger", "group")
              .startNow()
              .withSchedule(simpleSchedule().withIntervalInSeconds(1).repeatForever())
              .forJob(new JobKey("testJob", "group"))
              .build();
  }
}

2.4 覆盖已存在的job

如果更改了定时任务配置文件中的信息,如cron表达式等,新的表达式在重启后不能生效,原因是没有设置overrideExistingJobs参数。Spring Boot没有提供这个参数的配置,需要手动设置:

@Configuration
@AutoConfigureAfter(QuartzAutoConfiguration.class) 
public class QuartzSupportConfig {
  @Autowired(required = false) 
  private List<Trigger> triggers;

  @Autowired
  private SchedulerFactoryBean schedulerFactoryBean;

  @PostConstruct
  public void quartzScheduler() throws SchedulerException {
    schedulerFactoryBean.setOverrideExistingJobs(true);
    if (triggers != null) {
      Scheduler scheduler = schedulerFactoryBean.getScheduler();
      for (Trigger trigger : triggers) {
        scheduler.reschedulerJob(trigger.getKey(), trigger);
      }
    }
  }
}

3 集群

使用集群要配置以下几点:

  • Quartz通过数据库管理集群,数据表结构可从官网获得;
  • 集群只能使用JDBCJobStore,如果使用了和项目中不同的数据源,需要另外配置。

3.1 持久化

以下是Quartz的默认数据库表,JobDetailTriggerScheduler等都会保存在对应名称的表中:

Table Name Description
QRTZ_CALENDARS 存储Quartz的Calendar信息
QRTZ_CRON_TRIGGERS 存储CronTrigger,包括Cron表达式和时区信息
QRTZ_FIRED_TRIGGERS 存储与已触发的Trigger相关的状态信息,以及相联Job的执行信息
QRTZ_PAUSED_TRIGGER_GRPS 存储已暂停的Trigger组的信息
QRTZ_SCHEDULER_STATE 存储少量的有关Scheduler的状态信息,和别的Scheduler实例
QRTZ_LOCKS 存储程序的悲观锁的信息
QRTZ_JOB_DETAILS 存储每一个已配置的Job的详细信息
QRTZ_JOB_LISTENERS 存储有关已配置的JobListener的信息
QRTZ_SIMPLE_TRIGGERS 存储简单的Trigger,包括重复次数、间隔、以及已触的次数
QRTZ_BLOG_TRIGGERS Trigger作为Blob类型存储
QRTZ_TRIGGER_LISTENERS 存储已配置的TriggerListener的信息
QRTZ_TRIGGERS 存储已配置的Trigger的信息

3.2 负载均衡

quartz_lock是Quartz的锁表,使用SELECT ... FOR UPDATE语句获取行锁,保证同一时刻只有一个节点会执行定时任务。锁分为5种,分别用于实现多个节点对Job、Trigger、Calendar等访问的同步控制:

CALENDAR_ACCESS 
JOB_ACCESS      
MISFIRE_ACCESS  
STATE_ACCESS    
TRIGGER_ACCESS  

具体流程:

3.3 故障转移

RequestsRecovery设置为true时,任务可以实现故障转移。

以下摘自Configuration of JDBC-JobStoreCMT

Fail-over occurs when one of the nodes fails while in the midst of executing one or more jobs. When a node fails, the other nodes detect the condition and identify the jobs in the database that were in progress within the failed node. Any jobs marked for recovery (with the “requests recovery” property on the JobDetail) will be re-executed by the remaining nodes. Jobs not marked for recovery will simply be freed up for execution at the next time a related trigger fires.

4 参考

Quartz应用与集群原理分析