【前情提要】因爲項目須要,須要一個定時任務集羣,故此有了這個spring-boot-starter-quartz集羣的實踐。springboot的版本爲:2.1.6.RELEASE;quartz的版本爲:2.3.1.假如這裏一共有兩個定時任務的節點,它們的代碼徹底同樣。java
<properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
這裏選擇將定時任務的數據入庫,避免數據直接存在內存中,因應用重啓形成的數據丟失和作集羣控制。mysql
spring: server: port: 8080 servlet: context-path: /lovin datasource: url: jdbc:mysql://127.0.0.1:3306/training?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver 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
這裏須要注意的是兩個節點的端口號應該不一致,避免衝突git
[@Slf4j](https://my.oschina.net/slf4j) public class Job extends QuartzJobBean { [@Override](https://my.oschina.net/u/1162528) protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException { // 獲取參數 JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap(); // 業務邏輯 ... log.info("------springbootquartzonejob執行"+jobDataMap.get("name").toString()+"###############"+jobExecutionContext.getTrigger()); }
其中的日誌輸出是爲了便於觀察任務執行狀況github
[@Service](https://my.oschina.net/service) public class QuartzService { @Autowired private Scheduler scheduler; @PostConstruct public void startScheduler() { try { scheduler.start(); } catch (SchedulerException e) { e.printStackTrace(); } } /** * 增長一個job * * @param jobClass * 任務實現類 * @param jobName * 任務名稱 * @param jobGroupName * 任務組名 * @param jobTime * 時間表達式 (這是每隔多少秒爲一次任務) * @param jobTimes * 運行的次數 (<0:表示不限次數) * @param jobData * 參數 */ public void addJob(Class<? extends QuartzJobBean> jobClass, String jobName, String jobGroupName, int jobTime, int jobTimes, Map jobData) { 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(); } } /** * 增長一個job * * @param jobClass * 任務實現類 * @param jobName * 任務名稱(建議惟一) * @param jobGroupName * 任務組名 * @param jobTime * 時間表達式 (如:0/5 * * * * ? ) * @param jobData * 參數 */ public void addJob(Class<? extends QuartzJobBean> jobClass, String jobName, String jobGroupName, String jobTime, Map jobData) { try { // 建立jobDetail實例,綁定Job實現類 // 指明job的名稱,所在組的名稱,以及綁定job類 // 任務名稱和組構成任務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(jobTime)).startNow().build(); // 把做業和觸發器註冊到任務調度中 scheduler.scheduleJob(jobDetail, trigger); } catch (Exception e) { e.printStackTrace(); } } /** * 修改 一個job的 時間表達式 * * @param jobName * @param jobGroupName * @param jobTime */ public void updateJob(String jobName, String jobGroupName, String jobTime) { try { TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName); CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); trigger = trigger.getTriggerBuilder().withIdentity(triggerKey) .withSchedule(CronScheduleBuilder.cronSchedule(jobTime)).build(); // 重啓觸發器 scheduler.rescheduleJob(triggerKey, trigger); } catch (SchedulerException e) { e.printStackTrace(); } } /** * 刪除任務一個job * * @param jobName * 任務名稱 * @param jobGroupName * 任務組名 */ public void deleteJob(String jobName, String jobGroupName) { try { scheduler.deleteJob(new JobKey(jobName, jobGroupName)); } catch (Exception e) { e.printStackTrace(); } } /** * 暫停一個job * * @param jobName * @param jobGroupName */ public void pauseJob(String jobName, String jobGroupName) { try { JobKey jobKey = JobKey.jobKey(jobName, jobGroupName); scheduler.pauseJob(jobKey); } catch (SchedulerException e) { e.printStackTrace(); } } /** * 恢復一個job * * @param jobName * @param jobGroupName */ public void resumeJob(String jobName, String jobGroupName) { try { JobKey jobKey = JobKey.jobKey(jobName, jobGroupName); scheduler.resumeJob(jobKey); } catch (SchedulerException e) { e.printStackTrace(); } } /** * 當即執行一個job * * @param jobName * @param jobGroupName */ public void runAJobNow(String jobName, String jobGroupName) { try { JobKey jobKey = JobKey.jobKey(jobName, jobGroupName); scheduler.triggerJob(jobKey); } 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<>(); 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; }
這裏不許備給用戶用web界面來配置定時任務,故此採用CommandLineRunner來子啊應用初始化的時候來初始化任務。只須要實現CommandLineRunner的run()方法便可。web
@Override public void run(String... args) throws Exception { HashMap<String,Object> map = new HashMap<>(); map.put("name",1); quartzService.deleteJob("job", "test"); quartzService.addJob(Job.class, "job", "test", "0 * * * * ?", map); map.put("name",2); quartzService.deleteJob("job2", "test"); quartzService.addJob(Job.class, "job2", "test", "10 * * * * ?", map); map.put("name",3); quartzService.deleteJob("job3", "test2"); quartzService.addJob(Job.class, "job3", "test2", "15 * * * * ?", map); }
分別夏侯啓動兩個應用,而後觀察任務執行,以及在運行過程當中殺死某個服務,來觀察定時任務的執行。 spring