探索动态执行的计划任务-DynamicSchedule

开发 前端
通过上述方法,我们可以在 Spring Boot 应用中实现动态计划任务,使得任务的执行更加灵活可控。还实验了几种不同的方式,比如每秒轮询数据库、手动计算cron表达式 的执行时间。感觉就属这个事件驱动的方式最优雅。

背景

在现代软件开发中,计划任务是一种常见的需求。无论是定时发送邮件、定期清理缓存,还是执行数据同步,计划任务都能帮助我们自动化这些重复性工作。

最近有一个需求,用户想要自己设定定时时间,来动态的执行定时任务。 很离谱,原来每天晚上12点定时执行的几个数据同步、数据清理任务,想不通用户要这个功能干啥!!!

探索历程

原本的cron表达式,是直接写死到代码里的,显然不能动态的修改。

如果采用配置文件的方式,每次改动要重启项目,或者再写个定时任务,每秒读取文件内容,也不太合适。

如果引入分布式任务调度平台,比如xxl-job、power-job、snail-job,又觉得太复杂。

选择采用放到数据库的方式,实现过程中,发现并不是很顺利,写一篇文章记录一下这次的过程。

原本的实现

@Scheduled(cron = "0/5 * * * * *")  
public void demo() {  
    System.out.println(LocalDateTime.now());  
}

结果

图片图片

动态设置

配置类

@Component  
@RequiredArgsConstructor  
public class JobConfig implements SchedulingConfigurer {  
  
    private final ITestJobService jobService;  
  
    @Override  
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {  
        taskRegistrar.addTriggerTask(  
                //1.添加任务内容(Runnable)  
                () -> System.out.println("执行动态定时任务1: " + LocalDateTime.now()),  
                //2.设置执行周期(Trigger)  
                triggerContext -> {  
                    TestJob job = jobService.getById(1L);  
                    return new CronTrigger(job.getCron()).nextExecutionTime(triggerContext).toInstant();  
                }  
        );  
    }  
}

修改入口

@GetMapping("upd")  
public String upd(@RequestParam("cron") String cron) {  
    jobService.updateById(new TestJob(1, cron));  
    System.out.println("修改时间:"+ LocalDateTime.now());  
    return "success";  
}

将 0/10 * * * * * 改为 0/5 * * * * *

结果

图片图片

可以看出来 修改的时间是 15:01 ,但是下次执行时间还是间隔了10秒,第二次之后的时间才是间隔5秒。 更新结果有一个周期的延迟。

在这种情况下,延迟还算可以接收,但是周期如果是一天、一周,那生效周期就太长了,需要一种即时生效的方法。

即时生效

实现方案是,以事件驱动,动态修改定时任务。

定义事件

@Getter  
public class ScheduleTaskUpdateEvent extends ApplicationEvent {  
  
    private final Integer taskId;  
  
    public ScheduleTaskUpdateEvent(Object source, Integer taskId) {  
        super(source);  
        this.taskId = taskId;  
    }  
}

构造调度任务程序

@Configuration  
public class SchedulerConfig {  
  
    @Bean  
    public ThreadPoolTaskScheduler taskScheduler() {  
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();  
        scheduler.setPoolSize(10); // 设置线程池大小  
        scheduler.setThreadNamePrefix("scheduled-task-");  
        scheduler.initialize();  
        return scheduler;  
    }  
}

动态任务配置

@Component  
public class DynamicScheduleTaskConfig implements ApplicationListener<ScheduleTaskUpdateEvent> {  
  
    @Resource  
    private ITestJobService jobService;  
  
    @Resource  
    private TaskScheduler taskScheduler;  
  
    private final Map<Integer, ScheduledFuture<?>> scheduledTasks = new ConcurrentHashMap<>();  
  
    @PostConstruct  
    private void initializeTasks() {  
        List<TestJob> list = jobService.list();  
        list.forEach(job -> {  
            ScheduledFuture<?> future = scheduleTask(job);  
            scheduledTasks.put(job.getId(), future);  
        });  
    }  
  
    // 根据任务配置创建任务  
    private ScheduledFuture<?> scheduleTask(TestJob job) {  
        System.out.println("创建新的定时任务,id:" + job.getId() + ", cron: " + job.getCron());  
        return taskScheduler.schedule(  
                () -> System.out.println("执行动态定时任务2: " + LocalDateTime.now()),  
                triggerContext -> {  
                    return new CronTrigger(job.getCron()).nextExecutionTime(triggerContext).toInstant();  
                }  
        );  
    }  
  
    @Override  
    public void onApplicationEvent(ScheduleTaskUpdateEvent event) {  
        System.out.println("收到修改定时任务事件,任务id:" + event.getTaskId());  
        // 取消并移除旧任务  
        ScheduledFuture<?> future = scheduledTasks.get(event.getTaskId());  
        if (future != null) {  
            future.cancel(false);  
            scheduledTasks.remove(event.getTaskId());  
        }  
  
        // 获取最新的任务配置并重新注册该任务  
        TestJob job = jobService.getById(event.getTaskId());  
        ScheduledFuture<?> newFuture = scheduleTask(job);  
        scheduledTasks.put(job.getId(), newFuture);  
    }  
}

修改接口,增加事件

@GetMapping("upd")  
public String upd(@RequestParam("cron") String cron) {  
    jobService.updateById(new TestJob(1, cron));  
    eventPublisher.publishEvent(new ScheduleTaskUpdateEvent(this, 1));  
    System.out.println("修改时间:"+ LocalDateTime.now());  
    return "success";  
}

结果

图片图片

可以看到,在收到修改任务的事件后,直接删除了原来的定时任务,创建了一个新的执行任务,即时生效,不需要等待一个执行周期就可立即执行。

小结

通过上述方法,我们可以在 Spring Boot 应用中实现动态计划任务,使得任务的执行更加灵活可控。

还实验了几种不同的方式,比如每秒轮询数据库、手动计算cron表达式 的执行时间。感觉就属这个事件驱动的方式最优雅。

责任编辑:武晓燕 来源: Java技术指北
相关推荐

2010-03-09 17:45:11

Linux计划任务

2010-01-07 11:19:45

Linux计划任务

2017-03-27 08:30:00

Windows 8Windows计划任务

2010-01-20 10:27:16

Linuxcrond命

2023-10-06 12:15:02

2021-01-08 08:47:56

Saltstack架构管理服务器

2017-07-14 11:50:15

Windows 10Windows计划任务

2017-11-23 09:00:02

Linuxcron计划任务

2017-06-09 13:17:04

Linuxcronanacron

2019-05-29 08:00:00

Linux计划任务cron

2011-04-01 09:17:01

备份cactiwindows

2010-05-04 12:29:15

Oracle数据库

2021-01-14 16:14:06

Python爬虫代码

2023-06-29 07:55:52

Quartz.Net开源

2010-05-04 12:43:11

Oracle数据库

2010-11-15 09:10:43

CrontabLinux计划任务

2024-11-11 10:58:03

Spring接口编程

2021-03-16 21:30:27

Windows 10Windows操作系统

2010-01-07 11:10:00

cron配置计划

2018-10-08 14:00:28

LinuxCron定时任务
点赞
收藏

51CTO技术栈公众号