web-dev-qa-db-fra.com

Modifier le calendrier du planificateur de manière dynamique en fonction de la condition utilisée avec l'annotation Spring-boot @Scheduled

J'ai un planificateur, qui se déclenche avec un retard fixe de 5 secondes.
Je prévois d'avoir plusieurs planificateurs, mais pour l'instant, restons sur un seul planificateur.

Exigence: Selon le planificateur de conditions commerciales fixedDelay doit être modifié.
** par exemple, ** par défaut fixedDelay est 5secs, mais cela peut être 6, 8, 10secs, basé sur la condition.

Donc, pour y parvenir, j'essaie de modifier le fixedDelay. Mais ça ne marche pas pour moi.

Code:
Interface, avec méthodes de retard.

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


Implémentation de l'interface de déclenchement située à org.springframework.scheduling dans le projet 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:

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


Classe de test, pour tester l'utilisation.

@EnableScheduling
@Component
public class TestSchedulerComponent {

    @Autowired
    private CustomDynamicSchedule dynamicSchedule;

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

}



J'ai pris l'aide de https://stackoverflow.com/a/51333059/4770397 ,

Mais malheureusement, ce code ne fonctionne pas pour moi.
Le planificateur s'exécute à fixedDelay, il n'y a pas de changement.

S'il vous plaît aider ..

9
mayank bisht

En utilisant @Scheduled ne permettra d'utiliser que des plannings statiques. Vous pouvez utiliser des propriétés pour rendre la planification configurable comme ceci

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

// or

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

Cependant, le calendrier résultant sera fixé une fois votre contexte de printemps initialisé (l'application est lancée).

Pour obtenir un contrôle précis sur une exécution planifiée, vous devez implémenter un Trigger - personnalisé similaire à ce que vous avez déjà fait. Avec la tâche à exécuter, ce déclencheur peut être enregistré en implémentant SchedulingConfigurer dans votre @Configuration classe utilisant 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();
    }
}

Mais ne faites pas d'enregistrement d'une tâche dans le CustomDynamicSchedule utilisez-le simplement pour calculer le temps d'exécution suivant:

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

Mais n'oubliez pas de sécuriser le thread CustomDynamicSchedule car il sera créé en tant que singleton au printemps et pourrait être accessible par plusieurs threads en parallèle.

9
dpr

Avec l'annotation, vous ne pouvez le faire que par approximation en trouvant un dénominateur commun et en l'interrogeant. Je te montrerai plus tard. Si vous voulez une vraie solution dynamique, vous ne pouvez pas utiliser d'annotations mais vous pouvez utiliser la configuration programmatique. Le positif de cette solution est que vous pouvez changer la période d'exécution même en runtime! Voici un exemple sur la façon de procéder:

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

Il existe un moyen de tricher et de faire quelque chose avec des annotations. Mais vous ne pouvez le faire que si la précision n'est pas importante. Qu'est-ce que cela signifie précision. Si vous savez que vous voulez le démarrer toutes les 5 secondes mais plus ou moins 100 ms, ce n'est pas important. Si vous savez que vous devez démarrer toutes les 5-6-8 ou 10 secondes, vous pouvez configurer un travail qui s'exécute toutes les secondes et vérifier dans une seule instruction if combien de temps s'est-il écoulé depuis l'exécution précédente. C'est très boiteux, mais ça marche :) tant que vous n'avez pas besoin d'une précision allant jusqu'à la milliseconde. Voici un exemple:

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

}

Je suis un peu boiteux mais je ferai le travail pour les cas simples.

0
Alexandar Petrov

L'annotation @Scheduled De Spring ne fournit pas cette prise en charge.

Ceci est ma mise en œuvre préférée d'une fonctionnalité similaire à l'aide d'une solution basée sur la file d'attente qui permet une flexibilité de synchronisation et une mise en œuvre très robuste de cette fonctionnalité de planificateur.

Ceci est le pipeline- enter image description here

  1. Cron Maintainer and Publisher - Pour chaque tâche, il existe un cron affilié et un service d'exécuteur unique qui est responsable de la publication des messages à la file d'attente selon le cron. Les mappages task-cron Sont conservés dans le database et sont initialisés au démarrage. De plus, nous avons un API exposé pour mettre à jour cron pour une tâche lors de l'exécution.

    Nous fermons simplement l'ancien service d'exécuteur programmé et en créons un chaque fois que nous déclenchons un changement dans le cron via notre API. En outre, nous mettons à jour le même dans la base de données.

  2. File d'attente - Utilisé pour stocker les messages publiés par l'éditeur.
  3. Planificateur - C'est là que réside la logique métier des planificateurs. Un écouteur sur la file d'attente (Kafka, dans notre cas) écoute les messages entrants et appelle la tâche du planificateur correspondant dans un nouveau thread chaque fois qu'il reçoit un message pour le même.

Cette approche présente divers avantages. Cela dissocie le planificateur de la tâche de gestion du calendrier. Désormais, le planificateur peut se concentrer uniquement sur la logique métier. De plus, nous pouvons écrire autant d'ordonnanceurs que nous voulons, tous écoutant la même file d'attente et agissant en conséquence.

0
Mukul Bansal