springboot2.0與quartz的集成實現分佈式高可用,多個調度器模式

    本文完成了springboot2.0與調度工具quartz的集成,完成高可用,將狀態保存到數據initialize-schema: never這個有三個值,有興趣的可以去查一下,是關於在數據庫中創建quartz表的配置

    在項目啓動時,將配置文件的job加入到調度服務裏面,同時調度服務把它記錄到數據庫中,然後任務在觸發點執行,調度服務回去修改相應狀態,通過數據庫鎖的機制,完成分佈式高可用

1.pom.xml依賴加入

     

<!-- quartz -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

      2.添加application.yml文件配置

             

spring: 
    profiles:
      include:
        - quartz
    datasource:
      driverClassName: com.mysql.jdbc.Driver
      url:jdbc:mysql://localhost:3306/demoallowMultiQueries=true&amp;autoReconnect=true
      username: root
      pssword: root
      type: com.alibaba.druid.pool.DruidDataSource
      maxActive: 20
      initialSize: 1
      maxWait: 60000
      minIdle: 1
      timeBetweenEvictionRunsMillis: 60000
      minEvictableIdleTimeMillis: 300000
      validationQuery: select 'x'
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      poolPreparedStatements: true
      maxOpenPreparedStatements: 20
      filters: stat,wall,log4j
      useGlobalDataSourceStat: true
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=50
      druid:
        stat-view-servlet:
          enabled: true
          url-pattern: /druid/*
          login-username: admin
          login-password: aaa111
    quartz:
      jdbc:
        initialize-schema: never
      job-store-type: jdbc
      #相關屬性配置
      properties:
        org:
          quartz:
            scheduler:
              instanceName: DICMP_PARTNER_JOB
              instanceId: AUTO
            jobStore:
              class: org.quartz.impl.jdbcjobstore.JobStoreTX
              driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
              tablePrefix: qrtz_
              isClustered: true
              clusterCheckinInterval: 10000
              useProperties: false
            threadPool:
              class: org.quartz.simpl.SimpleThreadPool
              threadCount: 10
              threadPriority: 5
              threadsInheritContextClassLoaderOfInitializingThread: true
3.新建application-quartz.yml文件

 

4.編寫quartz配置類

 

@Component
@Data
@ConfigurationProperties
public class ScheduleJob implements Serializable {

    public static final String STATUS_RUNNING = "1";
    public static final String STATUS_NOT_RUNNING = "0";
    /**
     * 任務名稱
     */
    private String jobName;
    /**
     * 任務分組
     */
    private String jobGroup;
    /**
     * 任務狀態 是否啓動任務
     */
    private String jobStatus;
    /**
     * cron表達式
     */
    private String cronExpression;
    /**
     * spring bean
     */
    private String jobClass;
    /**
     * 任務調用的方法名
     */
    private String methodName;

    /**
     * 任務調用的方法傳入的參數,統一使用String
     */
    private Map parameters;
    /**
     * 任務是否有狀態
     */
    private boolean isConcurrent;
}

 

@Data
@ConfigurationProperties(prefix = "quartz")
@Component
public class QuartzConfig {
    private List<ScheduleJob> jobs = new ArrayList<ScheduleJob>();
}

5.編寫quartz執行入口

/**
    不允許並行執行
*/
@DisallowConcurrentExecution
public class QuartzJobFactoryDisallowConcurrentExecution implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        ScheduleJob scheduleJob = (ScheduleJob) context.getMergedJobDataMap().get("scheduleJob");
        TaskUtils.invokeMethod(scheduleJob);
    }
}
/** 允許並行執行 */
public class QuartzJobFactoryAllowConcurrentExecution implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        ScheduleJob scheduleJob = (ScheduleJob) context.getMergedJobDataMap().get("scheduleJob");
        TaskUtils.invokeMethod(scheduleJob);
    }
}

6.編寫quartz添加job調度類

@Component
public class ScheduleJobFactory {

    public final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private SchedulerFactoryBean schedulerFactoryBean;

    @Autowired
    private QuartzConfig quartzConfig;

    /**
     * 初始化任務
     * @param job
     */
    public void initJob(ScheduleJob job) throws Exception {
        if (job == null) {
            return;
        }
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        TriggerKey triggerKey = TriggerKey.triggerKey(job.getJobName(), job.getJobGroup());

        if (!ScheduleJob.STATUS_RUNNING.equals(job.getJobStatus())) {
            logger.info("刪除任務{}",job.getJobName());
            this.deleteJob(scheduler,triggerKey);
        } else {
            logger.info(scheduler + "添加任務:{}", JSONObject.toJSONString(job));
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            addJob(scheduler,triggerKey,job);
        }

    }

    /**
     * 增加定時任務job
     * @param scheduler 調度器
     * @param triggerKey 觸發器key
     * @param job 任務
     * @throws Exception
     */
    private void addJob(Scheduler scheduler,TriggerKey triggerKey,ScheduleJob job) throws Exception {
        CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        if (null == trigger) {
            // 不存在,創建一個
            setJobExistTrigger(scheduler,trigger,triggerKey,job);
        } else {
            setJobNoExistTrigger(scheduler,trigger,triggerKey,job);
        }
    }

    /**
     * 如果觸發器key不存在,設置job
     * @param scheduler 調度器
     * @param trigger 觸發器
     * @param triggerKey 觸發器key
     * @param job 任務
     * @throws Exception
     */
    private void setJobNoExistTrigger(Scheduler scheduler,CronTrigger trigger,TriggerKey triggerKey,ScheduleJob job)
        throws Exception {
        // Trigger已存在,那麼更新相應的定時設置
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
        // 按新的cronExpression表達式重新構建trigger
        trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
        // 按新的trigger重新設置job執行
        scheduler.rescheduleJob(triggerKey, trigger);
    }

    /**
     * 如果觸發器key存在,設置job
     * @param scheduler 調度器
     * @param trigger 觸發器
     * @param triggerKey 觸發器key
     * @param job 任務
     * @throws Exception
     */
    private void setJobExistTrigger(Scheduler scheduler,CronTrigger trigger,TriggerKey triggerKey,ScheduleJob job)
        throws  Exception{
        Class clazz = job.isConcurrent() ? QuartzJobFactoryAllowConcurrentExecution.class
                : QuartzJobFactoryDisallowConcurrentExecution.class;

        JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(job.getJobName(), job.getJobGroup()).build();
        jobDetail.getJobDataMap().put("scheduleJob", job);

        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());

        trigger = TriggerBuilder.newTrigger().withIdentity(job.getJobName(), job.getJobGroup())
                .withSchedule(scheduleBuilder).build();
        scheduler.scheduleJob(jobDetail, trigger);
    }

    /**
     * 刪除job
     * @param scheduler
     * @param triggerKey
     * @throws Exception
     */
    private void deleteJob(Scheduler scheduler,TriggerKey triggerKey) throws Exception{
        if (null != triggerKey) {
            scheduler.unscheduleJob(triggerKey);
        }
    }

    /**
     * 初始化job
     * @throws Exception
     */
    @PostConstruct
    public void init() {
        // 這裏獲取任務信息數據
        List<ScheduleJob> jobList = quartzConfig.getJobs();
        try {
            logger.info("任務初始化開始....");
            for (ScheduleJob job : jobList) {
                initJob(job);
            }
        } catch (Exception e) {
            logger.error("任務初始化異常",e);
        }

    }
}

7.反射工具類

public class TaskUtils {
    private static final Logger logger = LoggerFactory.getLogger(TaskUtils.class);

    /**
     * 通過反射調用scheduleJob中定義的方法
     * @param scheduleJob
     */
    public static void invokeMethod(ScheduleJob scheduleJob) {
        Object object = null;
        Class clazz;
        if (StringUtils.isNotBlank(scheduleJob.getJobClass())) {
            object = SpringUtil.getBean(scheduleJob.getJobClass());
        }
        if (object == null) {
            logger.error("任務名稱 = [" + scheduleJob.getJobName() + "]未啓動成功,請檢查是否配置正確!!!");
            return;
        }
        clazz = object.getClass();
        Method method = null;
        try {
            if (scheduleJob.getParameters() == null) {
                method = clazz.getDeclaredMethod(scheduleJob.getMethodName());
                method.invoke(object);
            } else {
                method = clazz.getDeclaredMethod(scheduleJob.getMethodName(), Map.class);
                method.invoke(object, scheduleJob.getParameters());
            }
        } catch (Exception e) {
            logger.error("job反射執行方法異常",e);
        }
    }
@Component
public class SpringUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringUtil.applicationContext == null) {
            SpringUtil.applicationContext = applicationContext;
        }
    }

    /**
     * 獲取applicationContext
     * @return
     */
    public static ApplicationContext getApplicationContext() {

        return applicationContext;
    }

    /**
     * 通過name獲取 Bean.
     * @param name
     * @return
     */
    public static Object getBean(String name) {

        return getApplicationContext().getBean(name);
    }

    /**
     * 通過class獲取Bean.
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> clazz) {

        return getApplicationContext().getBean(clazz);
    }

    /**
     * 通過name,以及Clazz返回指定的Bean
     * @param name
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(String name, Class<T> clazz) {

        return getApplicationContext().getBean(name, clazz);
    }

8.適應多個服務,需要多個調度器,修改調度器名稱

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章