根据 spring-boot @Scheduled 注解使用的条件动态修改调度程序计时

问题描述 投票:0回答:5

我有一个调度程序,它以 5 秒的固定延迟触发。我计划拥有多个调度程序,但现在我们只使用一个调度程序。

要求:根据业务条件调度程序的fixedDelay应更改(例如,默认fixedDelay5秒,但可以是6、8、10秒,根据条件)。

为了实现这一点,我正在尝试修改fixedDelay。但这对我不起作用。

接口,带有延迟方法

public abstract class DynamicSchedule{
        /**
         * Delays scheduler
         * @param milliseconds - the time to delay scheduler.
         */
        abstract void delay(Long milliseconds);

        /**
         * Decreases delay period
         * @param milliseconds - the time to decrease delay period.
         */
        abstract void decreaseDelayInterval(Long milliseconds);

        /**
         * Increases delay period
         * @param milliseconds - the time to increase dela period
        */
        abstract void increaseDelayInterval(Long milliseconds);
}

实现位于 spring-context 项目中的 org.springframework.scheduling 的 Trigger 接口。

import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;

import java.util.Date;
import java.util.concurrent.ScheduledFuture;

public class CustomDynamicSchedule extends DynamicSchedule implements Trigger {

    private TaskScheduler taskScheduler;
    private ScheduledFuture<?> schedulerFuture;

    /**
     * milliseconds
     */
    private long delayInterval;

    public CustomDynamicSchedule(TaskScheduler taskScheduler) {
        this.taskScheduler = taskScheduler;
    }


    @Override
    public void increaseDelayInterval(Long delay) {
        if (schedulerFuture != null) {
            schedulerFuture.cancel(true);
        }
        this.delayInterval += delay;
        schedulerFuture = taskScheduler.schedule(() -> { }, this);
    }

    @Override
    public void decreaseDelayInterval(Long delay) {
        if (schedulerFuture != null) {
            schedulerFuture.cancel(true);
        }
        this.delayInterval += delay;
        schedulerFuture = taskScheduler.schedule(() -> { }, this);
    }

    @Override
    public void delay(Long delay) {
        if (schedulerFuture != null) {
            schedulerFuture.cancel(true);
        }
        this.delayInterval = delay;
        schedulerFuture = taskScheduler.schedule(() -> { }, this);
    }

    @Override
    public Date nextExecutionTime(TriggerContext triggerContext) {
        Date lastTime = triggerContext.lastActualExecutionTime();
        return (lastTime == null) ? new Date() : new Date(lastTime.getTime() + delayInterval);
    }
}

配置:

@Configuration
public class DynamicSchedulerConfig {
    @Bean
    public CustomDynamicSchedule getDinamicScheduler() {
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
        threadPoolTaskScheduler.initialize();
        return  new CustomDynamicSchedule(threadPoolTaskScheduler);
    }
}

测试类,测试使用情况。

@EnableScheduling
@Component
public class TestSchedulerComponent {

    @Autowired
    private CustomDynamicSchedule dynamicSchedule;

    @Scheduled(fixedDelay = 5000)
    public void testMethod() {
        dynamicSchedule.delay(1000l);
        dynamicSchedule.increaseDelayInterval(9000l);
        dynamicSchedule.decreaseDelayInterval(5000l);
    }

}

我尝试使用https://stackoverflow.com/a/51333059/4770397但此代码对我不起作用。

调度程序以 fixedDelay 运行,没有任何变化。

java spring scheduled-tasks
5个回答
12
投票

使用

@Scheduled
只允许使用静态计划。您可以使用属性来使时间表可配置,如下所示

@Scheduled(cron = "${yourConfiguration.cronExpression}")

// or

@Scheduled(fixedDelayString = "${yourConfiguration.fixedDelay}")

但是,一旦 Spring 上下文初始化(应用程序启动),结果时间表将被修复。

要对计划执行进行细粒度控制,您需要实现自定义

Trigger
- 类似于您已经所做的。与要执行的任务一起,可以通过使用
SchedulingConfigurer
@Configuration
类中实现
ScheduledTaskRegistrar.addTriggerTask
来注册此触发器:

@Configuration
@EnableScheduling
public class AppConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(taskScheduler());
        taskRegistrar.addTriggerTask(() -> myTask().work(), myTrigger());
    }

    @Bean(destroyMethod="shutdown")
    public Executor taskScheduler() {
        return Executors.newScheduledThreadPool(42);
    }

    @Bean
    public CustomDynamicSchedule myTrigger() {
        new CustomDynamicSchedule();
    }

    @Bean
    public MyTask myTask() {
        return new MyTask();
    }
}

但是不要在

CustomDynamicSchedule
中对任务进行任何注册,只需使用它来计算下一次执行时间:

public class CustomDynamicSchedule extends DynamicSchedule implements Trigger {

    private long delayInterval;

    @Override
    public synchronized void increaseDelayInterval(Long delay) {
        this.delayInterval += delay;
    }

    @Override
    public synchronized void decreaseDelayInterval(Long delay) {
        this.delayInterval += delay;
    }

    @Override
    public synchronized void delay(Long delay) {
        this.delayInterval = delay;
    }

    @Override
    public Date nextExecutionTime(TriggerContext triggerContext) {
        Date lastTime = triggerContext.lastActualExecutionTime();
        return (lastTime == null) ? new Date() : new Date(lastTime.getTime() + delayInterval);
    }
}

但请记住使

CustomDynamicSchedule
线程安全,因为它将由 spring 创建为单例,并且可能被多个线程并行访问。


1
投票

Spring 的

@Scheduled
注解不提供此支持。

这是我使用基于队列的解决方案实现类似功能的首选,它允许灵活的计时以及非常强大的调度程序功能实现。

这是管道——

  1. Cron 维护者和发布者 - 对于每个任务,都有一个附属的
    cron
    和一个单线程执行器服务,负责根据 cron 将消息发布到队列。
    task-cron
    映射保留在
    database
    中,并在启动期间初始化。此外,我们还公开了一个
    API
    ,用于在运行时更新任务的 cron。

    我们只需关闭旧的计划执行器服务,并在每次通过 API 触发 cron 中的更改时创建一个服务。此外,我们也在数据库中更新了相同的内容。

  2. Queue- 用于存储发布者发布的消息。
  3. 调度程序 - 这是调度程序的业务逻辑所在的位置。队列上的侦听器(在我们的例子中为 Kafka)侦听传入消息,并在收到相同消息时在新线程中调用相应的调度程序任务。

这种方法有多种优点。这将调度程序与调度管理任务解耦。现在,调度程序可以只专注于业务逻辑。此外,我们可以编写任意数量的调度程序,所有调度程序都监听同一个队列并相应地执行操作。


1
投票

使用注释,您只能通过找到公分母并对其进行民意调查来进行近似。稍后我会告诉你。如果您想要一个真正的动态解决方案,您不能使用注释,但可以使用编程配置。 此解决方案的优点是您甚至可以在运行时更改执行周期!以下是如何执行此操作的示例:

  public initializeDynamicScheduledTAsk (ThreadPoolTaskScheduler scheduler,Date start,long executionPeriod) {
    scheduler.schedule(
      new ScheduledTask(),
      new Date(startTime),period
    );
    }
class ScheduledTask implements Runnable{


    @Override
    public void run() {
       // my scheduled logic here 
    }
}

有一种方法可以作弊并实际使用注释做一些事情。但只有当精度不重要时才可以这样做。精度是什么意思。如果你知道你想每 5 秒启动一次,但多或少 100 毫秒并不重要。如果您知道需要每 5-6-8 或 10 秒启动一次,您可以配置一个每秒执行一次的作业,并在一个 if 语句中检查自上次执行以来已经过去了多长时间。它非常蹩脚,但只要您不需要达到毫秒精度,它就可以工作:)。这是一个例子:

public class SemiDynamicScheduledService {

private Long lastExecution;
@Value(#{yourDynamicConfiguration})
private int executeEveryInMS

@Scheduled(fixedDelay=1000)
public semiDynamicScheduledMethod() {
   if (System.currentTimeMS() - lastExecution>executeEveryInMS) {
      lastExecution = System.currentTimeMS();
      // put your processing logic here
   }
}

}

我有点蹩脚,但对于简单的情况我会做的。


0
投票

Spring 不直接支持使用变量作为调度程序的频率。编写自定义主调度程序(假设每秒执行一次)来控制其他调度程序是一个不错的选择。


0
投票

这可以通过实现spring的Trigger类来实现。

由此我们可以推导出动态的下一个调度,限制是我们不能执行 nextSchedule 之前的调度。

© www.soinside.com 2019 - 2024. All rights reserved.