定时任务JOB框架之Quartz (三) Quartz的集群Cluster

一、前言

使用Quartz框架执行定时任务时,若是将应用部署到多台服务器,那么到了设置的时间点,多台服务器便会同时都在执行定时任务,这不符合我们预期的功能。按照我们的预期是:只有1台服务器在执行定时任务,其他服务器为备用服务器,当其中一台服务器出现故障时,Quartz自动进行故障转移failover,不用人为进行干预,就自动由另一台服务器来执行定时任务,这就是我们使用Quartz集群的目的。

Quartz集群支持多种方式,本文中主要介绍Quartz+Mysql的实现方式,在Quartz集群中每一个Scheduler实例都是一个节点,各个节点之间没有直接的通信,而是通过定时访问同一个数据库的方式来实现集群。每个节点在访问数据库时会更新实例的状态并检测节点的健康状态,每个节点会定时(org.quartz.jobStore.clusterCheckinInterval配置属性的时间,默认是7500毫秒)更新qrtz_scheduler_state数据表中的对应节点的LAST_CHECKIN_TIME字段,同时会遍历集群中是否有其他节点在到达他们预期的时间还未CHECKIN,则认为该节点故障,集群管理线程检测到故障节点,就会更新触发器的状态,从而达到故障转移的功能。

二、创建数据表

从Quartz的jar包org.quartz.impl.jdbcjobstore目录下取出tables_mysql_innodb.sql,然后在mysql中执行该sql脚本

执行成功后会生成11张数据表

三、创建Spring Boot工程

四、修改POM文件

在pom.xml文件中添加c3p0

<dependency>

  <groupId>com.mchange</groupId>

  <artifactId>c3p0</artifactId>

  <version>0.9.5.2</version>

</dependency>

Quartz默认是使用c3p0数据库连接池,如果缺少c3p0库,系统会报异常信息:ConnectionProvider class 'org.quartz.utils.C3p0PoolingConnectionProvider' could not be instantiated。

五、创建OrderJob和QuartzConfiguration类

5.1 OrderJob类

package com.ljhua.quartz3;

import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class OrderJob implements Job {
    Logger log = LoggerFactory.getLogger(this.getClass());
    private static final String NUM_EXECUTIONS = "NumExecutions";

    @Override
    public void execute(JobExecutionContext context) {
        int executeCount = 0;
        JobKey jobKey = context.getJobDetail().getKey();
        JobDataMap map = context.getJobDetail().getJobDataMap();

        if (map.containsKey(NUM_EXECUTIONS)) {
            executeCount = Integer.valueOf(map.getString(NUM_EXECUTIONS));
        }
        executeCount++;
        map.put(NUM_EXECUTIONS, String.valueOf(executeCount));


        log.info("{}-处理逻辑-start:*** {}", jobKey, executeCount);
        log.info("{}-处理逻辑-end:*** {}", jobKey, executeCount);
    }
}

5.2 QuartzConfiguration类

package com.ljhua.quartz3;

import org.quartz.*;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

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

@Configuration
@AutoConfigureAfter({SchedulerFactoryBean.class})
public class QuartzConfiguration {

    public QuartzConfiguration(SchedulerFactoryBean quartzScheduler) throws SchedulerException {
        Scheduler sched = quartzScheduler.getScheduler();

        TriggerKey triggerKey1 = TriggerKey.triggerKey("trigger1", "group1");
        JobDetail job1 = JobBuilder.newJob(OrderJob.class).withIdentity("job1", "group1").build(); 
        CronTrigger trigger1 = newTrigger().withIdentity(triggerKey1).withSchedule(cronSchedule("0/5 * * * * ?")).build();

        if (sched.checkExists(triggerKey1)) {
            sched.rescheduleJob(triggerKey1, trigger1);
        } else {
            sched.scheduleJob(job1, trigger1);
        }
    }
}

在Quartz集群模式下,Trigger和JobDetail是保存在数据库中的,只有在项目中有增加了新的Trigger和JobDetail时才调用sched.scheduleJob()方法,所以在QuartzConfiguration类中要做条件判断sched.checkExists(triggerKey1),不能每次程序启动时都调用sched.scheduleJob(job1, trigger1),否则系统会抛出异常信息:Unable to store Job : 'group1.job1', because one already exists with this identification。

调用sched.rescheduleJob(triggerKey1, trigger1)会更新qrtz_cron_triggers数据表中的cron_expression

调用sched.addJob(job1, true, true)会把JobDetail更新到qrtz_job_details数据表中,同时也会影响到JobDetail中的JobDataMap数据,一般情况下不需要用到ached.addJob()

六、配置属性

6.1 配置文件application.yml

spring:
  quartz:
    properties:
      org:
        quartz:
          scheduler:
            instanceName: TestScheduler
            instanceId: AUTO
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool
            threadCount: 10
            threadPriority: 5
          jobStore:
            misfireThreshold: 60000
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
            useProperties: true
            tablePrefix: qrtz_
            isClustered: true
            dataSource: myDS
            clusterCheckinInterval: 5000
          dataSource:
            myDS:
              driver: com.mysql.cj.jdbc.Driver
              URL: jdbc:mysql://127.0.0.1:3306/ljh?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowMultiQueries=true&useAffectedRows=true&verifyServerCertificate=false&useSSL=false
              user: root
              password: 123456
              maxConnections: 5
              validationQuery: select 0

6.2 配置属性org.quartz.jobStore.userProperties 

userProperties默认配置为false,设置为true时表示JobDataMap中的value存放的类型必须是String类型,存放非字符串时会抛出一个异常信息:Couldn't serialize job data: JobDataMap values must be Strings when the 'useProperties' property is set。

在OrderJob类中演示了用JobDataMap存储数据记录Job的执行次数,并没有使用Integer类型而是String类型。

实际应用中最好不要在userProperties=false条件下使用JobDataMap,会出现Job假死,但是qrtz_scheduler_state数据表中的LAST_CHECKIN_TIME字段却不断更新,导致故障转移failover无效,Quartz的集群作用没有发挥出来。

6.3 配置属性org.quartz.scheduler.instanceId 

instanceId属性配置的是集群中节点的名称,为了给每个节点一个不同的名称,而又不需要像下面这样来传参:

java -jar target\quartz3-0.0.1-SNAPSHOT.jar --spring.quartz.properties.org.quartz.scheduler.instanceId=ONE

java -jar target\quartz3-0.0.1-SNAPSHOT.jar --spring.quartz.properties.org.quartz.scheduler.instanceId=TWO

org.quartz.scheduler.instanceId配置等于AUTO时,系统会为每个Scheduler实例自动生成一个节点名称,而不是节点的名称都是AUTO,直接这样运行就可以了

java -jar target\quartz3-0.0.1-SNAPSHOT.jar

即使是在同一台主机运行2个Scheduler实例,节点的名称也是不同的,从qrtz_scheduler_state数据表中的INSTANCE_NAME字段可以看出来

发布了5 篇原创文章 · 获赞 0 · 访问量 157

猜你喜欢

转载自blog.csdn.net/anron/article/details/104514104
今日推荐