Динамическое изменение расписания планировщика в зависимости от условия, используемого с аннотацией spring-boot @Scheduled - PullRequest
8 голосов
/ 25 мая 2019

У меня есть планировщик, который запускается с фиксированной задержкой в ​​5 секунд.
Я планирую иметь более одного планировщика, но сейчас давайте придерживаться только одного планировщика.

Требование: На основе планировщика бизнес-условий необходимо изменить fixedDelay .
** например, ** по умолчанию fixedDelay равно 5secs, но это может быть 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);
}


Реализация интерфейса Trigger, расположенного в org.springframework.scheduling в проекте Spring-context.

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 , в этом нет изменений.

Пожалуйста, помогите ..

Ответы [ 3 ]

7 голосов
/ 27 мая 2019

Использование @Scheduled позволит использовать только статические расписания.Вы можете использовать свойства, чтобы сделать расписание настраиваемым, как это

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

// or

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

Однако полученный график будет исправлен после инициализации весеннего контекста (запускается приложение).

Чтобы получить мелкозернистую структуруДля контроля над запланированным выполнением вам необходимо реализовать пользовательский 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 безопасным, так как он будет создан как singletonк весне и могут быть доступны для нескольких потоков параллельно.

0 голосов
/ 28 мая 2019

С помощью annottaion вы можете сделать это только путем приближения, найдя общий знаменатель и опросив его. Я сделаю вам покупки позже. Если вы хотите настоящее динамическое решение, вы не можете использовать аннотации, но вы можете использовать программную конфигурацию. Положительным моментом этого решения является то, что вы можете изменить период выполнения даже во время выполнения! Вот пример того, как это сделать:

  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 секунд, вы можете настроить одно задание, которое выполняется каждую секунду, и проверить в течение одного утверждения, сколько времени прошло с момента предыдущего выполнения. Это очень неубедительно, но работает :), если вам не нужна точность с точностью до миллисекунды. Вот пример:

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 голосов
/ 27 мая 2019

Аннотация @Scheduled Spring не предоставляет эту поддержку.

Это моя предпочтительная реализация аналогичной функции с использованием решения на основе очередей, которое обеспечивает гибкость синхронизации и очень надежную реализацию этой функциональности планировщика.

Это конвейер - enter image description here

  1. Cron Maintainer and Publisher - Для каждой задачи есть дочерний cron иоднопоточный сервис-исполнитель, который отвечает за публикацию сообщений в очереди согласно cron.Отображения task-cron сохраняются в database и инициализируются во время запуска.Кроме того, у нас есть API для обновления cron для задачи во время выполнения.

    Мы просто закрываем старый запланированный сервис executor и создаем его каждый раз, когда запускаем изменение в cron через наш API.Кроме того, мы обновляем то же самое в базе данных.

  2. Очередь - Используется для хранения сообщений, опубликованных издателем.
  3. Планировщик - Здесьбизнес-логика планировщиков постоянно.Слушатель в очереди (в нашем случае Kafka) прослушивает входящие сообщения и вызывает соответствующую задачу планировщика в новом потоке всякий раз, когда он получает сообщение для того же самого.

У этого подхода есть различные преимущества.Это отделяет планировщик от задачи управления расписанием.Теперь планировщик может сосредоточиться только на бизнес-логике.Кроме того, мы можем написать столько планировщиков, сколько захотим, все слушают одну и ту же очередь и действуют соответственно.

...