本文完成了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&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.適應多個服務,需要多個調度器,修改調度器名稱