因项目是集群部署,因此同样的定时任务,在集群中的每台机器都会执行,这样定时任务就会重复执行,不但会增加服务器的负担,还会因为定时任务重复执行造成额外的不可预期的错误。因此下面采用 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分布式任务调度是通过数据库悲观锁实现的(后边专门出一篇讲一下实现原理)。