springboot2.0 + quartz 分布式定时任务

因项目是集群部署,因此同样的定时任务,在集群中的每台机器都会执行,这样定时任务就会重复执行,不但会增加服务器的负担,还会因为定时任务重复执行造成额外的不可预期的错误。因此下面采用 spring-boot-starter-quartz 实现分布式任务部署。

使用步骤:

版本信息:
springboot:2.1.6
quartz:2.3.1

1、pom配置

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

2、application-quartz.yml配置

spring:
  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

在application.yml文件中将上面的文件配置进去

spring:
  profiles:
    # 运行环境 dev|test|prod,根据active动态加载application-active.yml配置文件
    active: dev,quartz

3、实现一个任务类

@Slf4j
@DisallowConcurrentExecution
public class Job extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        // 获取参数
        JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
        log.info("定时任务执行中。。。参数:"+jobDataMap.get("name").toString());
    } 
}

@DisallowConcurrentExecution的作用是 保证上一个任务执行完后,再去执行下一个任务,这里的任务是同一个任务,所以这里不必担心线程安全问题了。

4、实现对定时任务的操作

@Service
@Slf4j
public class ScheduleJobManage {
 @Autowired
 private Scheduler scheduler;
 @PostConstruct
 public void startScheduler() {
  try {
   // 开启调度器
   scheduler.start();
  } catch (SchedulerException e) {
   e.printStackTrace();
  }
 }
 /**
  * 使用simpleTrigger触发器触发简单任务
  * 
  * @param jobClass
  * @param jobName
  * @param jobGroupName
  * @param jobTime
  *            每隔多长时间执行一次任务
  * @param jobTimes
  *            执行几次 <0时不限制次数
  * @param jobData
  */
 public void saveSimpleJob(Class<? extends QuartzJobBean> jobClass, String jobName, String jobGroupName, int jobTime,
   int jobTimes, Map jobData) {
  log.info("新增任务");
  try {
   // 任务名称和组构成任务key
   JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName).build();
   // 设置job参数
   if (jobData != null && jobData.size() > 0) {
    jobDetail.getJobDataMap().putAll(jobData);
   }
   // 使用simpleTrigger规则
   Trigger trigger = null;
   if (jobTimes < 0) {
    trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName)
      .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(1).withIntervalInSeconds(jobTime))
      .startNow().build();
   } else {
    trigger = TriggerBuilder
      .newTrigger().withIdentity(jobName, jobGroupName).withSchedule(SimpleScheduleBuilder
        .repeatSecondlyForever(1).withIntervalInSeconds(jobTime).withRepeatCount(jobTimes))
      .startNow().build();
   }
   scheduler.scheduleJob(jobDetail, trigger);
  } catch (SchedulerException e) {
   e.printStackTrace();
  }
 }
 /**
  * 使用cornTrigger触发器
  * 
  * @param jobClass
  * @param jobName
  * @param jobGroupName
  * @param cron
  * @param jobData
  */
 public void saveCronJob(Class<? extends QuartzJobBean> jobClass, String jobName, String jobGroupName, String cron,
   Map jobData) {
  log.info("新增任务");
  try {
   // 任务名称和组构成任务key
   JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName).build();
   // 设置job参数
   if (jobData != null && jobData.size() > 0) {
    jobDetail.getJobDataMap().putAll(jobData);
   }
   // 使用cornTrigger规则
   // 触发器key
   Trigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName)
     .startAt(DateBuilder.futureDate(1, IntervalUnit.SECOND))
     .withSchedule(CronScheduleBuilder.cronSchedule(cron)).startNow().build();
   // 把作业和触发器注册到任务调度中
   scheduler.scheduleJob(jobDetail, trigger);
  } catch (SchedulerException e) {
   e.printStackTrace();
  }
 }
 /**
  * 修改cron时间表达式
  * 
  * @param jobName
  * @param jobGroupName
  * @param jobTime
  */
 public void updateJob(String jobName, String jobGroupName, String cron) {
  try {
   TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
   CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
   trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)
     .withSchedule(CronScheduleBuilder.cronSchedule(cron)).build();
   // 重启触发器
   scheduler.rescheduleJob(triggerKey, trigger);
  } catch (SchedulerException e) {
   e.printStackTrace();
  }
 }
 /**
  * 删除job
  * @param jobName
  * @param jobGroupName
  */
 public void deleteJob(String jobName, String jobGroupName) {
  log.info("删除任务");
  try {
   TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
   // 停止触发器
   scheduler.pauseTrigger(triggerKey);
   // 移除触发器
   scheduler.unscheduleJob(triggerKey);
   // 删除任务
   scheduler.deleteJob(new JobKey(jobName, jobGroupName));
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
 /**
  * 暂停任务
  * 
  * @param jobName
  * @param jobGroupName
  */
 public void pause(String jobName, String jobGroupName) {
  log.info("暂停任务");
  try {
   JobKey key = new JobKey(jobName, jobGroupName);
   scheduler.pauseJob(key);
  } catch (SchedulerException e) {
   e.printStackTrace();
  }
 }
 /**
  * 恢复任务
  * 
  * @param jobName
  * @param jobGroupName
  */
 public void resume(String jobName, String jobGroupName) {
  log.info("恢复任务");
  try {
   JobKey key = new JobKey(jobName, jobGroupName);
   scheduler.resumeJob(key);
  } catch (SchedulerException e) {
   e.printStackTrace();
  }
 }
 /**
  * 触发任务 立即执行
  * 
  * @param jobName
  * @param jobGroupName
  */
 public void trigger(String jobName, String jobGroupName) {
  log.info("触发任务");
  try {
   JobKey key = new JobKey(jobName, jobGroupName);
   scheduler.triggerJob(key);
  } catch (SchedulerException e) {
   e.printStackTrace();
  }
 }
 /**
  * 获取所有计划中的任务列表
  *
  * @return
  */
 public List<Map<String, Object>> queryAllJob() {
  List<Map<String, Object>> jobList = null;
  try {
   GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
   Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
   jobList = new ArrayList<Map<String, Object>>();
   for (JobKey jobKey : jobKeys) {
    List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
    for (Trigger trigger : triggers) {
     Map<String, Object> map = new HashMap<String, Object>();
     map.put("jobName", jobKey.getName());
     map.put("jobGroupName", jobKey.getGroup());
     map.put("description", "触发器:" + trigger.getKey());
     Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
     map.put("jobStatus", triggerState.name());
     if (trigger instanceof CronTrigger) {
      CronTrigger cronTrigger = (CronTrigger) trigger;
      String cronExpression = cronTrigger.getCronExpression();
      map.put("jobTime", cronExpression);
     }
     jobList.add(map);
    }
   }
  } catch (SchedulerException e) {
   e.printStackTrace();
  }
  return jobList;
 }
 /**
  * 获取所有正在运行的job
  *
  * @return
  */
 public List<Map<String, Object>> queryRunJob() {
  List<Map<String, Object>> jobList = null;
  try {
   List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();
   jobList = new ArrayList<Map<String, Object>>(executingJobs.size());
   for (JobExecutionContext executingJob : executingJobs) {
    Map<String, Object> map = new HashMap<String, Object>();
    JobDetail jobDetail = executingJob.getJobDetail();
    JobKey jobKey = jobDetail.getKey();
    Trigger trigger = executingJob.getTrigger();
    map.put("jobName", jobKey.getName());
    map.put("jobGroupName", jobKey.getGroup());
    map.put("description", "触发器:" + trigger.getKey());
    Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
    map.put("jobStatus", triggerState.name());
    if (trigger instanceof CronTrigger) {
     CronTrigger cronTrigger = (CronTrigger) trigger;
     String cronExpression = cronTrigger.getCronExpression();
     map.put("jobTime", cronExpression);
    }
    jobList.add(map);
   }
  } catch (SchedulerException e) {
   e.printStackTrace();
  }
  return jobList;
 }
}

5、测试

使用CommandLineRunner实现在应用初始化时初始化任务。需要实现其run()方法:

@Component
public class TestQuartzTask implements CommandLineRunner {
 @Autowired
 ScheduleJobManage scheduleJobManage;
 public void run(String... args) throws Exception {
  System.out.println(">>>>>>>>>>>>>>>服务启动执行,执行加载数据等操作<<<<<<<<<<<<<"); 
  HashMap<String,Object> map = new HashMap<String, Object>();
        map.put("name",1);     
        ScheduleJobEntity scheduleJobEntity = new ScheduleJobEntity();     
        scheduleJobManage.deleteJob("job", "test");
        scheduleJobManage.saveCronJob(Job.class, "job", "test", "0 * * * * ?", map);
        map.put("name",2);
        scheduleJobManage.deleteJob("job2", "test");
        scheduleJobManage.saveCronJob(Job.class, "job2", "test", "10 * * * * ?", map);
        map.put("name",3);
        scheduleJobManage.deleteJob("job3", "test2");
        scheduleJobManage.saveCronJob(Job.class, "job3", "test2", "15 * * * * ?", map);
 }
}

这里实例化了三个任务进去,同时开启两个端口模拟测试任务调度情况。
模拟服务器1:
在这里插入图片描述
模拟服务器2:
在这里插入图片描述
由上面运行情况可以看出3个任务在两台模拟服务器上同时只能有一个执行成功。

PS:quartz分布式任务调度是通过数据库悲观锁实现的(后边专门出一篇讲一下实现原理)。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值