多节点部署服务实现动态的定时任务。

背景

实现对定时任务的开启关闭,对定时任务实现,不同参数,不同cron表达式。

spring boot使用TaskScheduler实现动态增删启停定时任务

添加执行定时任务的线程池配置类 初始化TaskScheduler



/**
 * @description 添加执行定时任务的线程池配置类 初始化TaskScheduler
 * @date 2023/9/6 9:06
 */
@Configuration
public class SchedulingConfig {

    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        // 定时任务执行线程池核心线程数
        taskScheduler.setPoolSize(4);
        taskScheduler.setRemoveOnCancelPolicy(true);
        taskScheduler.setThreadNamePrefix("TaskSchedulerThreadPool-");
        return taskScheduler;
    }

}

添加定时任务注册类,用来增加、删除定时任务。


/**
 * 
 * @description 添加定时任务注册类,用来增加、删除定时任务。
 * @date 2023/9/6 8:52
 */
@Component
public class CronTaskRegistrar implements DisposableBean {

    private final Map<Runnable, ScheduledTask> scheduledTasks = new ConcurrentHashMap<>(16);
    private final Map<String, ScheduleResult> schedulerJob = new HashMap<>();

    @Autowired
    private TaskScheduler taskScheduler;


    public TaskScheduler getScheduler() {
        return this.taskScheduler;
    }

    public void addCronTask(ScheduleResult scheduleResult) {
        SchedulingRunnable task = new SchedulingRunnable(scheduleResult.getBeanName(), scheduleResult.getMethodName(), scheduleResult.getMethodParams());
        String cronExpression = scheduleResult.getCronExpression();

        CronTask cronTask = new CronTask(task, cronExpression);
        // 如果当前包含这个任务,则移除
        if (this.scheduledTasks.containsKey(task)) {
            removeCronTask(scheduleResult.getBeanName(), scheduleResult.getMethodName(), scheduleResult.getMethodParams());
        }
        schedulerJob.put(scheduleResult.getJobId(), scheduleResult);
        this.scheduledTasks.put(task, scheduleCronTask(cronTask));
    }

    public void removeCronTask(String beanName, String methodName, String methodParams) {
        SchedulingRunnable task = new SchedulingRunnable(beanName, methodName, methodParams);
        ScheduledTask scheduledTask = this.scheduledTasks.remove(task);
        if (scheduledTask != null) {
            scheduledTask.cancel();
        }
    }

    public void removeCronTask(ScheduleResult scheduleResult) {
        schedulerJob.put(scheduleResult.getJobId(), scheduleResult);
        removeCronTask(scheduleResult.getBeanName(), scheduleResult.getMethodName(), scheduleResult.getMethodParams());
    }

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


    public Map<Runnable, ScheduledTask> getScheduledTasks() {
        return scheduledTasks;
    }

    public Map<String, ScheduleResult> getSchedulerJob() {
        return schedulerJob;
    }

    @Override
    public void destroy() {
        for (ScheduledTask task : this.scheduledTasks.values()) {
            task.cancel();
        }

        this.scheduledTasks.clear();
    }

    public ScheduleResult getSchedulerByJobId(String jobId) {
        for (ScheduleResult job : findAllTask()) {
            if (jobId.equals(job.getJobId())) {
                return job;
            }
        }
        return null;
    }

    public List<ScheduleResult> findAllTask() {
        List<ScheduleResult> ScheduleResults = new ArrayList<>();
        Set<Map.Entry<String, ScheduleResult>> entries = schedulerJob.entrySet();
        for (Map.Entry<String, ScheduleResult> en : entries) {
            ScheduleResults.add(en.getValue());
        }
        return ScheduleResults;
    }


}

cheduledFuture的包装类,ScheduledFuture是ScheduledExecutorService定时任务线程池的执行结果。

public final class ScheduledTask {

    volatile ScheduledFuture<?> future;

    /**
     * 取消定时任务
     */
    public void cancel() {
        ScheduledFuture<?> future = this.future;
        if (future != null) {
            future.cancel(true);
        }
    }

}

定时任务的参数实体

@Data
public class ScheduleResult {

    /**
     * 任务ID
     */
    private String jobId;
    /**
     * bean名称
     */
    private String beanName;
    /**
     * 方法名称
     */
    private String methodName;
    /**
     * 方法参数: 执行service里面的哪一种方法
     */
    private String methodParams;
    /**
     * cron表达式
     */
    private String cronExpression;
    /**
     * 状态(1正常 0暂停)
     */
    private Integer jobStatus;
    /**
     * 备注
     */
    private String remark;
    /**
     * 创建时间
     */
    private String createTime;
    /**
     * 更新时间
     */
    private String updateTime;

}

Runnable接口实现类,被定时任务线程池调用,用来执行指定bean里面的方法

public class SchedulingRunnable implements Runnable{

    private static final Logger logger = LoggerFactory.getLogger(SchedulingRunnable.class);

    private final String beanName;

    private final String methodName;

    private final String params;

    public SchedulingRunnable(String beanName, String methodName) {
        this(beanName, methodName, null);
    }

    public SchedulingRunnable(String beanName, String methodName, String params) {
        this.beanName = beanName;
        this.methodName = methodName;
        this.params = params;
    }



    @Override
    public void run() {
        logger.info("定时任务开始执行 - bean:{},方法:{},参数:{}", beanName, methodName, params);
        long startTime = System.currentTimeMillis();

        try {
            Object target = SpringContextUtils.getBean(beanName);

            Method method = null;
            if (StringUtils.isNotEmpty(params)) {
                method = target.getClass().getDeclaredMethod(methodName, String.class);
            } else {
                method = target.getClass().getDeclaredMethod(methodName);
            }

            ReflectionUtils.makeAccessible(method);
            if (StringUtils.isNotEmpty(params)) {
                method.invoke(target, params);
            } else {
                method.invoke(target);
            }
        } catch (Exception ex) {
            logger.error(String.format("定时任务执行异常 - bean:%s,方法:%s,参数:%s ", beanName, methodName, params), ex);
        }

        long times = System.currentTimeMillis() - startTime;
        logger.info("定时任务执行结束 - bean:{},方法:{},参数:{},耗时:{} 毫秒", beanName, methodName, params, times);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        SchedulingRunnable that = (SchedulingRunnable) o;
        if (params == null) {
            return beanName.equals(that.beanName) &&
                    methodName.equals(that.methodName) &&
                    that.params == null;
        }

        return beanName.equals(that.beanName) &&
                methodName.equals(that.methodName) &&
                params.equals(that.params);
    }

    @Override
    public int hashCode() {
        if (params == null) {
            return Objects.hash(beanName, methodName);
        }

        return Objects.hash(beanName, methodName, params);
    }

状态枚举

public enum ScheduleJobStatus {

    /**
     * 暂停
     */
    PAUSE,

    /**
     * 正常
     */
    NORMAL;
}

校验cron表达式合法性

public class CronUtils {

    /**
     * 返回一个布尔值代表一个给定的Cron表达式的有效性
     *
     * @param cronExpression Cron表达式
     * @return boolean 表达式是否有效
     */
    public static boolean isValid(String cronExpression) {
        return CronExpression.isValidExpression(cronExpression);
    }

}

当集群化部署时,每个节点服务可能存在多个定时任务实例,或者存在定时任务的节点挂掉后,另外的节点无法根据状态进行创建。类似于xxl-job具有的功能。灾备。采用redis缓存或者分布式锁实现。

当某个节点存在定时任务时,会定时扫描数据库里参数状态进行定时任务的删除与新增。

@Component
@Slf4j
public class PatrolPlanInitializing {

    @Autowired
    private EvtPatrolPlanServiceImpl evtPatrolPlanService;

    @Autowired
    private ScheduleJobService scheduleJobService;

    @Autowired
    private RedisService redisService;

    
    @Scheduled(cron = "0 0/10 * ? * ? ")
    public void initializingTask(){
        EvtPatrolPlan evtPatrolPlan =new EvtPatrolPlan();
        evtPatrolPlan.setIsPulse(EvtPatrolPlanServiceImpl.NORMAL);
        List<EvtPatrolPlan> evtPatrolPlans = evtPatrolPlanService.selectEvtPatrolPlanList(evtPatrolPlan);
        for (EvtPatrolPlan it :evtPatrolPlans){
            String key = it.getTaskCode();
            EvtPatrolPlan param = evtPatrolPlanService.checkParam(it);
//            if (redisService.hasKey(key) && !redisService.hasKey(key+"-heartbeat")){
            if (!redisService.hasKey(key+"-heartbeat")){
                ScheduleResult scheduleResult = new ScheduleResult();
                scheduleResult.setJobId(it.getTaskCode());
                scheduleResult.setBeanName(EvtPatrolPlanServiceImpl.BeanName);
                scheduleResult.setMethodName(EvtPatrolPlanServiceImpl.AddMethod);
                scheduleResult.setMethodParams(JSON.toJSONString(param));
                scheduleResult.setCronExpression(it.getCycleCron());
                scheduleResult.setJobStatus(EvtPatrolPlanServiceImpl.NORMAL);
                scheduleJobService.addScheduleJob(scheduleResult);
                redisService.setCacheObject(it.getTaskCode()+"-heartbeat",it,11L, TimeUnit.MINUTES);
                log.info("定时任务扫描:------需要开启的巡查任务:{}",scheduleResult.getMethodParams());
                }
            }
    }

    @Scheduled(cron = "0 0/10 * ? * ?")
    public void checkJob(){
        List<ScheduleResult> allTask = scheduleJobService.findAllTask();
        List<String> hasJobIds = allTask.stream().map(ScheduleResult::getJobId).collect(Collectors.toList());
        log.info("定时任务扫描:------线程节点存在开启状态的巡查任务:{}",JSON.toJSONString(hasJobIds));
        EvtPatrolPlan evtPatrolPlan =new EvtPatrolPlan();
        evtPatrolPlan.setIsPulse(0);
        List<EvtPatrolPlan> evtPatrolPlans = evtPatrolPlanService.selectEvtPatrolPlanList(evtPatrolPlan);
        List<String> jobIds = evtPatrolPlans.stream().map(EvtPatrolPlan::getTaskCode).collect(Collectors.toList());
        for (ScheduleResult it: allTask){
            if (jobIds.contains(it.getJobId())){
                scheduleJobService.deleteScheduleJob(it);
                redisService.deleteObject(it.getJobId()+"-heartbeat");
                log.info("定时任务扫描:------需要关闭的巡查任务:{}",it.getMethodParams());
            }
            redisService.setCacheObject(it.getJobId()+"-heartbeat",it,11L, TimeUnit.MINUTES);
        }

    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值