SpringBoot 整合Quartz(集群)实现定时任务调度

如果对quartz集群原理和配置不太了解,大家可参考文章:Quartz集群原理及配置应用,这里我就不细说了

SpringBoot实现定时任务两种方式:

1、基于 spring的@Scheduled注解;

可参考文章:SpringBoot整合Quartz定时任务(基于注解方式),这种方式比较便捷,在单台服务部署情况下可使用,若部署多台机器,到了时间点,便会同时均开始这个执行定时任务(问题)。

2、使用Quartz(集群环境)实现:

2.1 Quzrtz集群的实现方式在于11张表,集群节点相互之间不通信,而是通过相同的数据库表来感知到另一节点的,因此Quartz集群依赖于数据库,必须先创建数据库表,数据表是官方提供的 ,有11张表,如下:

SpringBoot 整合Quartz(集群)实现定时任务调度_第1张图片

项目结构:

SpringBoot 整合Quartz(集群)实现定时任务调度_第2张图片

2.2 创建maven项目pom.xml添加依赖:




    4.0.0

    com.test.quartz
    quartz
    1.0-SNAPSHOT

    
        
            junit
            junit
            4.11
            test
        
        
            mysql
            mysql-connector-java
            8.0.13
        
        
            org.mybatis.generator
            mybatis-generator-core
            1.3.7
        
        
        
            org.springframework.boot
            spring-boot-devtools
            true
            2.1.0.RELEASE
        
        
            org.springframework.boot
            spring-boot-starter-actuator
            2.1.0.RELEASE
            
                
                    log4j-api
                    org.apache.logging.log4j
                
            
        
        
            org.springframework.boot
            spring-boot-starter-websocket
            2.1.0.RELEASE
            
                
                    log4j-api
                    org.apache.logging.log4j
                
                
                    spring-boot-starter-logging
                    org.springframework.boot
                
            
        
        
            org.springframework.boot
            spring-boot-starter-jdbc
            2.1.0.RELEASE
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
            2.1.0.RELEASE
        
        
        
            org.apache.commons
            commons-lang3
            3.4
        
        
            org.apache.httpcomponents
            httpclient
            4.5.2
        
        
            org.apache.curator
            curator-framework
            2.12.0
            
                
                    netty
                    io.netty
                
                
                    log4j
                    log4j
                
                
                    guava
                    com.google.guava
                
            
        
        
            org.springframework.boot
            spring-boot-starter-quartz
            2.1.0.RELEASE
        

        
            org.apache.logging.log4j
            log4j-api
            2.8.2
        

        
            org.apache.logging.log4j
            log4j-core
            2.8.2
        
    

    
        
        
        
        
        
        
        
        
        
        
        
        
        ${artifactId}-${version}
        
            
                org.jacoco
                jacoco-maven-plugin
                0.8.2
                
                    
                        prepare-agent
                        
                            prepare-agent
                        
                    
                    
                        report
                        prepare-package
                        
                            report
                        
                    
                    
                        post-unit-test
                        test
                        
                            report
                        
                        
                            target/jacoco.exec
                        
                    
                
                
                    
                        target/jacoco.exec
                    
                
            
            
            
                com.github.wvengen
                proguard-maven-plugin
                2.0.11
                
                    
                        
                        package
                        
                            proguard
                        
                    
                
                
                
                    
                    6.0.3
                    
                    
                        
                            
                                ${project.groupId}
                                quartz
                            
                        
                    
                    
                    ${project.build.finalName}.jar
                    
                    ${project.build.finalName}.jar
                    
                    true
                    
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                    
                    
                    
                        
                        ${java.home}/lib/rt.jar
                        
                    
                
                
                
                    
                    
                        net.sf.proguard
                        proguard-base
                        6.0.3
                    
                
            

            
            
                org.springframework.boot
                spring-boot-maven-plugin
                2.0.4.RELEASE
                
                    
                        
                            repackage
                        
                        
                            
                            com.test.quartz.application.CenterApplication
                            
                            
                        
                    
                
            
            
                org.apache.maven.plugins
                maven-compiler-plugin
                
                    6
                    6
                
            
        
    


 

2.3  创建springboot启动类(CenterApplication):

 

package com.test.quartz.application;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.stereotype.Component;

/**
 * @author [email protected]
 * @date 2019/2/13
 * @description: springboot启动类
 */
@SpringBootApplication
@ComponentScan(basePackages = "com.test.quartz.*")
@EnableScheduling
public class CenterApplication {
    private static final Logger LOG = LoggerFactory.getLogger(CenterApplication.class);

    private static final String START_PACKAGE = "com.test.quartz.";

    public static class CustomGenerator implements BeanNameGenerator {
        @Override
        public String generateBeanName(BeanDefinition beanDefinition, BeanDefinitionRegistry beanDefinitionRegistry) {
            String beanName = beanDefinition.getBeanClassName();
            if (beanName.startsWith(START_PACKAGE)) {
                return beanName;
            }
            try {
                Class aClass = Class.forName(beanName);
                Component annotation = aClass.getAnnotation(Component.class);
                if (annotation == null) {
                    return beanName;
                }
                String value = annotation.value();
                if (StringUtils.isNotBlank(value)) {
                    return value;
                }
            } catch (Exception e) {
                LOG.error("generateBeanName method throw a exception", e);
            }
            return beanName;
        }
    }


    public static void main(String... args) {
        ApplicationContext ctx = new SpringApplicationBuilder(CenterApplication.class)
                .beanNameGenerator(new CustomGenerator())
                .run(args);
        String[] activeProfiles = ctx.getEnvironment().getActiveProfiles();
        for (String profile : activeProfiles) {
            LOG.info("Spring Boot 使用profile为: application-{}.yml", profile);
        }
    }
}


2.4   配置文件:

application.yml 

spring: 
  profiles: 
    active: win
    #active: linux

application-win.yml(window上的配置,一般喜欢将window和linux分开来写,后期部署时方便切换,liunx用不上暂时就不放了):

server:
  port: 7008

logging:
  config: classpath:properties/logback-spring.xml
  #config: classpath:properties/logback-spring.xml

charset:
  encoding: utf-8
  
spring:
  quartz:
    job-store-type: jdbc

 

日志文件logback-spring.xml ,通过在配置文件中指定生效的环境和日志的级别 :



    
    
        
    
    

 

QuartzConfiguration配置文件:org.quartz.jobStore.isClustered 是否启用集群,设置为true

package com.test.quartz.configuration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.quartz.SchedulerFactoryBeanCustomizer;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import javax.sql.DataSource;
import java.util.Properties;

/**
 * @author [email protected]
 * @date 2019/2/13
 * @description: quartz配置
 */
@Configuration
public class QuartzConfiguration implements SchedulerFactoryBeanCustomizer {

    @Autowired
    DataSource dataSource;

    @Override
    public void customize(SchedulerFactoryBean schedulerFactoryBean) {
        schedulerFactoryBean.setQuartzProperties(quartzProperties());
        schedulerFactoryBean.setStartupDelay(2);
        schedulerFactoryBean.setAutoStartup(true);
        schedulerFactoryBean.setDataSource(dataSource);
        //如果这个覆盖配置为false,quratz启动以后将以数据库的数据为准,配置文件的修改不起作用。
        schedulerFactoryBean.setOverwriteExistingJobs(true);
    }

    /**
     * 设置参数
     *
     * @return quartz参数
     */
    private Properties quartzProperties() {
        Properties prop = new Properties();
        // 调度标识名 集群中每一个实例都必须使用相同的名称
        prop.put("org.quartz.scheduler.instanceName", "quartz-01");
        // ID设置为自动获取 每一个必须不同
        prop.put("org.quartz.scheduler.instanceId", "AUTO");
        // 数据保存方式为持久化
        prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
        // 数据库方言
        prop.put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.StdJDBCDelegate");
        // 表前缀
        prop.put("org.quartz.jobStore.tablePrefix", "t_jk_qrtz_");
        // 是否启用集群功能
        prop.put("org.quartz.jobStore.isClustered", "true");
        // 设置一个频度(毫秒),用于实例报告给集群中的其他实例。这会影响到侦测失败实例的敏捷度。它只用于设置了 isClustered 为 true 的时候。
        prop.put("org.quartz.jobStore.clusterCheckinInterval", "20000");
        // 这是 JobStore 能处理的错过触发的 Trigger 的最大数量。处理太多(超过两打) 很快会导致数据库表被锁定够长的时间,这样就妨碍了触发别的(还未错过触发) trigger 执行的性能。
        prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");
        // 当前时间与下一次执行的时间差大于改值时认为missFire(错过触发),根据missFire原则处理任务;若小于该值直接执行任务。默认60000(60秒)
        prop.put("org.quartz.jobStore.misfireThreshold", "120000");
        // 值为 True 时告诉 Quartz (当使用 JobStoreTX 或 CMT 时),调用 JDBC 连接的 setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE) 方法,设置事务隔离级别为串行
        // 这能助于防止某些数据库在高负荷和长事物时的锁超时。
        prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true");
        // 执行任务的线程池配置
        prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
        prop.put("org.quartz.threadPool.threadCount", "10");
        prop.put("org.quartz.threadPool.threadPriority", "5");
        // 指定Quartz生成的线程是否继承初始化线程的上下文类加载器
        prop.put("org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread", "true");
        // 跳过更新检查
        prop.put("org.quartz.scheduler.skipUpdateCheck", true);
        //quartz 插件配置
        prop.put("org.quartz.plugin.triggHistory.class", "org.quartz.plugins.history.LoggingJobHistoryPlugin");
        prop.put("org.quartz.plugin.shutdownhook.class", "org.quartz.plugins.management.ShutdownHookPlugin");
        prop.put("org.quartz.plugin.shutdownhook.cleanShutdown", "true");
        return prop;
    }
}

 

mysql配置文件:MysqlConfig

package com.test.quartz.configuration;

import com.zaxxer.hikari.HikariDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;

/**
 * @author [email protected]
 * @date 2019/2/13
 * @description: mysql连接配置
 */
@Component
public class MysqlConfig {

    @Bean
    public DataSource dataSource() {

        HikariDataSource hikariDataSource = new HikariDataSource();
        hikariDataSource.setJdbcUrl("jdbc:mysql://localhost:3306/hiseejk?characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai");
        hikariDataSource.setUsername("root");
        hikariDataSource.setPassword("root");
        hikariDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        hikariDataSource.setPoolName("hiseejk");
//      连接超时时间
        hikariDataSource.setConnectionTimeout(30000);
//      最大池大小
        hikariDataSource.setMaximumPoolSize(20);
//      默认连接数
        hikariDataSource.setMinimumIdle(10);
        hikariDataSource.setIdleTimeout(600000);
        hikariDataSource.setMaxLifetime(1800000);

        return hikariDataSource;
    }
}

任务实体类:

package com.test.quartz.quartz;

import java.util.Map;

/**
 * @author [email protected]
 * @date 2019/2/13
 * @description: 任务实体类
 */
public class JobView {
    private String name;
    private String group;
    private String cronExpression;

    private String nextFireTime;
    private String status;
    Map params;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGroup() {
        return group;
    }

    public void setGroup(String group) {
        this.group = group;
    }

    public String getCronExpression() {
        return cronExpression;
    }

    public void setCronExpression(String cronExpression) {
        this.cronExpression = cronExpression;
    }

    public String getNextFireTime() {
        return nextFireTime;
    }

    public void setNextFireTime(String nextFireTime) {
        this.nextFireTime = nextFireTime;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public Map getParams() {
        return params;
    }

    public void setParams(Map params) {
        this.params = params;
    }
}

 

任务工具类QuartzJobUtils:

package com.test.quartz.quartz;

import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.curator.shaded.com.google.common.collect.Lists;
import org.apache.curator.shaded.com.google.common.collect.Maps;
import org.apache.http.client.utils.DateUtils;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.quartz.QuartzJobBean;

import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @author [email protected]
 * @date 2019/2/13
 * @description:
 */
public class QuartzJobUtils {
    private static final Logger LOG = LoggerFactory.getLogger(QuartzJobUtils.class);

    private QuartzJobUtils() {
        super();
    }

    /**
     * 创建quartz定时任务
     *
     * @param scheduler      调度器
     * @param jobClass       任务class,必须继承QuartzJobBean
     * @param cronExpression cron表达式
     * @param jobDataMap     任务信息
     */
    public static void createJob(Scheduler scheduler, String name, Class jobClass, String cronExpression, JobDataMap jobDataMap) {
        //任务所属分组
        String group = "TEST";
        LOG.info("----createJob start ,name:{},group:{}", name, group);
        if (exist(scheduler, name, group)) {
            LOG.info("----createJob fail ,job already existed,name:{},group:{}", name, group);
            return;
        }
        //cron表达式封装
        //missfire处理 withMisfireHandlingInstructionDoNothing 错过触发时间时,不执行执行的,等待下一个执行时间
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
        //创建任务
        JobDetail jobDetail;
        if (jobDataMap != null) {
            //requestRecovery(true)指在集群中,一个scheduler执行job失败,将会被另外一个scheduler执行
            jobDetail = JobBuilder.newJob(jobClass).withIdentity(name, group).usingJobData(jobDataMap).requestRecovery(true).build();
        } else {
            jobDetail = JobBuilder.newJob(jobClass).withIdentity(name, group).requestRecovery(true).build();
        }
        //创建任务触发器
        Trigger trigger = TriggerBuilder.newTrigger().withIdentity(name, group).withSchedule(scheduleBuilder).build();
        //将触发器与任务绑定到调度器内
        try {
            Date firstFireTime = scheduler.scheduleJob(jobDetail, trigger);
            LOG.info("----createJob success,firstFireTime:{}", DateFormatUtils.format(firstFireTime, "yyyy-MM-dd HH:mm:ss"));
        } catch (SchedulerException e) {
            LOG.error("-----createJob exception", e);
        }
    }

    /**
     * 创建quartz定时任务
     *
     * @param scheduler      调度器
     * @param jobClass       任务class,必须继承QuartzJobBean
     * @param cronExpression cron表达式
     */
    public static void createJob(Scheduler scheduler, String name, Class jobClass, String cronExpression) {
        createJob(scheduler, name, jobClass, cronExpression, null);
    }

    public static void close(Scheduler scheduler, JobExecutionContext context) {
        TriggerKey triggerKey = context.getTrigger().getKey();
        JobKey jobKey = context.getJobDetail().getKey();
        LOG.info("----quartzJob close,jobName:{}", jobKey.getName());
        try {
            // 停止触发器
            scheduler.pauseTrigger(triggerKey);
            // 移除触发器
            scheduler.unscheduleJob(triggerKey);
            // 删除任务
            scheduler.deleteJob(jobKey);
            LOG.info("----quartzJob success");
        } catch (SchedulerException e) {
            LOG.error("----quartzJob close exception", e);
        }
    }

    /**
     * 判断任务是否存在
     *
     * @param scheduler 调度器
     * @param name      任务名称
     * @param group     任务分组
     * @return true==存在 false==不存在
     */
    public static boolean exist(Scheduler scheduler, String name, String group) {
        JobDetail jobDetail;
        JobKey jobKey = new JobKey(name, group);
        TriggerKey triggerKey = new TriggerKey(name, group);
        try {
            jobDetail = scheduler.getJobDetail(jobKey);
            if (jobDetail != null) {
                if (Trigger.TriggerState.ERROR.equals(scheduler.getTriggerState(triggerKey))) {
                    LOG.info("-----定时任务状态异常,恢复状态,job name :{}", name);
                    scheduler.resumeJob(jobKey);
                }
                return true;
            }
        } catch (SchedulerException e) {
            LOG.error("-----exist exception", e);
        }
        return false;
    }

    /**
     * 获取定时任务列表
     *
     * @param scheduler 任务调度器
     * @return
     */
    public static List jobs(Scheduler scheduler) {
        List jobViews = Lists.newArrayList();
        try {
            for (String groupName : scheduler.getJobGroupNames()) {
                for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName))) {
                    String jobName = jobKey.getName();
                    String jobGroup = jobKey.getGroup();

                    // name and group
                    JobView jobView = new JobView();
                    jobView.setName(jobName);
                    jobView.setGroup(jobGroup);

                    // params
                    JobDetail qJobDetail = scheduler.getJobDetail(jobKey);
                    if (null != qJobDetail.getJobDataMap()) {
                        Map params = Maps.newHashMap();
                        params.putAll(qJobDetail.getJobDataMap());
                        jobView.setParams(params);
                    }
                    List triggers = scheduler.getTriggersOfJob(jobKey);
                    // 应该只有一个
                    for (Trigger trigger : triggers) {
                        trigger.getNextFireTime();
                        if (trigger instanceof CronTrigger) {
                            CronTrigger cronTrigger = (CronTrigger) trigger;
                            String cronExpr = cronTrigger.getCronExpression();
                            jobView.setCronExpression(cronExpr);
                        }
                        jobView.setNextFireTime(DateUtils.formatDate(trigger.getNextFireTime(), "yyyy-MM-dd HH:mm:ss"));
                        Trigger.TriggerState state = scheduler.getTriggerState(trigger.getKey());
                        jobView.setStatus(state.name());
                    }
                    jobViews.add(jobView);
                }
            }
        } catch (Exception e) {
            return Collections.emptyList();
        }
        return jobViews;
    }
}

 

创建具体任务类(写需要实现的一些逻辑):

package com.test.quartz.quartz;

import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.quartz.QuartzJobBean;

/**
 * @author [email protected]
 * @date 2019/2/13
 * @description: 具体任务类
 */
@DisallowConcurrentExecution
public class TestQuartzJob extends QuartzJobBean {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestQuartzJob.class);

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) {
        LOGGER.info("-----TestQuartzJob start");
      //这里写具体的实现业务逻辑
        try {
         //线程睡眠是因为需要测试,防止程序过快执行
            Thread.sleep(5000);
            System.out.println("任务执行了" + "================================");

        } catch (Exception e) {
            LOGGER.error("休眠时间异常", e);
        }

    }

}

 

最后再将所有任务初始化:QuartzJobManager

package com.test.quartz.quartz;

import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * @author [email protected]
 * @date 2019/2/13
 * @description: 初始化任务
 */
@Component
public class QuartzJobManager {
    @Autowired
    private Scheduler scheduler;


    /**
     * 初始化任务的创建
     * 判断任务是否存在,不存在则创建
     */
    @PostConstruct
    public void initJob() {
       //每分钟执行一次
        QuartzJobUtils.createJob(scheduler, TestQuartzJob.class.getSimpleName(), TestQuartzJob.class,
                "0 0/1 * * * ? ");
    }
}

执行结果:

若是部署多台机器,到了时间点,只有一台会执行,其他不会执行。(测试方案:修改tamcat端口号,启动多台服务)

若多个节点其中一个scheduler执行job失败,将会被另外一个scheduler执行。(测试方案:修改tamcat端口号,启动多台服务,将正在执行这个任务的服务关掉)

问题:

任务节点部署在不同服务器时间同步问题(暂未解决)

代码地址:https://download.csdn.net/download/qq_33914912/10957062 (积分默认的,我想改成免费的,如果有的会的教下我去改)

 

你可能感兴趣的:(java-开发学习)