博客 / 詳情

返回

玩轉SpringBoot動態定時任務(啓動、暫停)

關注公眾號 不愛總結的麥穗 將不定期推送技術好文

最近在做一個項目,需要用到動態定時任務,現在比較普遍的做法是集成第三方框架(例如Quartz、XXL-JOB),我自己在做這個項目的時候也考慮過去集成Quartz實現,但是基於項目本身的複雜度和使用場景放棄了

本文主要分享在不依賴過多的其他框架,使用springBoot自身帶有的定時任務框架來實現動態定時任務

註解實現定時任務

具體實現

主要基於@EnableScheduling@Scheduled註解

  • 主啓動類上加上 @EnableScheduling 註解
  • 寫一個類,注入到容器中,在方法上加上 @Scheduled 註解
@Slf4j
@Component
public class TimeTask {
    
    @Scheduled(cron = "0 0/31 * * * ?")
    public void refresh(){
        log.info("//// 定時刷新");
        
      //業務代碼
    }

}

這樣就實現了定時任務,是不是很簡單?不難發現,這種方式定時執行時間是固定的,但是大部分業務的定時執行時間是經常在變化的,這時候我們就需要通過動態定時任務實現

實現動態定時任務

Spring實現動態定時任務的核心就是其提供的任務調度類ThreadPoolTaskSchedulerThreadPoolTaskScheduler基於線程池來執行任務,可以按照固定的時間間隔或者指定的Cron表達式來調度任務的執行。

image.png

在我的項目中主要用到了ScheduledFuture<?> schedule(Runnable task, Trigger trigger)方法,指定的Cron表達式來調度任務的執行

具體實現

  • 創建ThreadPoolTaskScheduler配置類

    @Configuration
    public class SchedulingConfig {
    
      @Bean
      public TaskScheduler taskScheduler() {
          // 獲取系統處理器個數, 作為線程池數量
          int corePoolSize = Runtime.getRuntime().availableProcessors();
          ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
          // 定時任務執行線程池核心線程數
          taskScheduler.setPoolSize(corePoolSize);
          taskScheduler.setRemoveOnCancelPolicy(true);
          taskScheduler.setThreadNamePrefix("AntiFraudSchedulerThreadPool-");
          return taskScheduler;
      }
    
    }
  • 創建ScheduledTask包裝類
    ScheduledFutureScheduledFuture<?> schedule(Runnable task, Trigger trigger)方法的返回值。

ScheduledFuture繼承了Future接口,Future接口提供一組輔助方法,比如:

  • cancel():取消任務
  • isCancelled():任務是不是取消了
  • isDone():任務是不是已經完成了
  • get():用來獲取執行結果

當我們調用cancel方法時,會將我們的任務從workQueue中移除

public final class ScheduledTask {

    public volatile ScheduledFuture<?> future;

    /**
     * 取消定時任務
     */
    public void cancel() {
        ScheduledFuture<?> scheduledFuture = this.future;
        if (Objects.nonNull(scheduledFuture)) {
            scheduledFuture.cancel(true);
        }
    }
}
  • 創建CronTaskRegistrar

實現了DisposableBean接口的類,用於註冊定時任務。它具有添加、刪除和調度定時任務的方法。在銷燬時,會取消所有定時任務。

@Slf4j
@Component
@SuppressWarnings("all")
public class CronTaskRegistrar implements DisposableBean {

    @Resource
    private TaskScheduler taskScheduler;

    // 保存任務Id和定時任務
    private final Map<String, ScheduledTask> scheduledTaskMap = new ConcurrentHashMap<>(64);

    // 添加任務
    public void addTask(Runnable task, String cronExpression,String jobId) {
        addTask(new CronTask(task, cronExpression),jobId);
    }

    public void addTask(CronTask cronTask,String jobId) {
        if (Objects.nonNull(cronTask)) {
            Runnable task = cronTask.getRunnable();
            if (this.scheduledTaskMap.containsKey(task)) {
                removeTask(jobId);
            }
            // 保存任務Id和定時任務
            this.scheduledTaskMap.put(jobId, scheduleTask(cronTask));
        }
    }

    // 通過任務Id,取消定時任務
    public void removeTask(String jobId) {
        ScheduledTask scheduledTask = this.scheduledTaskMap.remove(jobId);
        if (Objects.nonNull(scheduledTask)) {
            scheduledTask.cancel();
        }
    }

    public ScheduledTask scheduleTask(CronTask cronTask) {
        ScheduledTask scheduledTask = new ScheduledTask();
        scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
        return scheduledTask;
    }

    // 銷燬
    @Override
    public void destroy() {
        this.scheduledTaskMap.values().forEach(ScheduledTask::cancel);
        this.scheduledTaskMap.clear();
    }
}

動態定時任務的核心邏輯到這基本就已經完成了,具體的Service類代碼,這裏就不貼出來了,因為裏面基本上都是業務邏輯和CronTaskRegistrar類的編排(ps:需要的也可以私聊demo)

  • 定時任務表設計

    create table schedule_setting
    (
      id               varchar(32)  not null comment '唯一id'
          primary key,
      job_id           varchar(64)  null comment '任務ID',
      cron_expression  varchar(255) null comment 'cron表達式',
      job_result       varchar(32)  null comment '任務結果(通過 複議 拒絕)',
      create_date      datetime     null comment '創建時間',
      status           varchar(4)   null,
      create_by        varchar(64)  null comment '創建人賬號',
      creator          varchar(64)  null comment '創建人',
      version          bigint(19)   null comment '版本號'
    )
      comment '定時任務表';
  • 項目啓動時加載所有任務

    @Component
    @RequiredArgsConstructor
    @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
    public class ScheduleRunner implements CommandLineRunner {
    
    
      ScheduleSettingRepository scheduleSettingRepository;
    
      CronTaskRegistrar scheduledTaskRegistrar;
    
      @Override
      public void run(String... args) throws Exception {
          // 查詢所有定時任務
          List<ScheduleSetting> scheduleSettingList=scheduleSettingRepository.findByStatus(EnableFlag.Y.name());
          for (ScheduleSetting scheduleSetting : scheduleSettingList) {
              //調用CronTaskRegistrar添加任務方法
              scheduledTaskRegistrar.addCronTask(() -> {
                  ···
                  任務執行方法
                  ...
              }, scheduleSetting.getCronExpression(), scheduleSetting.getJobId());
          }
    
      }
    }

    至此,動態定時任務就説完啦

GitHup demo dynamic-timing-task

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.