Quartz是一個徹底由java編寫的開源做業調度框架。不要讓做業調度這個術語嚇着你。儘管Quartz框架整合了許多額外功能, 但就其簡易形式看,你會發現它易用得簡直讓人受不了!。簡單地建立一個實現org.quartz.Job接口的java類。Job接口包含惟一的方法:java
public void execute(JobExecutionContext context) throws JobExecutionException;mysql
在你的Job接口實現類裏面,添加一些邏輯到execute()方法。一旦你配置好Job實現類並設定好調度時間表,Quartz將密切注意剩餘時間。當調度程序肯定該是通知你的做業的時候,Quartz框架將調用你Job實現類(做業類)上的execute()方法並容許作它該作的事情。無需報告任何東西給調度器或調用任何特定的東西。僅僅執行任務和結束任務便可。若是配置你的做業在隨後再次被調用,Quartz框架將在恰當的時間再次調用它。linux
單機模式下的定時任務調用很簡單,有不少可實現的方案,這裏很少說了,例如spring schedule,java timer等。web
這裏說一下集羣部署的狀況下,定時任務的使用。這種狀況下,quartz是一個比較好的選擇。簡單,穩定。spring
想象一下,如今有 A , B , C 3 臺機器同時做爲集羣服務器對外統一提供 SERVICE :sql
A , B , C 3 臺機器上各有一個 QUARTZ Job,它們會按照即定的 SCHEDULE 自動執行各自的任務。數據庫
先不說實現什麼功能,這樣的架構有點像多線程。因爲三臺SERVER 裏都有 QUARTZ ,所以會存在重複處理 TASK 的現象。apache
通常外面的解決方案是隻在一臺 服務器上裝 QUARTZ ,其它兩臺不裝,這樣的話其實就是單機了,quartz存在單點問題,一旦裝有quartz的服務器宕了。服務就沒法提供了。json
固然還有其餘一些解決方案,無非就是改 QUARTZ JOB 的代碼了,這對程序開發人員來講比較痛苦;windows
而quartz自己提供了很好的集羣方案。下面咱們來講一下在spring boot下的集成:
quartz集羣須要數據庫的支持(JobStore TX或者JobStoreCMT),從本質上來講,是使集羣上的每個節點經過共享同一個數據庫來工做的
到quartz官網下載最新的包:http://www.quartz-scheduler.org/downloads/
解壓後,能夠看到結構目錄。在\docs\dbTables下選擇合適你數據庫的SQL執行文件,建立quartz集羣須要的表(共11張表)
找到本身使用的數據庫腳本文件執行
數據庫中對應表,注意:默認狀況,在windows環境下,mysql表名不區分大小寫,linux下區分大小寫
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.2.3</version>
</dependency>
注:若是嫌須要額外配置quart數據源很煩,也能夠共用你項目配置的數據庫連接,這樣每次更換數據庫鏈接,就不須要額外在修改。
直接使用springboot注入的的datasource的內容配置quartz的數據庫鏈接
quartz屬性配置能夠讀取配置文件讀取,我這裏沒在配置文件讀取,直接寫在代碼裏測試的,直接新建一個配置文件裏面寫quartz的配置內容,而後經過springboot注入屬性進來。
package com.kerry.config; import java.io.IOException; import java.util.Properties; import javax.sql.DataSource; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.quartz.SchedulerFactoryBean; /** * 分佈式定時任務管理配置 * @author kerry * @date 2018-05-09 11:36:21 */ @Configuration //@ConditionalOnProperty(prefix = "qybd", name = "quartz-open", havingValue = "true") public class QuartzConfig{ @Autowired DataSource dataSource; @Bean public SchedulerFactoryBean schedulerFactoryBean(QuartzJobFactory myJobFactory) throws Exception { SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); schedulerFactoryBean.setDataSource(dataSource); //使job實例支持spring 容器管理 schedulerFactoryBean.setOverwriteExistingJobs(true); schedulerFactoryBean.setJobFactory(myJobFactory); schedulerFactoryBean.setQuartzProperties(quartzProperties()); // 延遲10s啓動quartz schedulerFactoryBean.setStartupDelay(10); return schedulerFactoryBean; } @Bean public Scheduler scheduler(SchedulerFactoryBean schedulerFactoryBean) throws IOException, SchedulerException { // SchedulerFactory schedulerFactory = new StdSchedulerFactory(quartzProperties()); // Scheduler scheduler = schedulerFactory.getScheduler(); // scheduler.start();//初始化bean並啓動scheduler Scheduler scheduler = schedulerFactoryBean.getScheduler(); scheduler.start(); return scheduler; } /** * 設置quartz屬性 */ public Properties quartzProperties() throws IOException { Properties prop = new Properties(); prop.put("quartz.scheduler.instanceName", "ServerScheduler"); prop.put("org.quartz.scheduler.instanceId", "AUTO"); prop.put("org.quartz.scheduler.skipUpdateCheck", "true"); prop.put("org.quartz.scheduler.instanceId", "NON_CLUSTERED"); prop.put("org.quartz.scheduler.jobFactory.class", "org.quartz.simpl.SimpleJobFactory"); 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.dataSource", "quartzDataSource"); prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_"); prop.put("org.quartz.jobStore.isClustered", "true"); prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool"); prop.put("org.quartz.threadPool.threadCount", "5"); // prop.put("org.quartz.dataSource.quartzDataSource.driver", druidProperties.getDriverClassName()); // prop.put("org.quartz.dataSource.quartzDataSource.URL", druidProperties.getUrl()); // prop.put("org.quartz.dataSource.quartzDataSource.user", druidProperties.getUsername()); // prop.put("org.quartz.dataSource.quartzDataSource.password", druidProperties.getPassword()); // prop.put("org.quartz.dataSource.quartzDataSource.maxConnections", druidProperties.getMaxActive()); return prop; } }
注意上面的schedulerFactoryBean.setJobFactory(myJobFactory); //這個myJobFactory是自定義配置的一個類,若是這裏不配置這個jobFactory,下面的那個CtripScenicTask會爲空,獲取不了注入對象
@Component
public class CtripScenicJob implements Job{
private Logger logger = LoggerFactory.getLogger(CtripScenicJob.class);
@Autowired
private CtripScenicTask ctripScenicTask;
這個類主要解決spring管理的Quartz job裏面注入不了其餘bean
package com.kerry.config; import org.quartz.spi.TriggerFiredBundle; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.scheduling.quartz.AdaptableJobFactory; import org.springframework.stereotype.Component; @Component public class QuartzJobFactory extends AdaptableJobFactory { //這個對象Spring會幫咱們自動注入進來,也屬於Spring技術範疇. @Autowired private AutowireCapableBeanFactory capableBeanFactory; protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { //調用父類的方法 Object jobInstance = super.createJobInstance(bundle); //進行注入,這屬於Spring的技術,不清楚的能夠查看Spring的API. capableBeanFactory.autowireBean(jobInstance); return jobInstance; } }
接口類
package com.kerry.modular.biz.service; import java.util.List; import org.quartz.SchedulerException; import com.kerry.modular.biz.model.TaskInfo; public interface TaskService { List<TaskInfo> list(); void addJob(TaskInfo info); void edit(TaskInfo info); void delete(String jobName, String jobGroup); void pause(String jobName, String jobGroup); void resume(String jobName, String jobGroup); boolean checkExists(String jobName, String jobGroup)throws SchedulerException; }
實現類
package com.kerry.modular.biz.service; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.List; import org.apache.commons.lang3.time.DateFormatUtils; import org.quartz.CronScheduleBuilder; import org.quartz.CronTrigger; import org.quartz.Job; import org.quartz.JobBuilder; import org.quartz.JobDetail; import org.quartz.JobKey; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.Trigger; import org.quartz.TriggerBuilder; import org.quartz.TriggerKey; import org.quartz.impl.matchers.GroupMatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.kerry.modular.biz.model.TaskInfo; @Service public class TaskServiceImpl implements TaskService { private Logger logger = LoggerFactory.getLogger(TaskServiceImpl.class); @Autowired(required=false) private Scheduler scheduler; /** * 全部任務列表 */ public List<TaskInfo> list(){ List<TaskInfo> list = new ArrayList<>(); try { for(String groupJob: scheduler.getJobGroupNames()){ for(JobKey jobKey: scheduler.getJobKeys(GroupMatcher.<JobKey>groupEquals(groupJob))){ List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey); for (Trigger trigger: triggers) { Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey()); JobDetail jobDetail = scheduler.getJobDetail(jobKey); String cronExpression = "", createTime = ""; if (trigger instanceof CronTrigger) { CronTrigger cronTrigger = (CronTrigger) trigger; cronExpression = cronTrigger.getCronExpression(); createTime = cronTrigger.getDescription(); } TaskInfo info = new TaskInfo(); info.setJobName(jobKey.getName()); info.setJobGroup(jobKey.getGroup()); info.setJobDescription(jobDetail.getDescription()); info.setJobStatus(triggerState.name()); info.setCronExpression(cronExpression); info.setCreateTime(createTime); list.add(info); } } } } catch (SchedulerException e) { e.printStackTrace(); } return list; } /** * 保存定時任務 * @param info */ @SuppressWarnings("unchecked") public void addJob(TaskInfo info) { String jobName = info.getJobName(), jobGroup = info.getJobGroup(), cronExpression = info.getCronExpression(), jobDescription = info.getJobDescription(), createTime = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"); try { if (checkExists(jobName, jobGroup)) { logger.info("add job fail, job already exist, jobGroup:{}, jobName:{}", jobGroup, jobName); } TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup); JobKey jobKey = JobKey.jobKey(jobName, jobGroup); CronScheduleBuilder schedBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing(); CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withDescription(createTime).withSchedule(schedBuilder).build(); Class<? extends Job> clazz = (Class<? extends Job>)Class.forName(jobName); JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(jobKey).withDescription(jobDescription).build(); scheduler.scheduleJob(jobDetail, trigger); } catch (SchedulerException | ClassNotFoundException e) { logger.error("類名不存在或執行表達式錯誤,exception:{}",e.getMessage()); } } /** * 修改定時任務 * @param info */ public void edit(TaskInfo info) { String jobName = info.getJobName(), jobGroup = info.getJobGroup(), cronExpression = info.getCronExpression(), jobDescription = info.getJobDescription(), createTime = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"); try { if (!checkExists(jobName, jobGroup)) { logger.info("edit job fail, job is not exist, jobGroup:{}, jobName:{}", jobGroup, jobName); } TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup); JobKey jobKey = new JobKey(jobName, jobGroup); CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing(); CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withDescription(createTime).withSchedule(cronScheduleBuilder).build(); JobDetail jobDetail = scheduler.getJobDetail(jobKey); jobDetail.getJobBuilder().withDescription(jobDescription); HashSet<Trigger> triggerSet = new HashSet<>(); triggerSet.add(cronTrigger); scheduler.scheduleJob(jobDetail, triggerSet, true); } catch (SchedulerException e) { logger.error("類名不存在或執行表達式錯誤,exception:{}",e.getMessage()); } } /** * 刪除定時任務 * @param jobName * @param jobGroup */ public void delete(String jobName, String jobGroup){ TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup); try { if (checkExists(jobName, jobGroup)) { scheduler.pauseTrigger(triggerKey); scheduler.unscheduleJob(triggerKey); logger.info("delete job, triggerKey:{},jobGroup:{}, jobName:{}", triggerKey ,jobGroup, jobName); } } catch (SchedulerException e) { logger.error(e.getMessage()); } } /** * 暫停定時任務 * @param jobName * @param jobGroup */ public void pause(String jobName, String jobGroup){ TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup); try { if (checkExists(jobName, jobGroup)) { scheduler.pauseTrigger(triggerKey); logger.info("pause job success, triggerKey:{},jobGroup:{}, jobName:{}", triggerKey ,jobGroup, jobName); } } catch (SchedulerException e) { logger.error(e.getMessage()); } } /** * 從新開始任務 * @param jobName * @param jobGroup */ public void resume(String jobName, String jobGroup){ TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup); try { if (checkExists(jobName, jobGroup)) { scheduler.resumeTrigger(triggerKey); logger.info("resume job success,triggerKey:{},jobGroup:{}, jobName:{}", triggerKey ,jobGroup, jobName); } } catch (SchedulerException e) { logger.error(e.getMessage()); } } /** * 驗證是否存在 * @param jobName * @param jobGroup * @throws SchedulerException */ public boolean checkExists(String jobName, String jobGroup) throws SchedulerException{ TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup); return scheduler.checkExists(triggerKey); } }
taskinfo實體類
package com.kerry.modular.biz.model; import java.io.Serializable; public class TaskInfo implements Serializable{ private static final long serialVersionUID = -8054692082716173379L; private int id = 0; /**任務名稱*/ private String jobName; /**任務分組*/ private String jobGroup; /**任務描述*/ private String jobDescription; /**任務狀態*/ private String jobStatus; /**任務表達式*/ private String cronExpression; private String createTime; public String getJobName() { return jobName; } public void setJobName(String jobName) { this.jobName = jobName; } public String getJobGroup() { return jobGroup; } public void setJobGroup(String jobGroup) { this.jobGroup = jobGroup; } public String getJobDescription() { return jobDescription; } public void setJobDescription(String jobDescription) { this.jobDescription = jobDescription; } public String getJobStatus() { return jobStatus; } public void setJobStatus(String jobStatus) { this.jobStatus = jobStatus; } public String getCronExpression() { return cronExpression; } public void setCronExpression(String cronExpression) { this.cronExpression = cronExpression; } public String getCreateTime() { return createTime; } public void setCreateTime(String createTime) { this.createTime = createTime; } public int getId() { return id; } public void setId(int id) { this.id = id; } }
任務管理Controller類
package com.kerry.modular.biz.controller; import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.alibaba.fastjson.JSON; import com.kerry.modular.biz.model.TaskInfo; import com.kerry.modular.biz.service.TaskService; /** * 任務管理 */ @Controller @RequestMapping("/qy/api/task/") public class TaskManageController { @Autowired(required=false) private TaskService taskService; /** * Index.jsp */ @RequestMapping(value={"", "/", "index"}) public String info(){ return "index.jsp"; } /** * 任務列表 * @return */ @ResponseBody @RequestMapping(value="list") public String list(){ Map<String, Object> map = new HashMap<>(); List<TaskInfo> infos = taskService.list(); map.put("rows", infos); map.put("total", infos.size()); return JSON.toJSONString(map); } /** * 保存定時任務 * @param info */ @ResponseBody @RequestMapping(value="save", produces = "application/json; charset=UTF-8") public String save(TaskInfo info){ try { if(info.getId() == 0) { taskService.addJob(info); }else{ taskService.edit(info); } } catch (Exception e) { return e.getMessage(); } return "成功"; } /** * 刪除定時任務 * @param jobName * @param jobGroup */ @ResponseBody @RequestMapping(value="delete/{jobName}/{jobGroup}", produces = "application/json; charset=UTF-8") public String delete(@PathVariable String jobName, @PathVariable String jobGroup){ try { taskService.delete(jobName, jobGroup); } catch (Exception e) { return e.getMessage(); } return "成功"; } /** * 暫停定時任務 * @param jobName * @param jobGroup */ @ResponseBody @RequestMapping(value="pause/{jobName}/{jobGroup}", produces = "application/json; charset=UTF-8") public String pause(@PathVariable String jobName, @PathVariable String jobGroup){ try { taskService.pause(jobName, jobGroup); } catch (Exception e) { return e.getMessage(); } return "成功"; } /** * 從新開始定時任務 * @param jobName * @param jobGroup */ @ResponseBody @RequestMapping(value="resume/{jobName}/{jobGroup}", produces = "application/json; charset=UTF-8") public String resume(@PathVariable String jobName, @PathVariable String jobGroup){ try { taskService.resume(jobName, jobGroup); } catch (Exception e) { return e.getMessage(); } return "成功"; } }
任務實現類實現job接口,重寫execute方法
package com.kerry.modular.biz.task.quartz; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.kerry.modular.biz.task.schedule.CtripScenicTask; @Component public class CtripScenicJob implements Job{ private Logger logger = LoggerFactory.getLogger(CtripScenicJob.class); @Autowired private CtripScenicTask ctripScenicTask; @Override public void execute(JobExecutionContext context) throws JobExecutionException { logger.info("JobName: {}", context.getJobDetail().getKey().getName()); ctripScenicTask.loadComment(); } }
此時能夠經過調用TaskManageController時間動態控制定時任務
輸入添加任務的url:
http://localhost:8080/qy/api/task/save?jobName=com.stylefeng.guns.modular.biz.task.quartz.CtripHotelJob&jobGroup=group1&jobDescription=job描述&cronExpression=0/10 * * * * ?
jobName爲job類的包名類名,jobGroup該任務所屬組,jobDescription 描述,cronExpression :core表達式
上面的請求會添加一個定時任務,每10秒執行一次 CtripHotelJob裏面的execute方法。
保存的定時任務會在quartz相關表裏保存數據
如: