Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I have a scheduler, which triggers at a fixed delay of 5secs.
I am planning to have more than one schedulers, but for now, let's stick to just one scheduler.

Requirement: Based on business condition scheduler's fixedDelay should be changed.
**e.g, ** default fixedDelay is 5secs, but it can be 6, 8, 10secs, based on condition.

So, in order to acheive this, I am trying to modify the fixedDelay. But it's not working for me.

Code:
Interface, with delay methods.

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


Implementating Trigger interface that is located at org.springframework.scheduling in the spring-context project.

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:

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


Test class, to test the usage.

@EnableScheduling
@Component
public class TestSchedulerComponent {

    @Autowired
    private CustomDynamicSchedule dynamicSchedule;

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

}



I have took help of https://stackoverflow.com/a/51333059/4770397,

But unfortunately, this code is not working for me.
Scheduler is running at fixedDelay, there is not change in that.

Please help..

like image 230
mayank bisht Avatar asked May 25 '19 05:05

mayank bisht


People also ask

What is @scheduled annotation in spring?

The @EnableScheduling annotation is used to enable the scheduler for your application. This annotation should be added into the main Spring Boot application class file. @SpringBootApplication @EnableScheduling public class DemoApplication { public static void main(String[] args) { SpringApplication.

What is @scheduled in spring?

Spring Core. Spring provides excellent support for both task scheduling and asynchronous method execution based on cron expression using @Scheduled annotation. The @Scheduled annotation can be added to a method along with trigger metadata.

What is @scheduled in Java?

The schedule (TimerTask task, Date time) method of Timer class is used to schedule the task for execution at the given time. If the time given is in the past, the task is scheduled at that movement for execution.


2 Answers

Spring's @Scheduled annotation does not provide this support.

This is my preferred implementation of a similar feature using a Queue-based solution which allows flexibility of timing and very robust implementation of this scheduler functionality.

This is the pipeline- enter image description here

  1. Cron Maintainer and Publisher- For every task, there is an affiliated cron and a single-threaded executor service which is responsible for publishing messages to the Queue as per the cron. The task-cron mappings are persisted in the database and are initialized during the startup. Also, we have an API exposed to update cron for a task at runtime.

    We simply shut down the old scheduled executor service and create a one whenever we trigger a change in the cron via our API. Also, we update the same in the database.

  2. Queue- Used for storing messages published by the publisher.
  3. Scheduler- This is where the business logic of the schedulers reside. A listener on the Queue(Kafka, in our case) listens for the incoming messages and invokes the corresponding scheduler task in a new thread whenever it receives a message for the same.

There are various advantages to this approach. This decouples the scheduler from the schedule management task. Now, the scheduler can focus on business logic alone. Also, we can write as many schedulers as we want, all listening to the same queue and acting accordingly.

like image 99
Mukul Bansal Avatar answered Oct 16 '22 23:10

Mukul Bansal


Using @Scheduled will only allow to use static schedules. You can use properties to make the schedule configruable like this

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

// or

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

However the resulting schedule will be fixed once your spring context is initialized (the application is started).

To get fine grained control over a scheduled execution you need implement a custom Trigger - similar to what you already did. Together with the to be executed task, this trigger can be registered by implementing SchedulingConfigurer in your @Configuration class using 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();
    }
}

But don't do any registration of a task in the CustomDynamicSchedule just use it to compute the next execution time:

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

But remember to make CustomDynamicSchedule thread safe as it will be created as singleton by spring and might be accessed by multiple threads in parallel.

like image 12
dpr Avatar answered Oct 16 '22 23:10

dpr