likes
comments
collection
share

实现零重启自由编排任务的定时管理器

作者站长头像
站长
· 阅读数 15

大家好,我是小趴菜,关于定时任务相信大家在项目中使用了很多,我们一般都是使用Spring自带的@EnableScheduling来实现定时任务

虽然Spring自带的定时任务已经可以满足我们的业务需求,但是它还是有不足的地方

  • 我们需要改变定时任务的时间,此时我们就需要重启项目
  • 我们不需要这个定时任务了,我们就要修改代码,然后重启项目
  • 后期我们又要开启这个定时任务,我们又要修改代码,然后重启项目

我们发现,我们使用Spring自带的定时任务如果要有修改,那么就要修改代码,然后重启项目,那么有没有办法能够让我们不重启项目就可以直接编排我们自己的定时任务呢?

答案是:有的,接下来我就带大家实现一个零重启自由编排任务的定时管理器

实现

创建一个普通的SpringBoot项目,首先我们需要一个配置类,来配置定时任务的线程池

package com.coco.schedule;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

@Configuration
public class ScheduleConfig {

    
    @Bean
    public ThreadPoolTaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        //定制化我们的线程池
        taskScheduler.setPoolSize(1);        
        taskScheduler.setThreadNamePrefix("myTask-");
        return taskScheduler;
    }
}

创建一个接口,以供其它服务调用

/**
 * 定时任务的任务接口,需要实现Runnable接口
 */
public interface ScheduleTask extends Runnable {

    /**
     * 获取定时任务的名称
     */
    String getTaskName();
}

接下来就是定时任务的编排核心处理类

package com.coco.schedule;

import cn.hutool.core.util.StrUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;

/**
 * 定时任务管理器
 */
@Component
public class ScheduleManager {


    private static final Logger LOGGER = LoggerFactory.getLogger(ScheduleManager.class);

    @Resource
    private ThreadPoolTaskScheduler taskScheduler;

    /**
     * 内部正在执行的定时任务缓存
     */
    private Map<String,ScheduleTaskHolder> cache = new ConcurrentHashMap<>();


    /**
     * 启动一个定时任务
     * scheduleTask:定时任务实现类
     * cron:cron表达式
     */
    public String startTask(ScheduleTask scheduleTask,String cron) {
        ScheduledFuture<?> scheduledFuture = taskScheduler.schedule(scheduleTask, new CronTrigger(cron));
        //使用UUID生成定时任务的唯一key
        String key = UUID.randomUUID().toString();
        
        //将定时任务与定时任务结果封装成ScheduleTaskHolder对象,
        //这个对象下文有源码,也是我们自定义的
        ScheduleTaskHolder scheduleTaskHolder = new ScheduleTaskHolder(scheduleTask,scheduledFuture);
        //将正在执行的定时任务缓存起来
        cache.put(key,scheduleTaskHolder);
        LOGGER.info("定时任务启动成功,key = {}",key);
        return key;
    }


    /**
     * 停止一个定时任务
     * @param key:缓存里面的定时任务的key
     */
    public void stopTask(String key) {
        //基本判断
        if(StrUtil.isBlank(key) || !cache.containsKey(key)) {
            return;
        }
        //从缓存中拿到这个定时任务
        ScheduleTaskHolder scheduleTaskHolder = cache.get(key);
        if(scheduleTaskHolder == null) {
            return;
        }
        ScheduledFuture scheduledFuture = scheduleTaskHolder.getScheduledFuture();
        //停止这个定时任务
        boolean isCancel = scheduledFuture.cancel(true);
        if(isCancel) {
            //停止成功,就从缓存中移除这个定时任务
            cache.remove(key);
            LOGGER.info("定时任务停止成功,key = {}",key);
        }else {
            LOGGER.error("定时任务停止失败,key = {}",key);
        }
    }


    /**
     * 停止一个定时任务
     * @param key:缓存里面的定时任务的key
     * @param cron:新的cron表达式
     */
    public String changeTask(String key,String cron) {
        //基本判空处理
        if(StrUtil.isBlank(key) || StrUtil.isBlank(cron)) {
            throw new RuntimeException("key and cron mast not null");
        }

        ScheduleTaskHolder scheduleTaskHolder = cache.get(key);
        if(scheduleTaskHolder == null) {
            throw new RuntimeException("ScheduleTaskHolder not exist,key = {}" + key);
        }
        
        //先停止这个定时任务
        stopTask(key);
        
        //然后重启开启一个定时任务
        return startTask(scheduleTaskHolder.getScheduleTask(),cron);

    }

}

定时任务与定时任务结果的缓存封装类

package com.coco.schedule;

import java.io.Serializable;
import java.util.concurrent.ScheduledFuture;

/**
 * 定时任务和定时任务结果的缓存对象
 */
public class ScheduleTaskHolder implements Serializable {

    /**
     * 执行任务实体
     */
    private ScheduleTask scheduleTask;

    /**
     * 执行任务的结果
     */
    private ScheduledFuture scheduledFuture;


    public ScheduleTaskHolder() {
    }

    public ScheduleTaskHolder(ScheduleTask scheduleTask, ScheduledFuture scheduledFuture) {
        this.scheduleTask = scheduleTask;
        this.scheduledFuture = scheduledFuture;
    }

    public ScheduleTask getScheduleTask() {
        return scheduleTask;
    }

    public void setScheduleTask(ScheduleTask scheduleTask) {
        this.scheduleTask = scheduleTask;
    }

    public ScheduledFuture getScheduledFuture() {
        return scheduledFuture;
    }

    public void setScheduledFuture(ScheduledFuture scheduledFuture) {
        this.scheduledFuture = scheduledFuture;
    }
}

测试

写好的程序怎么能不测试,首先我们需要创建一个自己的定时任务执行的业务处理类,我这里创建了个MyTask.class

package com.coco.schedule;

import org.springframework.stereotype.Component;

//需要实现ScheduleTask接口
@Component
public class MyTask implements ScheduleTask {


    @Override
    public String getTaskName() {
        return "MyTask";
    }

    @Override
    public void run() {
        //在这里就是我们定时任务的业务逻辑
        System.out.println("这是自定义的定时起任务");
    }
}

为了简单,我直接在启动类中编写接口了,大家别跟我一样这么懒哈

package com.coco;

import com.coco.schedule.MyTask;
import com.coco.schedule.ScheduleManager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;



@RestController
@SpringBootApplication
public class App {


    public static void main(String[] args) {
        SpringApplication.run(App.class,args);
    }


    @Resource
    private ScheduleManager scheduleManager;


    @Resource
    private MyTask myTask;


    //启动我们的定时任务
    @GetMapping("/startTask")
    public String startTask() {
        String key = scheduleManager.startTask(myTask, "0/10 * * * * ? ");
        return key;
    }

    //修改我们的定时任务
    @GetMapping("/changeTask")
    public String changeTask(@RequestParam("key") String key) {
        String keyValue = scheduleManager.changeTask(key, "0/20 * * * * ? ");
        return keyValue;
    }

    //停止我们的定时任务
    @GetMapping("/stopTask")
    public String stopTask(@RequestParam("key") String key) {
        scheduleManager.stopTask(key);
        return "ok";
    }
}

可以先启动,然后修改,最后停止

实现零重启自由编排任务的定时管理器