繁体   English   中英

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

[英]Modify scheduler timing dynamically based on the condition used with spring-boot @Scheduled annotation

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

需求:根据业务条件调度器的fixedDelay应该改变。
**例如,**默认的fixedDelay5secs ,但根据条件可以是6, 8, 10secs

因此,为了实现这一点,我正在尝试修改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运行,没有变化。

请帮忙..

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

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

// or

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

但是,一旦您的 spring 上下文初始化(应用程序启动),结果计划将被修复。

要对预定执行进行细粒度控制,您需要实现一个自定义Trigger - 类似于您已经做过的。 与要执行的任务一起,可以通过在@Configuration类中使用ScheduledTaskRegistrar.addTriggerTask实现SchedulingConfigurer来注册此触发器:

@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 创建为单例,并且可能被多个线程并行访问。

Spring 的@Scheduled注释不提供这种支持。

这是我使用基于队列的解决方案的类似功能的首选实现,它允许时间的灵活性和此调度程序功能的非常健壮的实现。

这是管道—— 在此处输入图片说明

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

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

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

这种方法有多种优点。 这将调度程序与调度管理任务分离。 现在,调度程序可以单独关注业务逻辑。 此外,我们可以根据需要编写任意数量的调度程序,所有调度程序都侦听相同的队列并相应地执行操作。

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

  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
   }
}

}

我有点蹩脚,但会在简单的情况下完成这项工作。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM