文章出處

第一代定時任務系統上線用了大概半年之后,就被我們厭倦了。于是就規劃了第二代定時任務系統。

第二代定時任務系統

第二代調度系統主要解決的是,避免每次修改定時任務的執行時間都需要重新啟動整個項目。另外也可支持單獨重新調度單個定時任務。

我們做了一個請求入口,當更新了庫表里面的數據之后,重新請求一下特定的url就會自動重新加載定時任務。

使用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重新加載所有的定時任務。

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);
}

如果只是重新調度某一個定時任務可以觸發單獨的調用

// 初始化某個加載定時任務
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);
}

這樣我們的第二代定時任務系統就完成了,第二代定時任務是在第一代定時任務的基礎上改造的,增加了重新調度所有定時任務和單個定時任務。

第二代定時任務系統的缺點是:定時調度和業務代碼耦合

第三代定時任務系統

第二代定時任務上線沒有多久,我們就意識到有很多的子系統也需要定時任務,比如訂單系統需要45分鐘不支付的訂單失效,監控系統需要定時掃描是否有業務報警,統計系統需要定時去統計一些數據,但是如果我們給每一個子系統都做一個定時任務的話,就不太合理,很分散。

于是計劃開發一個統一的定時任務調度中心,負責整個平臺中所有的定時任務的調度,另外規劃了監控系統,來監控和分析每次定時任務的執行結果和執行時間等信息。為了更好的管理定時任務開發了簡單的管理界面。如下:

根據上圖可以看出,通過這個管理界面我們可以非常方便的去修改、啟動、暫停定時任務。別的系統如果需要定時任務,可以隨時在頁面去添加,全部界面化操作,不需要重新啟動項目等。

點擊詳情可以清晰的查看定時任務的上次執行情況

定時任務的支持的調度方式分有兩種: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上面查看。

zx-quartz

其實最后這版定時調度系統,還是有很多的缺陷,http模式沒有進行完善,開源的代碼中有部分內部依賴的jar還沒有去掉。開放出來僅僅做為交流使用,后期有時間的話再去慢慢完善。也歡迎各網友多提提建議,一起加入完善。


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()