SpringBoot + Quartz 集群实现高可用

Quartz是一个Java编写的开源任务调度框架,在实际的项目中可能有很多的费时的批处理作业需要完成,可以使用Quartz将这种任务安排到用户活跃量少的时候执行。

Quartz API的关键接口是

  • Scheduler - 与调度程序交互的主要API。
  • Job - 由希望由调度程序执行的组件实现的接口。
  • JobDetail - 用于定义作业的实例。
  • Trigger(即触发器) - 定义执行给定作业的计划的组件。
  • JobBuilder - 用于定义/构建JobDetail实例,用于定义作业的实例。
  • TriggerBuilder - 用于定义/构建触发器实例。

我认为Quartz 的使用可分为三种方式:

  • 1.只使用Quartz的jar包通过api开发。
  • 2.Quartz整合Spring的非Web开发。
  • 3.Quartz整合SpringBoot的开发。

当然321是包含的关系,第一种方式可以查看文档很好实现。

如果要整合Spring和SpringBoot 那就不得不将Quartz 中的对象交给Spring管理,不然就失去了意义不是?可是如何将Quartz交给Spring管理呢?的业务代码需要写在Job中,和Spring整合之后这个类的实例就需要交给Spring 管理。这里有两种方式来处理:

  • 1.不实现Job的接口,自己定义一个类,指定调用方法
@Component
public class CommonJob {

  private final Logger logger = LoggerFactory.getLogger(getClass());

  @Autowired
  private UserService userService;

  /**
   * @Author: wks
   * @Description: 执行任务通用方法 
   * @Param: [quartzTask]
   * @return: void
   * @throw: 请描述异常信息
   */
  public void execute(QuartzTask quartzTask) {
      userService.getUserInfo(String userId);
      //业务代码
}

@Component
public class QuartzTaskHandler {

  @Autowired
  @Qualifier("myScheduler")
  private Scheduler scheduler;

  @Autowired
  private CommonJob commonJob;

  @Autowired
  private QuartzTaskMapper quartzTaskMapper;

  /**
   * @Author: zhaomeinan
   * @Description: 新增任务
   * @Date: 15:17 2019/1/3
   * @Modificd By:
   * @Param: [quartzTask]
   * @return: void
   * @throw: 请描述异常信息
   */
  public void addJob(QuartzTask quartzTask)
      throws SchedulerException, NoSuchMethodException, ClassNotFoundException {
    if (quartzTask == null) {
      return;
    }

    //任务启用状态 1启用 2暂停
    if (quartzTask.getInitiateMode() == 2) {
      return;
    }

    TriggerKey triggerKey = TriggerKey
        .triggerKey(quartzTask.getJobName(), quartzTask.getJobGroup());
    CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);

    if (null == trigger) {
      //定义jobdetail
      MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean();
      jobDetail.setConcurrent(false); // 是否并发执行
      jobDetail.setName(quartzTask.getJobName()); // 设置任务的名字
      jobDetail.setGroup(quartzTask.getJobGroup()); // 设置任务的分组,在多任务的时候使用
      jobDetail.setTargetObject(commonJob); // 需要执行的对象
      jobDetail.setTargetMethod("execute"); //非常重要,执行CommonJob类中的需要执行方法
      jobDetail.setArguments(quartzTask); //执行CommonJob类中的需要执行方法传入的参数
      jobDetail.afterPropertiesSet();

      //定义corn
      CronScheduleBuilder scheduleBuilder = CronScheduleBuilder
          .cronSchedule(quartzTask.getCronExpression()).withMisfireHandlingInstructionDoNothing();
      trigger = TriggerBuilder.newTrigger()
          .withIdentity(quartzTask.getJobName(), quartzTask.getJobGroup())
          .withSchedule(scheduleBuilder).build();

      //将任务加入工厂中
      scheduler.scheduleJob(jobDetail.getObject(), trigger);
    }
  }
}

通过MethodInvokingJobDetailFactoryBean实现了不通过实现Job接口的方式来实现任务调度

  • 2.通过继承QuartzJobBean类实现
@Component
public class CommonJob extends QuartzJobBean {

  private final Logger logger = LoggerFactory.getLogger(getClass());

   @Autowired
  private UserService userService;
  @Value("${server.port}")
  private String port;
  /**
   * @Author: wks
   * @Description: 执行任务通用方法 
   * @Modificd By:
   * @Param: [quartzTask]
   * @return: void
   * @throw: 请描述异常信息
   */
  @Override
  public void executeInternal(JobExecutionContext jobExecutionContext) { 
      //获得传过来的参数
      JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
      QuartzTask quartzTask = (QuartzTask)jobDataMap.get("quartzTask");
       userService.getUserInfo(String userId);
      //业务代码
  }
}

进入到QuartzJobBean类中

public abstract class QuartzJobBean implements Job {

    /**
     * This implementation applies the passed-in job data map as bean property
     * values, and delegates to {@code executeInternal} afterwards.
     * @see #executeInternal
     */
    @Override
    public final void execute(JobExecutionContext context) throws JobExecutionException {
        try {
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            MutablePropertyValues pvs = new MutablePropertyValues();
            pvs.addPropertyValues(context.getScheduler().getContext());
            pvs.addPropertyValues(context.getMergedJobDataMap());
            bw.setPropertyValues(pvs, true);
        }
        catch (SchedulerException ex) {
            throw new JobExecutionException(ex);
        }
        executeInternal(context);
    }

    /**
     * Execute the actual job. The job data map will already have been
     * applied as bean property values by execute. The contract is
     * exactly the same as for the standard Quartz execute method.
     * @see #execute
     */
    protected abstract void executeInternal(JobExecutionContext context) throws JobExecutionException;

}

发现他将Job对象包装为BeanWrapper 并保存了context.getScheduler().getContext(),和context.getMergedJobDataMap()这样子CommonJob对象就能拿到Quartz 的上下文信息,
这也就是为什么第一种方式为什么不可以直接实现Job接口了,因为这样子只是将CommonJob交给了Spring 但是关于Quartz 的上下文信息并没有。

@Component
public class QuartzTaskHandler {

  @Autowired
  private Scheduler scheduler;

  @Autowired
  private QuartzTaskMapper quartzTaskMapper;

  @PostConstruct
  public void startScheduler() {
    try {
      scheduler.start();
    } catch (SchedulerException e) {
      e.printStackTrace();
    }
  }
  /**
   * @Author: zhaomeinan
   * @Description: 新增任务
   * @Date: 15:17 2019/1/3
   * @Modificd By:
   * @Param: [quartzTask]
   * @return: void
   * @throw: 请描述异常信息
   */
  public void addJob(QuartzTask quartzTask)
      throws SchedulerException, NoSuchMethodException, ClassNotFoundException {
    if (quartzTask == null) {
      return;
    }

    //任务启用状态 1启用 2暂停
    if (quartzTask.getInitiateMode() == 2) {
      return;
    }

    TriggerKey triggerKey = TriggerKey
        .triggerKey(quartzTask.getJobName(), quartzTask.getJobGroup());
    CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);

    if (null == trigger) {
      //定义jobdetail
      JobDataMap jobDataMap = new JobDataMap();
      jobDataMap.put("quartzTask", quartzTask);
      JobDetail jobDetail = JobBuilder.newJob(CommonJob.class)
          .withIdentity(quartzTask.getJobName(), quartzTask.getJobGroup())
          .usingJobData(jobDataMap)
          .build();
      //定义corn
      CronScheduleBuilder scheduleBuilder = CronScheduleBuilder
          .cronSchedule(quartzTask.getCronExpression()).withMisfireHandlingInstructionDoNothing();
      trigger = TriggerBuilder.newTrigger()
          .withIdentity(quartzTask.getJobName(), quartzTask.getJobGroup())
          .withSchedule(scheduleBuilder).build();

      //将任务加入工厂中
      scheduler.scheduleJob(jobDetail, trigger);
    }
  }
}

如何实现Quartz集群呢?
Quartz的集群功能通过故障转移和负载平衡功能为您的调度程序带来高可用性和可扩展性。

image.png

目前,群集仅适用于JDBC-Jobstore(JobStoreTX或JobStoreCMT),并且基本上通过使群集的每个节点共享相同的数据库来工作。

负载平衡自动发生,群集的每个节点都尽可能快地触发jobs。当Triggers的触发时间发生时,获取它的第一个节点(通过在其上放置一个锁定)是将触发它的节点。

每次射击只能有一个节点开火。我的意思是,如果工作有一个重复的Triggers,告诉它每10秒钟发射一次,那么在12:00:00,正好一个节点将运行这个工作,在12:00:10,一个节点将运行作业等。它不一定是每次相同的节点 - 它或多或少是随机的,哪个节点运行它。负载平衡机制对于繁忙的调度器(大量的Triggers)是近似随机的,但是对于非忙(例如,很少的Triggers)调度器而言,有利于同一个节点。

当其中一个节点在执行一个或多个作业期间失败时发生故障切换。当节点出现故障时,其他节点会检测到该状况并识别数据库中在故障节点内正在进行的作业。任何标记为恢复的作业(在JobDetail上都具有“请求恢复”属性)将被剩余的节点重新执行。没有标记为恢复的作业将在下一次相关的Triggers触发时简单地被释放以执行。

集群功能最适合扩展长时间运行和/或cpu密集型作业(通过多个节点分配工作负载)。如果需要扩展以支持数千个短期运行(例如1秒)作业,则可以考虑通过使用多个不同的调度程序(包括HA的多个群集调度程序)对作业集进行分区。调度程序使用集群范围的锁,这种模式会在添加更多节点(超过三个节点 - 取决于数据库的功能等)时降低性能。

通过将“org.quartz.jobStore.isClustered”属性设置为“true”来启用聚类。集群中的每个实例都应该使用相同的quartz.properties文件。这样做的例外是使用相同的属性文件,具有以下允许的异常:不同的线程池大小,以及“org.quartz.scheduler.instanceId”属性的不同值。集群中的每个节点必须具有唯一的instanceId,通过将“AUTO”作为此属性的值,可以轻松完成(不需要不同的属性文件)。有关更多信息,请参阅有关JDBC-JobStore的配置属性的信息。

不要在单独的机器上运行群集,除非它们的时钟使用某种形式的时间同步服务(守护进程)进行同步,而这些时间同步服务(守护进程)运行非常有限(时钟必须在彼此之间)。如果您不熟悉如何执行此操作,请参阅http://www.boulder.nist.gov/timefreq/service/its.htm

不要针对任何其他实例运行(start()ed)的同一组数据库表启动(scheduler.start())非集群实例。您可能会收到严重的数据损坏,一定会遇到不正常的行为。
附上建表语句,因为有的数据库设计可能存在大小写敏感

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for qrtz_BLOB_TRIGGERS
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_BLOB_TRIGGERS`;
CREATE TABLE `qrtz_BLOB_TRIGGERS` (
  `SCHED_NAME` varchar(120) NOT NULL,
  `TRIGGER_NAME` varchar(200) NOT NULL,
  `TRIGGER_GROUP` varchar(200) NOT NULL,
  `BLOB_DATA` blob,
  PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
  KEY `SCHED_NAME` (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
  CONSTRAINT `qrtz_BLOB_TRIGGERS_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `qrtz_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Trigger 作为 Blob 类型存储(用于 Quartz 用户用 JDBC 创建他们自己定制的 Trigger 类型,<span style="color:#800080;">JobStore</span> 并不知道如何存储实例的时候)';

-- ----------------------------
-- Table structure for qrtz_CALENDARS
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_CALENDARS`;
CREATE TABLE `qrtz_CALENDARS` (
  `SCHED_NAME` varchar(120) NOT NULL,
  `CALENDAR_NAME` varchar(200) NOT NULL,
  `CALENDAR` blob NOT NULL,
  PRIMARY KEY (`SCHED_NAME`,`CALENDAR_NAME`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='以 Blob 类型存储 Quartz 的 Calendar 信息';

-- ----------------------------
-- Table structure for qrtz_CRON_TRIGGERS
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_CRON_TRIGGERS`;
CREATE TABLE `qrtz_CRON_TRIGGERS` (
  `SCHED_NAME` varchar(120) NOT NULL,
  `TRIGGER_NAME` varchar(200) NOT NULL,
  `TRIGGER_GROUP` varchar(200) NOT NULL,
  `CRON_EXPRESSION` varchar(120) NOT NULL,
  `TIME_ZONE_ID` varchar(80) DEFAULT NULL,
  PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
  CONSTRAINT `qrtz_CRON_TRIGGERS_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `qrtz_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='存储 Cron Trigger,包括 Cron 表达式和时区信息';

-- ----------------------------
-- Table structure for qrtz_FIRED_TRIGGERS
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_FIRED_TRIGGERS`;
CREATE TABLE `qrtz_FIRED_TRIGGERS` (
  `SCHED_NAME` varchar(120) NOT NULL,
  `ENTRY_ID` varchar(95) NOT NULL,
  `TRIGGER_NAME` varchar(200) NOT NULL,
  `TRIGGER_GROUP` varchar(200) NOT NULL,
  `INSTANCE_NAME` varchar(200) NOT NULL,
  `FIRED_TIME` bigint(13) NOT NULL,
  `SCHED_TIME` bigint(13) NOT NULL,
  `PRIORITY` int(11) NOT NULL,
  `STATE` varchar(16) NOT NULL,
  `JOB_NAME` varchar(200) DEFAULT NULL,
  `JOB_GROUP` varchar(200) DEFAULT NULL,
  `IS_NONCONCURRENT` varchar(1) DEFAULT NULL,
  `REQUESTS_RECOVERY` varchar(1) DEFAULT NULL,
  PRIMARY KEY (`SCHED_NAME`,`ENTRY_ID`),
  KEY `IDX_QRTZ_FT_TRIG_INST_NAME` (`SCHED_NAME`,`INSTANCE_NAME`),
  KEY `IDX_QRTZ_FT_INST_JOB_REQ_RCVRY` (`SCHED_NAME`,`INSTANCE_NAME`,`REQUESTS_RECOVERY`),
  KEY `IDX_QRTZ_FT_J_G` (`SCHED_NAME`,`JOB_NAME`,`JOB_GROUP`),
  KEY `IDX_QRTZ_FT_JG` (`SCHED_NAME`,`JOB_GROUP`),
  KEY `IDX_QRTZ_FT_T_G` (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
  KEY `IDX_QRTZ_FT_TG` (`SCHED_NAME`,`TRIGGER_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='存储与已触发的 Trigger 相关的状态信息,以及相联 Job 的执行信息';

-- ----------------------------
-- Table structure for qrtz_JOB_DETAILS
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_JOB_DETAILS`;
CREATE TABLE `qrtz_JOB_DETAILS` (
  `SCHED_NAME` varchar(120) NOT NULL,
  `JOB_NAME` varchar(200) NOT NULL,
  `JOB_GROUP` varchar(200) NOT NULL,
  `DESCRIPTION` varchar(250) DEFAULT NULL,
  `JOB_CLASS_NAME` varchar(250) NOT NULL,
  `IS_DURABLE` varchar(1) NOT NULL,
  `IS_NONCONCURRENT` varchar(1) NOT NULL,
  `IS_UPDATE_DATA` varchar(1) NOT NULL,
  `REQUESTS_RECOVERY` varchar(1) NOT NULL,
  `JOB_DATA` blob,
  PRIMARY KEY (`SCHED_NAME`,`JOB_NAME`,`JOB_GROUP`),
  KEY `IDX_QRTZ_J_REQ_RECOVERY` (`SCHED_NAME`,`REQUESTS_RECOVERY`),
  KEY `IDX_QRTZ_J_GRP` (`SCHED_NAME`,`JOB_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='存储每一个已配置的 Job 的详细信息';

-- ----------------------------
-- Table structure for qrtz_LOCKS
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_LOCKS`;
CREATE TABLE `qrtz_LOCKS` (
  `SCHED_NAME` varchar(120) NOT NULL,
  `LOCK_NAME` varchar(40) NOT NULL,
  PRIMARY KEY (`SCHED_NAME`,`LOCK_NAME`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='存储程序的悲观锁的信息(假如使用了悲观锁)';

-- ----------------------------
-- Table structure for qrtz_PAUSED_TRIGGER_GRPS
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_PAUSED_TRIGGER_GRPS`;
CREATE TABLE `qrtz_PAUSED_TRIGGER_GRPS` (
  `SCHED_NAME` varchar(120) NOT NULL,
  `TRIGGER_GROUP` varchar(200) NOT NULL,
  PRIMARY KEY (`SCHED_NAME`,`TRIGGER_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='存储已暂停的 Trigger 组的信息';

-- ----------------------------
-- Table structure for qrtz_SCHEDULER_STATE
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_SCHEDULER_STATE`;
CREATE TABLE `qrtz_SCHEDULER_STATE` (
  `SCHED_NAME` varchar(120) NOT NULL,
  `INSTANCE_NAME` varchar(200) NOT NULL,
  `LAST_CHECKIN_TIME` bigint(13) NOT NULL,
  `CHECKIN_INTERVAL` bigint(13) NOT NULL,
  PRIMARY KEY (`SCHED_NAME`,`INSTANCE_NAME`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='存储少量的有关 Scheduler 的状态信息,和别的 Scheduler 实例(假如是用于一个集群中)';

-- ----------------------------
-- Table structure for qrtz_SIMPLE_TRIGGERS
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_SIMPLE_TRIGGERS`;
CREATE TABLE `qrtz_SIMPLE_TRIGGERS` (
  `SCHED_NAME` varchar(120) NOT NULL,
  `TRIGGER_NAME` varchar(200) NOT NULL,
  `TRIGGER_GROUP` varchar(200) NOT NULL,
  `REPEAT_COUNT` bigint(7) NOT NULL,
  `REPEAT_INTERVAL` bigint(12) NOT NULL,
  `TIMES_TRIGGERED` bigint(10) NOT NULL,
  PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
  CONSTRAINT `qrtz_SIMPLE_TRIGGERS_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `qrtz_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='存储简单的 Trigger,包括重复次数,间隔,以及已触的次数';

-- ----------------------------
-- Table structure for qrtz_SIMPROP_TRIGGERS
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_SIMPROP_TRIGGERS`;
CREATE TABLE `qrtz_SIMPROP_TRIGGERS` (
  `SCHED_NAME` varchar(120) NOT NULL,
  `TRIGGER_NAME` varchar(200) NOT NULL,
  `TRIGGER_GROUP` varchar(200) NOT NULL,
  `STR_PROP_1` varchar(512) DEFAULT NULL,
  `STR_PROP_2` varchar(512) DEFAULT NULL,
  `STR_PROP_3` varchar(512) DEFAULT NULL,
  `INT_PROP_1` int(11) DEFAULT NULL,
  `INT_PROP_2` int(11) DEFAULT NULL,
  `LONG_PROP_1` bigint(20) DEFAULT NULL,
  `LONG_PROP_2` bigint(20) DEFAULT NULL,
  `DEC_PROP_1` decimal(13,4) DEFAULT NULL,
  `DEC_PROP_2` decimal(13,4) DEFAULT NULL,
  `BOOL_PROP_1` varchar(1) DEFAULT NULL,
  `BOOL_PROP_2` varchar(1) DEFAULT NULL,
  PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
  CONSTRAINT `qrtz_SIMPROP_TRIGGERS_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `qrtz_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for qrtz_TRIGGERS
-- ----------------------------
DROP TABLE IF EXISTS `qrtz_TRIGGERS`;
CREATE TABLE `qrtz_TRIGGERS` (
  `SCHED_NAME` varchar(120) NOT NULL,
  `TRIGGER_NAME` varchar(200) NOT NULL,
  `TRIGGER_GROUP` varchar(200) NOT NULL,
  `JOB_NAME` varchar(200) NOT NULL,
  `JOB_GROUP` varchar(200) NOT NULL,
  `DESCRIPTION` varchar(250) DEFAULT NULL,
  `NEXT_FIRE_TIME` bigint(13) DEFAULT NULL,
  `PREV_FIRE_TIME` bigint(13) DEFAULT NULL,
  `PRIORITY` int(11) DEFAULT NULL,
  `TRIGGER_STATE` varchar(16) NOT NULL,
  `TRIGGER_TYPE` varchar(8) NOT NULL,
  `START_TIME` bigint(13) NOT NULL,
  `END_TIME` bigint(13) DEFAULT NULL,
  `CALENDAR_NAME` varchar(200) DEFAULT NULL,
  `MISFIRE_INSTR` smallint(2) DEFAULT NULL,
  `JOB_DATA` blob,
  PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
  KEY `IDX_QRTZ_T_J` (`SCHED_NAME`,`JOB_NAME`,`JOB_GROUP`),
  KEY `IDX_QRTZ_T_JG` (`SCHED_NAME`,`JOB_GROUP`),
  KEY `IDX_QRTZ_T_C` (`SCHED_NAME`,`CALENDAR_NAME`),
  KEY `IDX_QRTZ_T_G` (`SCHED_NAME`,`TRIGGER_GROUP`),
  KEY `IDX_QRTZ_T_STATE` (`SCHED_NAME`,`TRIGGER_STATE`),
  KEY `IDX_QRTZ_T_N_STATE` (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`,`TRIGGER_STATE`),
  KEY `IDX_QRTZ_T_N_G_STATE` (`SCHED_NAME`,`TRIGGER_GROUP`,`TRIGGER_STATE`),
  KEY `IDX_QRTZ_T_NEXT_FIRE_TIME` (`SCHED_NAME`,`NEXT_FIRE_TIME`),
  KEY `IDX_QRTZ_T_NFT_ST` (`SCHED_NAME`,`TRIGGER_STATE`,`NEXT_FIRE_TIME`),
  KEY `IDX_QRTZ_T_NFT_MISFIRE` (`SCHED_NAME`,`MISFIRE_INSTR`,`NEXT_FIRE_TIME`),
  KEY `IDX_QRTZ_T_NFT_ST_MISFIRE` (`SCHED_NAME`,`MISFIRE_INSTR`,`NEXT_FIRE_TIME`,`TRIGGER_STATE`),
  KEY `IDX_QRTZ_T_NFT_ST_MISFIRE_GRP` (`SCHED_NAME`,`MISFIRE_INSTR`,`NEXT_FIRE_TIME`,`TRIGGER_GROUP`,`TRIGGER_STATE`),
  CONSTRAINT `qrtz_TRIGGERS_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`) REFERENCES `qrtz_JOB_DETAILS` (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='存储已配置的 Trigger 的信息';

附上yml配置文件

spring:
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.1.10:3306/quartz_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
    username: root
    password: root
  quartz:
      job-store-type: jdbc #数据库方式
      jdbc:
        initialize-schema: never #不初始化表结构
      properties:
        org:
          quartz:
            scheduler:
              instanceId: AUTO #默认主机名和时间戳生成实例ID,可以是任何字符串,但对于所有调度程序来说,必须是唯一的 对应qrtz_scheduler_state INSTANCE_NAME字段
              #instanceName: clusteredScheduler #quartzScheduler
            jobStore:
              class: org.quartz.impl.jdbcjobstore.JobStoreTX #持久化配置
              driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate #仅为数据库制作了特定于数据库的代理
              useProperties: false #以指示JDBCJobStore将JobDataMaps中的所有值都作为字符串,因此可以作为名称 - 值对存储而不是在BLOB列中以其序列化形式存储更多复杂的对象。从长远来看,这是更安全的,因为您避免了将非String类序列化为BLOB的类版本问题。
              tablePrefix: qrtz_  #数据库表前缀
              misfireThreshold: 60000 #在被认为“失火”之前,调度程序将“容忍”一个Triggers将其下一个启动时间通过的毫秒数。默认值(如果您在配置中未输入此属性)为60000(60秒)。
              clusterCheckinInterval: 5000 #设置此实例“检入”*与群集的其他实例的频率(以毫秒为单位)。影响检测失败实例的速度。
              isClustered: true #打开群集功能
            threadPool: #连接池
              class: org.quartz.simpl.SimpleThreadPool
              threadCount: 10
              threadPriority: 5
              threadsInheritContextClassLoaderOfInitializingThread: true
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342