第一代定時任務系統上線用了大概半年以後,就被咱們厭倦了。因而就規劃了第二代定時任務系統。java
第二代調度系統主要解決的是,避免每次修改定時任務的執行時間都須要從新啓動整個項目。另外也可支持單獨從新調度單個定時任務。git
咱們作了一個請求入口,當更新了庫表裏面的數據以後,從新請求一下特定的url就會自動從新加載定時任務。github
使用scheduler刪除定時任務異步
public void reScheduler() throws Exception {
// 取消現有的任務
String[] jobNames = quartzUtil.getJobNames();
if (null != jobNames && jobNames.length > 0) {
for (String jobName : jobNames) {
logger.info("----開始移除任務:" + jobName);
quartzUtil.cancelJob(jobName);
logger.info("----成功移除任務:" + jobName);
}
}
logger.info("現有任務已所有取消");
this.initScheduler();
}複製代碼
public void cancelJob(String jobName) throws Exception {
scheduler.pauseTrigger(jobName, Scheduler.DEFAULT_GROUP);
scheduler.unscheduleJob(jobName, Scheduler.DEFAULT_GROUP);
scheduler.deleteJob(jobName, Scheduler.DEFAULT_GROUP);
}複製代碼
使用scheduler從新加載全部的定時任務。ui
job.setCronExpression(taskInfo.getSchedulerRule());
String jobName = taskInfo.getTaskNo() + "Job";
job.getJobDataMap().put(QuartzJob.OBJECT_ID,objectMethod);
job.setJobName(jobName);
logger.info("----開始部署任務:" + jobName);
quartzUtil.scheduleCronJob(job);
logger.info("----成功部署任務:" + jobName);複製代碼
public void scheduleCronJob(QuartzJobEntity jobEntity) throws Exception {
JobDetailBean jobDetail = createJobDetail(jobEntity);
scheduler.addJob(jobDetail, true);
CronTriggerBean trigger = new CronTriggerBean();
trigger.setCronExpression(jobEntity.getCronExpression());
trigger.setJobDetail(jobDetail);
trigger.setName(jobEntity.getJobName());
trigger.setJobName(jobDetail.getName());
scheduler.scheduleJob(trigger);
}複製代碼
若是隻是從新調度某一個定時任務能夠觸發單獨的調用this
// 初始化某個加載定時任務
public void initScheduler(TaskEntity taskInfo) throws Exception {
// 設置任務信息到quartz,並調度任務
QuartzJobEntity job = new QuartzJobEntity();
String objectName = taskInfo.getTaskNo()+"Task";
String objectMethod = "executeTask";
job.getJobDataMap().put(QuartzJob.OBJECT_NAME,objectName);
job.getJobDataMap().put(QuartzJob.OBJECT_METHOD,objectMethod);
// 單線程方式執行任務
job.setJobClass(QuartzJob.class);
job.setCronExpression(taskInfo.getSchedulerRule());
String jobName = taskInfo.getTaskNo() + "Job";
job.getJobDataMap().put(QuartzJob.OBJECT_ID,objectMethod);
job.setJobName(jobName);
logger.info("----開始部署任務:" + jobName);
quartzUtil.scheduleCronJob(job);
logger.info("----成功部署任務:" + jobName);
}複製代碼
這樣咱們的第二代定時任務系統就完成了,第二代定時任務是在第一代定時任務的基礎上改造的,增長了從新調度全部定時任務和單個定時任務。url
第二代定時任務系統的缺點是:定時調度和業務代碼耦合spa
第二代定時任務上線沒有多久,咱們就意識到有不少的子系統也須要定時任務,好比訂單系統須要45分鐘不支付的訂單失效,監控系統須要定時掃描是否有業務報警,統計系統須要定時去統計一些數據,可是若是咱們給每個子系統都作一個定時任務的話,就不太合理,很分散。線程
因而計劃開發一個統一的定時任務調度中心,負責整個平臺中全部的定時任務的調度,另外規劃了監控系統,來監控和分析每次定時任務的執行結果和執行時間等信息。爲了更好的管理定時任務開發了簡單的管理界面。以下:3d
根據上圖能夠看出,經過這個管理界面咱們能夠很是方便的去修改、啓動、暫停定時任務。別的系統若是須要定時任務,能夠隨時在頁面去添加,所有界面化操做,不須要從新啓動項目等。
點擊詳情能夠清晰的查看定時任務的上次執行狀況
定時任務的支持的調度方式分有兩種:http和mq,咱們通常建議使用mq。
http :使用http通常適用於用時特別少的定時任務。或者接收請求以後馬上返回結果,從新啓動另一個線程去執行具體的業務,業務執行完成以後在經過http回調返回執行結果。
mq :使用mq的話,調度系統和業務系統的交互就徹底異步來執行,調度系統定時觸發後,發送MQ消息給業務系統,業務系統接收到消息開始執行業務,執行完畢以後,再發送MQ系統通知調度系統的執行結果。
主要核心代碼
初始化加載
public void initScheduler(){
List<TaskInformationsEntity> taskList = taskInformationsDao.getTaskList();
Scheduler scheduler = schedulerBean.getScheduler();
for(TaskInformationsEntity task : taskList){
try {
this.scheduler(task, scheduler);
} catch (Exception e) {
logger.error("定時:" + task.getTaskNo() + "啓動失敗");
}
}
}複製代碼
遍歷調度
public void scheduler(TaskInformationsEntity task,Scheduler scheduler){
TriggerKey triggerKey = TriggerKey.triggerKey(task.getTaskNo(), Scheduler.DEFAULT_GROUP);
JobDetail jobDetail = JobBuilder.newJob(QuartzJobFactory.class).withDescription(task.getTaskName()).withIdentity(task.getTaskNo(), Scheduler.DEFAULT_GROUP).build();
jobDetail.getJobDataMap().put("targetObjectId", task.getTaskNo());
jobDetail.getJobDataMap().put("executorNo", task.getExecutorNo());
jobDetail.getJobDataMap().put("sendType", task.getSendType());
jobDetail.getJobDataMap().put("url", task.getUrl());
jobDetail.getJobDataMap().put("executeParamter", task.getExecuteParamter());
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(task.getSchedulerRule());
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
try {
scheduler.scheduleJob(jobDetail, trigger);
logger.info("task "+task.getTaskNo()+" schedulerRule :"+task.getSchedulerRule()+" reload succeed");
} catch (Exception e) {
logger.error("scheduler--異常:",e);
throw new RuntimeException();
}
}複製代碼
添加定時任務
public String addScheduler(String key){
TaskInformationsEntity entity = taskInformationsDao.getTaskByTaskNo(key);
if(null != entity){
Scheduler scheduler = schedulerBean.getScheduler();
try {
scheduler.deleteJob(new JobKey(key));
this.scheduler(entity, scheduler);
entity.setFrozenStatus(TaskStatusEnum.UNFROZEN);
entity.setUnfrozenTime(DateUtil.getLastModifyTime());
entity.setLastModifyTime(DateUtil.getLastModifyTime());
taskInformationsDao.updateById(entity);
return "任務啓動成功";
} catch (Exception e) {
logger.info("異常:",e);
return "任務啓動失敗";
}
}else{
return "該任務編號不存在";
}
}複製代碼
刪除定時任務
public String delScheduler(String key){
TaskInformationsEntity entity = taskInformationsDao.getTaskByTaskNo(key);
if(null != entity && TaskStatusEnum.UNFROZEN == entity.getFrozenStatus()){
Scheduler scheduler = schedulerBean.getScheduler();
try {
scheduler.deleteJob(new JobKey(key));
entity.setFrozenStatus(TaskStatusEnum.FROZEN);
entity.setFrozenTime(DateUtil.getLastModifyTime());
entity.setLastModifyTime(DateUtil.getLastModifyTime());
taskInformationsDao.updateById(entity);
return "暫停任務成功";
} catch (Exception e) {
logger.error("異常:",e);
return "暫停任務異常";
}
}else{
return "該任務編號不存在";
}
}複製代碼
從新加載定時任務
public String resumeScheduler(String key){
TaskInformationsEntity entity = taskInformationsDao.getTaskByTaskNo(key);
if(null != entity){
Scheduler scheduler = schedulerBean.getScheduler();
try {
scheduler.deleteJob(new JobKey(key));
this.scheduler(entity, scheduler);
return "重啓成功";
} catch (SchedulerException e) {
logger.info("異常:",e);
return "重啓異常";
}
}else{
return "該任務編號不存在";
}
}複製代碼
項目已經開源,詳細的代碼請在github上面查看。
其實最後這版定時調度系統,仍是有不少的缺陷,http模式沒有進行完善,開源的代碼中有部份內部依賴的jar尚未去掉。開放出來僅僅作爲交流使用,後期有時間的話再去慢慢完善。也歡迎各網友多提提建議,一塊兒加入完善。
喜歡個人文章,請關注個人公衆號