一个简单Quartz微服务————良好扩展性(springcloud,集群)

一、背景介绍

    项目早期为单体应用,近期因业务量上涨,架构逐渐转为springcloud+分布式集群。原先项目中的定时任务主要采用@Scheduled注解方式实现,并且因历史原因,分布散乱,管理不便。@Scheduled注解的定时任务无法直接应用于集群环境,并且服务重启或异常时,任务容易丢失。Quartz支持对数据的持久化,并且有misfire机制,任务不易丢失,同时支持集群设置,分离出的定时任务可独立为微服务应用,扩展性好。

二、代码实现

  1. 添加maven依赖
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
       <modelVersion>4.0.0</modelVersion>
    
       <groupId>com.cpdiem.demo</groupId>
       <artifactId>quick-quartz</artifactId>
       <version>0.0.1-SNAPSHOT</version>
       <packaging>jar</packaging>
    
       <name>quick-quartz</name>
       <description>Demo project for Spring Boot</description>
    
       <parent>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-parent</artifactId>
          <version>2.1.1.RELEASE</version>
          <relativePath/> <!-- lookup parent from repository -->
       </parent>
    
       <properties>
          <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
          <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
          <java.version>1.8</java.version>
       </properties>
    
       <dependencies>
          <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter</artifactId>
          </dependency>
    
          <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>
             <scope>test</scope>
          </dependency>
    
          <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <version>4.12</version>
             <scope>test</scope>
          </dependency>
    
          <!-- 访问数据库模块 -->
          <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-data-jpa</artifactId>
          </dependency>
    
          <!-- web模块 -->
          <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
          </dependency>
    
          <!-- MYSQL -->
          <dependency>
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-java</artifactId>
          </dependency>
    
          <!-- Jdbc 模块 -->
          <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-jdbc</artifactId>
          </dependency>
    
          <!-- quartz 模块 -->
          <dependency>
             <groupId>org.quartz-scheduler</groupId>
             <artifactId>quartz</artifactId>
             <version>2.3.0</version>
          </dependency>
          <dependency>
             <groupId>org.quartz-scheduler</groupId>
             <artifactId>quartz-jobs</artifactId>
             <version>2.3.0</version>
          </dependency>
          <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-context-support</artifactId>
          </dependency>
    
          <!-- druid 线程池模块 -->
          <dependency>
             <groupId>com.alibaba</groupId>
             <artifactId>druid</artifactId>
             <version>1.1.3</version>
          </dependency>
    
          <!-- hikari 连接池-->
          <dependency>
             <groupId>hikari-cp</groupId>
             <artifactId>hikari-cp</artifactId>
             <version>2.6.0</version>
          </dependency>
    
          <!-- redis缓存 -->
          <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-data-redis</artifactId>
             <version>2.1.1.RELEASE</version>
          </dependency>
    
          <dependency>
             <groupId>com.alibaba</groupId>
             <artifactId>fastjson</artifactId>
             <version>1.2.47</version>
          </dependency>
    
       </dependencies>
    
       <build>
          <plugins>
             <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
             </plugin>
          </plugins>
       </build>
    
    
    </project>
    
  2. 执行Quartz相关表入数据库(quick-quartz\quartz-tables.log)
    DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
    DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
    DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
    DROP TABLE IF EXISTS QRTZ_LOCKS;
    DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
    DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
    DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
    DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
    DROP TABLE IF EXISTS QRTZ_TRIGGERS;
    DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
    DROP TABLE IF EXISTS QRTZ_CALENDARS;
    
    
    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) 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 NULL,
        PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
    );
    
    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) NULL,
        NEXT_FIRE_TIME BIGINT(13) NULL,
        PREV_FIRE_TIME BIGINT(13) NULL,
        PRIORITY INTEGER NULL,
        TRIGGER_STATE VARCHAR(16) NOT NULL,
        TRIGGER_TYPE VARCHAR(8) NOT NULL,
        START_TIME BIGINT(13) NOT NULL,
        END_TIME BIGINT(13) NULL,
        CALENDAR_NAME VARCHAR(200) NULL,
        MISFIRE_INSTR SMALLINT(2) NULL,
        JOB_DATA BLOB NULL,
        PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
        FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
            REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
    );
    
    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),
        FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
            REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    );
    
    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(200) NOT NULL,
        TIME_ZONE_ID VARCHAR(80),
        PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
        FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
            REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    );
    
    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) NULL,
        STR_PROP_2 VARCHAR(512) NULL,
        STR_PROP_3 VARCHAR(512) NULL,
        INT_PROP_1 INT NULL,
        INT_PROP_2 INT NULL,
        LONG_PROP_1 BIGINT NULL,
        LONG_PROP_2 BIGINT NULL,
        DEC_PROP_1 NUMERIC(13,4) NULL,
        DEC_PROP_2 NUMERIC(13,4) NULL,
        BOOL_PROP_1 VARCHAR(1) NULL,
        BOOL_PROP_2 VARCHAR(1) NULL,
        PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
        FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    );
    
    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 NULL,
        PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
        FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
            REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    );
    
    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)
    );
    
    CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
      (
        SCHED_NAME VARCHAR(120) NOT NULL,
        TRIGGER_GROUP  VARCHAR(200) NOT NULL,
        PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
    );
    
    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 INTEGER NOT NULL,
        STATE VARCHAR(16) NOT NULL,
        JOB_NAME VARCHAR(200) NULL,
        JOB_GROUP VARCHAR(200) NULL,
        IS_NONCONCURRENT VARCHAR(1) NULL,
        REQUESTS_RECOVERY VARCHAR(1) NULL,
        PRIMARY KEY (SCHED_NAME,ENTRY_ID)
    );
    
    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)
    );
    
    CREATE TABLE QRTZ_LOCKS
      (
        SCHED_NAME VARCHAR(120) NOT NULL,
        LOCK_NAME  VARCHAR(40) NOT NULL,
        PRIMARY KEY (SCHED_NAME,LOCK_NAME)
    );
    
    
    commit;
    
  3. 添加应用配置(quick-quartz\src\main\resources\application.yml)
    server:
      port: 8088
    spring:
      application:
        name: quick-quartz
        datasource:
    #      type: com.zaxxer.hikari.HikariDataSource
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&userSSL=false&serverTimezone=GMT
          username: root
          password: root
          hikari:
            #
            minimum-idle: 5
            #
            idle-timeout: 180000
            #
            maximum-pool-size: 10
            aoto-commit: true
            pool-name: MyHikariCP
            connection-timeout: 30000
    
    
        jpa:
          hibernate:
            #ddl-auto: create 
            ddl-auto: update 
          show-sql: true
    
      redis:
        database: 0
        host: localhost
        port: 6379
        password:
        jedis:
          pool:
            max-active: 8
            max-wait: -1
            max-idle: 8
            min-idle: 0
        timeout: 5000
  4. 添加Quartz配置文件(quick-quartz\src\main\resources\config\quartz.properties)                                                                 配置说明可参考官方文档 http://www.quartz-scheduler.org/documentation/quartz-2.2.x/configuration/                                   w3cschool亦有中文版翻译 https://www.w3cschool.cn/quartz_doc/quartz_doc-i7oc2d9l.html
    org.quartz.scheduler.instanceName = quartzScheduler  
    org.quartz.scheduler.instanceId = AUTO  
    
    
    org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX  
    org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
    org.quartz.jobStore.tablePrefix = QRTZ_  
    org.quartz.jobStore.isClustered = true  
    org.quartz.jobStore.useProperties = false
    org.quartz.jobStore.clusterCheckinInterval = 20000    
    
    
    org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool  
    org.quartz.threadPool.threadCount = 10  
    org.quartz.threadPool.threadPriority = 5  
    org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
    
  5. 添加触发器配置文件(quick-quartz\src\main\resources\config\timers.properties)
    quartz.timer1=0/15 * * * * ?
    
    quartz.timer2=0 0/2 * * * ?
    
    quartz.timer3=0/30 * * * * ?
    
    
  6. 配置文件工具类(quick-quartz\src\main\java\com\cpdiem\demo\quickquartz\utils\PropertiesUtils.java)
    package com.cpdiem.demo.quickquartz.utils;
    
    import com.alibaba.fastjson.util.TypeUtils;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
    
    import java.math.BigDecimal;
    import java.math.BigInteger;
    import java.sql.Timestamp;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Properties;
    
    public class PropertiesUtils extends PropertyPlaceholderConfigurer {
    
        private static Map<String, Object> ctxPropertiesMap;
    
        /**
         * 重写processProperties方法
         */
        @Override
        protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)
                throws BeansException {
            super.processProperties(beanFactoryToProcess, props);
            ctxPropertiesMap = new HashMap<String, Object>();
            for (Object key : props.keySet()) {
                String keyStr = key.toString();
                String value = props.getProperty(keyStr);
                ctxPropertiesMap.put(keyStr, value);
            }
        }
    
        public static String getProperty(String key) {
            return TypeUtils.castToString(ctxPropertiesMap.get(key));
        }
    
        public static Object getObject(String key, Class clazz) {
            Object obj = getProperty(key);
            return TypeUtils.castToJavaBean(obj, clazz);
        }
    
        public static Boolean getBoolean(String key) {
            Object value = getProperty(key);
            if (value == null)
                return null;
            else
                return TypeUtils.castToBoolean(value);
        }
    
        public static byte[] getBytes(String key) {
            Object value = getProperty(key);
            if (value == null)
                return null;
            else
                return TypeUtils.castToBytes(value);
        }
    
        public static boolean getBooleanValue(String key) {
            Object value = getProperty(key);
            if (value == null)
                return false;
            else
                return TypeUtils.castToBoolean(value).booleanValue();
        }
    
        public static Byte getByte(String key) {
            Object value = getProperty(key);
            return TypeUtils.castToByte(value);
        }
    
        public static byte getByteValue(String key) {
            Object value = getProperty(key);
            if (value == null)
                return 0;
            else
                return TypeUtils.castToByte(value).byteValue();
        }
    
        public static Short getShort(String key) {
            Object value = getProperty(key);
            return TypeUtils.castToShort(value);
        }
    
        public static short getShortValue(String key) {
            Object value = getProperty(key);
            if (value == null)
                return 0;
            else
                return TypeUtils.castToShort(value).shortValue();
        }
    
        public static Integer getInteger(String key) {
            Object value = getProperty(key);
            return TypeUtils.castToInt(value);
        }
    
        public static int getIntValue(String key) {
            Object value = getProperty(key);
            if (value == null)
                return 0;
            else
                return TypeUtils.castToInt(value).intValue();
        }
    
        public static Long getLong(String key) {
            Object value = getProperty(key);
            return TypeUtils.castToLong(value);
        }
    
        public static long getLongValue(String key) {
            Object value = getProperty(key);
            if (value == null)
                return 0L;
            else
                return TypeUtils.castToLong(value).longValue();
        }
    
        public static Float getFloat(String key) {
            Object value = getProperty(key);
            return TypeUtils.castToFloat(value);
        }
    
        public static float getFloatValue(String key) {
            Object value = getProperty(key);
            if (value == null)
                return 0.0F;
            else
                return TypeUtils.castToFloat(value).floatValue();
        }
    
        public static Double getDouble(String key) {
            Object value = getProperty(key);
            return TypeUtils.castToDouble(value);
        }
    
        public static double getDoubleValue(String key) {
            Object value = getProperty(key);
            if (value == null)
                return 0.0D;
            else
                return TypeUtils.castToDouble(value).doubleValue();
        }
    
        public static BigDecimal getBigDecimal(String key) {
            Object value = getProperty(key);
            return TypeUtils.castToBigDecimal(value);
        }
    
        public static BigInteger getBigInteger(String key) {
            Object value = getProperty(key);
            return TypeUtils.castToBigInteger(value);
        }
    
        public static String getString(String key) {
            Object value = getProperty(key);
            if (value == null)
                return null;
            else
                return value.toString();
        }
    
        public static Date getDate(String key) {
            Object value = getProperty(key);
            return TypeUtils.castToDate(value);
        }
    
        public static java.sql.Date getSqlDate(String key) {
            Object value = getProperty(key);
            return TypeUtils.castToSqlDate(value);
        }
    
        public static Timestamp getTimestamp(String key) {
            Object value = getProperty(key);
            return TypeUtils.castToTimestamp(value);
        }
    
    
    }
    
  7. redis工具类(quick-quartz\src\main\java\com\cpdiem\demo\quickquartz\utils\RedisUtils.java)
    package com.cpdiem.demo.quickquartz.utils;
    
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Component;
    
    import java.util.Map;
    import java.util.Set;
    
    @Component
    public class RedisUtils {
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        /**
         * 根据key获取Set中的所有值
         * @param key 键
         * @return
         */
        public Set<Object> sGet(String key){
            try {
                return redisTemplate.opsForSet().members(key);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
    
        /**
         * 将数据放入set缓存
         * @param key 键
         * @param values 值 可以是多个
         * @return 成功个数
         */
        public long sSet(String key, Object...values) {
            try {
                return redisTemplate.opsForSet().add(key, values);
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
    
    
        /**
         * 获取set缓存的长度
         * @param key 键
         * @return
         */
        public long sGetSetSize(String key){
            try {
                return redisTemplate.opsForSet().size(key);
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
    
        /**
         * @param key 键
         * @return 移除的个数
         */
        public long setRemove(String key,Object... objects) {
            try {
                Long count = redisTemplate.opsForSet().remove(key,objects);
                return count;
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
    
    
    }
    
  8. 任务实现类(quick-quartz\src\main\java\com\cpdiem\demo\quickquartz\Jobs\Job1.java                                                     quick-quartz\src\main\java\com\cpdiem\demo\quickquartz\Jobs\Job2.java)
    package com.cpdiem.demo.quickquartz.Jobs;
    
    import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
    import org.springframework.scheduling.quartz.QuartzJobBean;
    import org.springframework.stereotype.Component;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     * 定时调度任务Job1.
     *
     *
     */
    @Component
    public class Job1 extends QuartzJobBean {
    
        @Override
        protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
    
            System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
            System.out.println("@@@@@@@@@@@@        "+(new SimpleDateFormat("yyyy-mm-dd HH:mm:ss.SSSSSS")).format(new Date())+"         @@@@@@@@@@@");
            System.out.println("@@@@@@@@@@@@@@@@@@@@@@@        Job1         @@@@@@@@@@@@@@@@@@@@@@");
            System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
            System.out.println();
        }
    }
    
    package com.cpdiem.demo.quickquartz.Jobs;
    
    import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.scheduling.quartz.QuartzJobBean;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     * 定时调度任务Job2.
     *
     */
    public class Job2 extends QuartzJobBean {
    
        @Override
        protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            System.out.println("==================================================================");
            System.out.println("===========        "+(new SimpleDateFormat("yyyy-mm-dd HH:mm:ss.SSSSSS")).format(new Date())+"         ============");
            System.out.println("=======================        Job2         ======================");
            System.out.println("==================================================================");
            System.out.println();
        }
    }
    
  9. redis配置类(quick-quartz\src\main\java\com\cpdiem\demo\quickquartz\config\RedisConfig.java)
    package com.cpdiem.demo.quickquartz.config;
    
    import com.fasterxml.jackson.annotation.JsonAutoDetect;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.context.annotation.Bean;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    public class RedisConfig {
    
    
        @Bean
        @SuppressWarnings("all")
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    
            RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
    
            template.setConnectionFactory(factory);
    
            Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    
            ObjectMapper om = new ObjectMapper();
    
            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    
            om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    
            jackson2JsonRedisSerializer.setObjectMapper(om);
    
            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
    
            // key采用String的序列化方式
    
            template.setKeySerializer(stringRedisSerializer);
    
            // hash的key也采用String的序列化方式
    
            template.setHashKeySerializer(stringRedisSerializer);
    
            // value序列化方式采用jackson
    
            template.setValueSerializer(jackson2JsonRedisSerializer);
    
            // hash的value序列化方式采用jackson
    
            template.setHashValueSerializer(jackson2JsonRedisSerializer);
    
            template.afterPropertiesSet();
    
            return template;
    
        }
    }
    
  10. 任务调度类(quick-quartz\src\main\java\com\cpdiem\demo\quickquartz\config\QuartzSchedulerConfig.java)
    package com.cpdiem.demo.quickquartz.config;
    
    
    import com.cpdiem.demo.quickquartz.Jobs.Job1;
    import com.cpdiem.demo.quickquartz.Jobs.Job2;
    import com.cpdiem.demo.quickquartz.utils.PropertiesUtils;
    import com.cpdiem.demo.quickquartz.utils.RedisUtils;
    import org.quartz.JobDetail;
    import org.quartz.Trigger;
    import org.quartz.spi.JobFactory;
    import org.quartz.spi.TriggerFiredBundle;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
    import org.springframework.beans.factory.config.PropertiesFactoryBean;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
    import org.springframework.scheduling.quartz.JobDetailFactoryBean;
    import org.springframework.scheduling.quartz.SchedulerFactoryBean;
    import org.springframework.scheduling.quartz.SpringBeanJobFactory;
    
    
    import javax.sql.DataSource;
    import java.io.IOException;
    import java.util.Properties;
    
    /**
     * Quartz调度配置类。
     *
     *
     */
    @Configuration
    public class QuartzSchedulerConfig {
    
        @Autowired
        private DataSource dataSource;
        @Autowired
        private RedisUtils redisUtils;
    
        private static final Logger Logger = LoggerFactory.getLogger(QuartzSchedulerConfig.class);
        private static final String QUARTZ_PROPERTIES_NAME = "/config/quartz.properties";
    
        @Bean
        public JobFactory jobFactory(ApplicationContext applicationContext) {
            AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
            jobFactory.setApplicationContext(applicationContext);
            return jobFactory;
        }
    
        @Bean
        public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory, Trigger[] triggers, JobDetail[]
                jobDetails) {
            SchedulerFactoryBean factoryBean = new SchedulerFactoryBean();
            try {
                factoryBean.setQuartzProperties(quartzProperties());
                factoryBean.setDataSource(dataSource);
                factoryBean.setJobFactory(jobFactory);
                factoryBean.setTriggers(triggers);
                factoryBean.setJobDetails(jobDetails);
                factoryBean.setOverwriteExistingJobs(true);
                factoryBean.setStartupDelay(10);
                factoryBean.setWaitForJobsToCompleteOnShutdown(true);
            } catch (Exception e) {
                Logger.error("加载 {} 配置文件失败.", QUARTZ_PROPERTIES_NAME, e);
                throw new RuntimeException("加载配置文件失败", e);
            }
            tempSetTriggers(triggers);
            return factoryBean;
        }
    
        void tempSetTriggers(Trigger[] triggers){
            if(triggers.length>0){
                for(Trigger trigger : triggers){
                    redisUtils.sSet("Triggers",trigger.getKey().getName());
                }
            }
        }
    
    
        public Properties quartzProperties() throws IOException {
            PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
            propertiesFactoryBean.setLocation(new ClassPathResource(QUARTZ_PROPERTIES_NAME));
            propertiesFactoryBean.afterPropertiesSet();
            return propertiesFactoryBean.getObject();
        }
    
        class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
    
            private transient AutowireCapableBeanFactory beanFactory;
    
            @Override
            public void setApplicationContext(final ApplicationContext context) {
                beanFactory = context.getAutowireCapableBeanFactory();
            }
    
            @Override
            protected Object createJobInstance(final TriggerFiredBundle bundle)
                    throws Exception {
                final Object job = super.createJobInstance(bundle);
                beanFactory.autowireBean(job);
                return job;
            }
        }
    
        @Bean(name = "job1Trigger")
        public CronTriggerFactoryBean job1Trigger(@Qualifier("job1Detail") JobDetail jobDetail) {
            CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
            cronTriggerFactoryBean.setJobDetail(jobDetail);
            cronTriggerFactoryBean.setCronExpression(PropertiesUtils.getProperty("quartz.timer1"));
            cronTriggerFactoryBean.setGroup("first");
            cronTriggerFactoryBean.setName("job1Trigger");
            return cronTriggerFactoryBean;
        }
    
        @Bean(name = "job1Detail")
        public JobDetailFactoryBean job1Detail() {
            JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean();
            jobDetailFactoryBean.setJobClass(Job1.class);
            jobDetailFactoryBean.setDurability(true);
            return jobDetailFactoryBean;
        }
    
        @Bean(name = "job2Trigger")
        public CronTriggerFactoryBean job2Trigger(@Qualifier("job2Detail") JobDetail jobDetail) {
            CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
            cronTriggerFactoryBean.setJobDetail(jobDetail);
            cronTriggerFactoryBean.setCronExpression(PropertiesUtils.getProperty("quartz.timer2"));
            return cronTriggerFactoryBean;
        }
    
        @Bean(name = "job2Detail")
        public JobDetailFactoryBean job2Detail() {
            JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean();
            jobDetailFactoryBean.setJobClass(Job2.class);
            jobDetailFactoryBean.setDurability(true);
            return jobDetailFactoryBean;
        }
    
    //    @Bean(name = "job3Trigger")
    //    public CronTriggerFactoryBean job3Trigger(@Qualifier("job1Detail") JobDetail jobDetail) {
    //        CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
    //        cronTriggerFactoryBean.setJobDetail(jobDetail);
    //        cronTriggerFactoryBean.setCronExpression(PropertiesUtils.getProperty("quartz.timer3"));
    //        return cronTriggerFactoryBean;
    //    }
    
    
    }
    

任务辅助类(quick-quartz\src\main\java\com\cpdiem\demo\quickquartz\service\JobTaskService.java)


package com.cpdiem.demo.quickquartz.service;

import com.cpdiem.demo.quickquartz.utils.RedisUtils;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Component
public class JobTaskService {

    @Autowired
    private SchedulerFactoryBean schedulerFactoryBean;
    @Autowired
    private RedisUtils redisUtils;

    @PostConstruct
    private void updateJobTaskBeforeStarting(){

        Set<String> hashSet = new HashSet<String>();
        Set set = redisUtils.sGet("Triggers");
        if(set!=null && set.size()>0){
            for(Object obj : set){
                hashSet.add((String)obj);
            }
        }

        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        Set<JobKey> jobKeySet = null;
        try {
            jobKeySet = scheduler.getJobKeys(GroupMatcher.<JobKey>anyGroup());
            if(jobKeySet != null && jobKeySet.size()>0) {
                for (JobKey jk : jobKeySet) {
                    List<Trigger> triggers = (List<Trigger>) scheduler.getTriggersOfJob(jk);
                    if (triggers != null && triggers.size() > 0) {
                        for (Trigger trigger : triggers) {
                            if(!hashSet.contains(trigger.getKey().getName())){
                                scheduler.unscheduleJob(trigger.getKey());
                            }

                        }
                    }
                }
            }
            redisUtils.setRemove("Triggers",hashSet.toArray());
        } catch (SchedulerException e) {
            e.printStackTrace();
        }

    }
}

启动类

@SpringBootApplication
public class QuickQuartzApplication {

   public static void main(String[] args) {
      ConfigurableApplicationContext configurableApplicationContext =SpringApplication.run(QuickQuartzApplication.class, args);
      configurableApplicationContext.getEnvironment();
   }

   @Bean
   public PropertiesUtils PropertiesUtils(){
      PropertiesUtils propertiesUtils = new PropertiesUtils();
      propertiesUtils.setLocation(new ClassPathResource("/config/timers.properties"));
      return propertiesUtils;
   }
}

三、简要说明

factoryBean.setOverwriteExistingJobs(true);

该参数默认值为false。当应用启动时,quartz不会更新相关数据库表中的数据(如cron表达式,任务下次执行时间等等),调度器将根据数据库原有数据信息执行任务。

该参数设为true时,数据库数据会被重新设置。如果应用重启、宕机期间有任务错过,将不触发misfire。

quartz在创建完scheduler时,会同时维护数据库及本次应用创建的任务,也就是说quartz只会进行新增、更新操作,不会执行删除操作。因此,对于不再使用的触发器,需自行手动删除(任务辅助类JobTaskService存在的意义,当然它可以做得更多)。

factoryBean.setStartupDelay(10);

设置调度中心在应用启动后n秒后开始运行。

factoryBean.setWaitForJobsToCompleteOnShutdown(true);

该参数用于设置当服务正常关闭时,会等所有任务执行完毕后再行关闭。但仍存在不正常关闭的bug。

四、参考资料

  1. quartz API  http://www.quartz-scheduler.org/api/2.2.1/index.html
  2. spring API  https://docs.spring.io/spring/docs/4.3.0.BUILD-SNAPSHOT/javadoc-api/
  3. 简单 Quartz-Cluster 微服务  https://blog.csdn.net/ylimh_hmily/article/details/78825329#comments
  4. quartz (从原理到应用)详解篇  https://my.oschina.net/songhongxu/blog/802574

猜你喜欢

转载自blog.csdn.net/cpdiem/article/details/84928825