簡體   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