Planificateur de tâches pour Java inspiré par la nécessité d'un java.util.concurrent.ScheduledExecutorService
en cluster plus simple que Quartz.
A ce titre, également apprécié par les utilisateurs (cbarbosa2, rafaelhofmann, BukhariH) :
Votre bibliothèque est géniale ! Je suis tellement contente de m'être débarrassé du Quartz et de l'avoir remplacé par le vôtre qui est bien plus facile à manipuler !
cbarbosa2
Voir aussi pourquoi pas Quartz ?
< dependency >
< groupId >com.github.kagkarlsson</ groupId >
< artifactId >db-scheduler</ artifactId >
< version >15.0.0</ version >
</ dependency >
Créez la table scheduled_tasks
dans votre schéma de base de données. Voir la définition du tableau pour postgresql, oracle, mssql ou mysql.
Instanciez et démarrez le planificateur, qui démarrera ensuite toutes les tâches récurrentes définies.
RecurringTask < Void > hourlyTask = Tasks . recurring ( "my-hourly-task" , FixedDelay . ofHours ( 1 ))
. execute (( inst , ctx ) -> {
System . out . println ( "Executed!" );
});
final Scheduler scheduler = Scheduler
. create ( dataSource )
. startTasks ( hourlyTask )
. threads ( 5 )
. build ();
// hourlyTask is automatically scheduled on startup if not already started (i.e. exists in the db)
scheduler . start ();
Pour plus d’exemples, continuez à lire. Pour plus de détails sur le fonctionnement interne, voir Comment ça marche. Si vous disposez d'une application Spring Boot, consultez Spring Boot Utilisation.
Liste des organisations connues pour exécuter db-scheduler en production :
Entreprise | Description |
---|---|
Digipost | Fournisseur de boîtes aux lettres numériques en Norvège |
Groupe Vy | L'un des plus grands groupes de transport des pays nordiques. |
Sage | Un moyen rapide et bon marché d’envoyer de l’argent à l’étranger. |
Formation professionnelle Becker | |
Monitorie | Service de surveillance de sites Web. |
Chargeur | Tests de charge pour les applications Web. |
Statens légumes | L'Administration norvégienne des routes publiques |
Année-lumière | Un moyen simple et accessible d’investir votre argent à l’échelle mondiale. |
VNI | L'Administration norvégienne du travail et de la protection sociale |
Boucle moderne | Adaptez-vous aux besoins de recrutement de votre entreprise en utilisant ModernLoop pour accroître l'efficacité de la planification des entretiens, de la communication et de la coordination. |
Diffia | Entreprise norvégienne de cybersanté |
Cygne | Swan aide les développeurs à intégrer facilement des services bancaires dans leur produit. |
TOMRA | TOMRA est une société multinationale norvégienne qui conçoit et fabrique des distributeurs automatiques inversés pour le recyclage. |
N'hésitez pas à ouvrir un PR pour ajouter votre organisation à la liste.
Voir aussi les exemples exécutables.
Définissez une tâche récurrente et planifiez la première exécution de la tâche au démarrage à l'aide de la méthode de création startTasks
. Une fois terminée, la tâche sera reprogrammée selon le planning défini (voir les types de planning prédéfinis).
RecurringTask < Void > hourlyTask = Tasks . recurring ( "my-hourly-task" , FixedDelay . ofHours ( 1 ))
. execute (( inst , ctx ) -> {
System . out . println ( "Executed!" );
});
final Scheduler scheduler = Scheduler
. create ( dataSource )
. startTasks ( hourlyTask )
. registerShutdownHook ()
. build ();
// hourlyTask is automatically scheduled on startup if not already started (i.e. exists in the db)
scheduler . start ();
Pour les tâches récurrentes avec plusieurs instances et planifications, voir l'exemple RecurringTaskWithPersistentScheduleMain.java.
Une instance d'une tâche ponctuelle a une seule heure d'exécution dans le futur (c'est-à-dire non récurrente). L'identifiant de l'instance doit être unique au sein de cette tâche et peut être utilisé pour encoder certaines métadonnées (par exemple un identifiant). Pour un état plus complexe, les objets Java sérialisables personnalisés sont pris en charge (tels qu'utilisés dans l'exemple).
Définissez une tâche ponctuelle et démarrez le planificateur :
TaskDescriptor < MyTaskData > MY_TASK =
TaskDescriptor . of ( "my-onetime-task" , MyTaskData . class );
OneTimeTask < MyTaskData > myTaskImplementation =
Tasks . oneTime ( MY_TASK )
. execute (( inst , ctx ) -> {
System . out . println ( "Executed! Custom data, Id: " + inst . getData (). id );
});
final Scheduler scheduler = Scheduler
. create ( dataSource , myTaskImplementation )
. registerShutdownHook ()
. build ();
scheduler . start ();
... puis à un moment donné (au moment de l'exécution), une exécution est planifiée à l'aide du SchedulerClient
:
// Schedule the task for execution a certain time in the future and optionally provide custom data for the execution
scheduler . schedule (
MY_TASK
. instanceWithId ( "1045" )
. data ( new MyTaskData ( 1001L ))
. scheduledTo ( Instant . now (). plusSeconds ( 5 )));
Exemple | Description |
---|---|
EnableImmediateExecutionMain.java | Lors de la planification d'exécutions pour qu'elles s'exécutent now() ou plus tôt, le Scheduler local en sera informé et se "réveillera" pour vérifier les nouvelles exécutions plus tôt qu'il ne le ferait normalement (comme configuré par pollingInterval . |
MaxRetriesMain.java | Comment définir une limite sur le nombre de tentatives qu'une exécution peut avoir. |
ExponentialBackoffMain.java | Comment utiliser l'intervalle exponentiel comme stratégie de nouvelle tentative au lieu du délai fixe comme c'est le cas par défaut. |
ExponentialBackoffWithMaxRetriesMain.java | Comment utiliser l'intervalle exponentiel comme stratégie de nouvelle tentative et une limite stricte sur le nombre maximum de tentatives. |
TrackingProgressRecurringTaskMain.java | Les tâches récurrentes peuvent stocker task_data afin de conserver l'état au fil des exécutions. Cet exemple montre comment. |
SpawningOtherTasksMain.java | Démontre les instances de planification de tâches d'un autre à l'aide de l' executionContext.getSchedulerClient() . |
SchedulerClientMain.java | Démontre certaines des fonctionnalités de SchedulerClient . Planification, récupération des exécutions planifiées, etc. |
RecurringTaskWithPersistentScheduleMain.java | Travaux récurrents multi-instances où le Schedule est stocké dans le cadre de task_data . Par exemple, adapté aux applications multi-locataires où chaque locataire doit avoir une tâche récurrente. |
StatefulRecurringTaskWithPersistentScheduleMain.java | |
JsonSerializerMain.java | Remplace la sérialisation de task_data de la sérialisation Java (par défaut) vers JSON. |
JobChainingUsingTaskDataMain.java | Enchaînement de tâches, c'est-à-dire "une fois l'exécution de cette instance terminée, planifiez une autre tâche. |
JobChainingUsingSeparateTasksMain.java | Enchaînement des tâches, comme ci-dessus. |
IntercepteurMain.java | Utilisation ExecutionInterceptor pour injecter de la logique avant et après l'exécution pour tous ExecutionHandler . |
Exemple | Description |
---|---|
Exemples de base | Une tâche ponctuelle de base et une tâche récurrente |
TransactionallyStagedJob | Exemple de mise en scène transactionnelle d'un travail, c'est-à-dire s'assurer que le travail en arrière-plan s'exécute si la transaction est validée (avec d'autres modifications de base de données). |
Travail de longue durée | Les tâches de longue durée doivent survivre aux redémarrages des applications et éviter de redémarrer depuis le début. Cet exemple montre comment conserver la progression à l'arrêt et en outre une technique pour limiter l'exécution du travail la nuit. |
RecurringStateTracking | Une tâche récurrente avec un état modifiable après chaque exécution. |
ParallelJobSpawner | Montre comment utiliser une tâche récurrente pour générer des tâches ponctuelles, par exemple pour la parallélisation. |
Enchaînement de tâches | Un travail ponctuel avec plusieurs étapes . L'étape suivante est planifiée une fois la précédente terminée. |
MultiInstanceRécurrent | Montre comment réaliser plusieurs tâches récurrentes du même type, mais avec des planifications et des données potentiellement différentes. |
Le planificateur est créé à l'aide du générateur Scheduler.create(...)
. Le constructeur a des valeurs par défaut raisonnables, mais les options suivantes sont configurables.
.threads(int)
Nombre de fils. Par défaut 10
.
.pollingInterval(Duration)
À quelle fréquence le planificateur vérifie la base de données pour les exécutions prévues. Par défaut 10s
.
.alwaysPersistTimestampInUTC()
Le planificateur suppose que les colonnes pour les horodatages persistants persistent dans les Instant
, et non dans les LocalDateTime
, c'est-à-dire qu'elles lient d'une manière ou d'une autre l'horodatage à une zone. Cependant, certaines bases de données ont une prise en charge limitée pour ces types (qui ne contiennent aucune information de zone) ou d'autres bizarreries, ce qui fait de « toujours stocker en UTC » une meilleure alternative. Dans de tels cas, utilisez ce paramètre pour toujours stocker les instants au format UTC. PostgreSQL et les schémas Oracle sont testés pour préserver les informations de zone. Les schémas MySQL et MariaDB n'utilisent pas et devraient utiliser ce paramètre. NB : Pour des raisons de compatibilité ascendante, le comportement par défaut des bases de données « inconnues » est de supposer que la base de données préserve le fuseau horaire. Pour les bases de données « connues », voir la classe AutodetectJdbcCustomization
.
.enableImmediateExecution()
Si cette option est activée, le planificateur tentera d'indiquer au Scheduler
local qu'il y a des exécutions à exécuter après leur exécution planifiée now()
, ou à une heure dans le passé. NB : Si l'appel à schedule(..)
/ reschedule(..)
intervient depuis une transaction, le planificateur peut tenter de l'exécuter avant que la mise à jour ne soit visible (la transaction n'a pas été validée). Il persiste cependant, donc même s'il s'agit d'un échec, il s'exécutera avant le prochain polling-interval
. Vous pouvez également déclencher par programme une vérification anticipée des exécutions dues à l'aide de la méthode Scheduler ( scheduler.triggerCheckForDueExecutions()
). false
par défaut.
.registerShutdownHook()
Enregistre un hook d'arrêt qui appellera Scheduler.stop()
à l'arrêt. Stop doit toujours être appelé pour un arrêt en douceur et pour éviter des exécutions mortes.
.shutdownMaxWait(Duration)
Combien de temps le planificateur attendra avant d’interrompre les threads du service exécuteur. Si vous utilisez cela, réfléchissez s'il est possible de vérifier régulièrement à la place executionContext.getSchedulerState().isShuttingDown()
dans ExecutionHandler et d'abandonner la tâche de longue durée. Par défaut 30min
.
.enablePriority()
Il est possible de définir une priorité pour les exécutions qui détermine l'ordre dans lequel les exécutions dues sont récupérées dans la base de données. Une exécution avec une valeur de priorité plus élevée s'exécutera avant une exécution avec une valeur inférieure (techniquement, l'ordre sera order by priority desc, execution_time asc
). Pensez à utiliser des priorités comprises entre 0 et 32 000 car le champ est défini comme un SMALLINT
. Si vous avez besoin d'une valeur plus grande, modifiez le schéma. Pour l'instant, cette fonctionnalité est facultative et priority
des colonnes n'est nécessaire que pour les utilisateurs qui choisissent d'activer la priorité via ce paramètre de configuration.
Définissez la priorité par instance à l'aide de TaskInstance.Builder
:
scheduler . schedule (
MY_TASK
. instance ( "1" )
. priority ( 100 )
. scheduledTo ( Instant . now ()));
Note:
(execution_time asc, priority desc)
(en remplacement de l'ancien execution_time asc
).null
pour la priorité peut être interprétée différemment selon la base de données (faible ou élevée). Si vous exécutez > 1 000 exécutions/s, vous souhaiterez peut-être utiliser la stratégie d'interrogation lock-and-fetch
pour réduire les frais généraux et un débit plus élevé (en savoir plus). Sinon, la fetch-and-lock-on-execute
par défaut conviendra.
.pollUsingFetchAndLockOnExecute(double, double)
Utilisez la stratégie d'interrogation par défaut fetch-and-lock-on-execute
.
Si la dernière récupération de la base de données était un lot complet ( executionsPerBatchFractionOfThreads
), une nouvelle récupération sera déclenchée lorsque le nombre d'exécutions restantes sera inférieur ou égal à lowerLimitFractionOfThreads * nr-of-threads
. Les exécutions récupérées ne sont pas verrouillées/sélectionnées, de sorte que le planificateur entrera en compétition avec d'autres instances pour le verrouillage lors de son exécution. Pris en charge par toutes les bases de données.
Valeurs par défaut : 0,5, 3.0
.pollUsingLockAndFetch(double, double)
Utilisez la stratégie d'interrogation lock-and-fetch
qui utilise select for update .. skip locked
pour moins de surcharge.
Si la dernière récupération de la base de données était un lot complet, une nouvelle récupération sera déclenchée lorsque le nombre d'exécutions restantes sera inférieur ou égal à lowerLimitFractionOfThreads * nr-of-threads
. Le nombre d'exécutions récupérées à chaque fois est égal à (upperLimitFractionOfThreads * nr-of-threads) - nr-executions-left
. Les exécutions récupérées sont déjà verrouillées/sélectionnées pour cette instance de planificateur, économisant ainsi une instruction UPDATE
.
Pour une utilisation normale, réglez par exemple sur 0.5, 1.0
.
Pour un débit élevé (c'est-à-dire garder les threads occupés), définissez par exemple sur 1.0, 4.0
. Actuellement, les pulsations ne sont pas mises à jour pour les exécutions sélectionnées dans la file d'attente (applicable si upperLimitFractionOfThreads > 1.0
). S'ils restent là pendant plus de 4 * heartbeat-interval
(par défaut 20m
), sans démarrer l'exécution, ils seront détectés comme morts et seront probablement à nouveau déverrouillés (déterminé par DeadExecutionHandler
). Actuellement pris en charge par postgres . sql-server le prend également en charge, mais les tests ont montré que cela est sujet aux blocages et n'est donc pas recommandé tant qu'il n'est pas compris/résolu.
.heartbeatInterval(Duration)
À quelle fréquence mettre à jour l’horodatage du battement de cœur pour les exécutions en cours. Par défaut 5m
.
.missedHeartbeatsLimit(int)
Combien de battements de cœur peuvent être manqués avant que l'exécution ne soit considérée comme morte. Par défaut 6
.
.addExecutionInterceptor(ExecutionInterceptor)
Ajoute un ExecutionInterceptor
qui peut injecter de la logique autour des exécutions. Pour Spring Boot, enregistrez simplement un Bean de type ExecutionInterceptor
.
.addSchedulerListener(SchedulerListener)
Ajoute un SchedulerListener
qui recevra les événements liés au planificateur et à l'exécution. Pour Spring Boot, enregistrez simplement un Bean de type SchedulerListener
.
.schedulerName(SchedulerName)
Nom de cette instance de planificateur. Le nom est stocké dans la base de données lorsqu'une exécution est sélectionnée par un planificateur. <hostname>
par défaut.
.tableName(String)
Nom de la table utilisée pour suivre les exécutions de tâches. Modifiez le nom dans les définitions de la table en conséquence lors de la création de la table. scheduled_tasks
par défaut .
.serializer(Serializer)
Implémentation du sérialiseur à utiliser lors de la sérialisation des données de tâche. Par défaut, la sérialisation Java standard est utilisée, mais db-scheduler regroupe également un GsonSerializer
et JacksonSerializer
. Voir des exemples pour un KotlinSerializer. Voir également la documentation supplémentaire sous Sérialiseurs.
.executorService(ExecutorService)
Si spécifié, utilisez ce service d'exécution géré en externe pour exécuter des exécutions. Idéalement, le nombre de threads qu'il utilisera devrait toujours être fourni (pour les optimisations des interrogations du planificateur). null
par défaut.
.deleteUnresolvedAfter(Duration)
Le temps après lequel les exécutions avec des tâches inconnues sont automatiquement supprimées. Il s’agit généralement d’anciennes tâches récurrentes qui ne sont plus utilisées. Cette valeur est différente de zéro pour éviter la suppression accidentelle de tâches suite à une erreur de configuration (tâches connues manquantes) et à des problèmes lors des mises à niveau propagées. Par défaut 14d
.
.jdbcCustomization(JdbcCustomization)
db-scheduler essaie de détecter automatiquement la base de données utilisée pour voir si des interactions jdbc doivent être personnalisées. Cette méthode est une trappe d'échappement permettant de définir explicitement JdbcCustomizations
. Détection automatique par défaut.
.commitWhenAutocommitDisabled(boolean)
Par défaut, aucune validation n'est émise sur les connexions DataSource. Si la validation automatique est désactivée, il est supposé que les transactions sont gérées par un gestionnaire de transactions externe. Définissez cette propriété sur true
pour remplacer ce comportement et permettre au planificateur d'émettre toujours des validations. false
par défaut.
.failureLogging(Level, boolean)
Configure comment enregistrer les échecs de tâches, c'est-à-dire Throwable
lancés par un gestionnaire d'exécution de tâches. Utilisez le niveau de journalisation OFF
pour désactiver complètement ce type de journalisation. WARN, true
.
Les tâches sont créées à l'aide de l'une des classes de générateur de Tasks
. Les constructeurs ont des valeurs par défaut raisonnables, mais les options suivantes peuvent être remplacées.
Option | Défaut | Description |
---|---|---|
.onFailure(FailureHandler) | voir desc. | Que faire lorsqu'un ExecutionHandler lève une exception. Par défaut, les tâches récurrentes sont replanifiées en fonction de leur Schedule . Les tâches ponctuelles sont réessayées toutes les 5 minutes. |
.onDeadExecution(DeadExecutionHandler) | ReviveDeadExecution | Que faire lorsqu'une exécution morte est détectée, c'est-à-dire une exécution avec un horodatage de battement de cœur périmé. Par défaut, les exécutions mortes sont reprogrammées à now() . |
.initialData(T initialData) | null | Données à utiliser la première fois qu'une tâche récurrente est planifiée. |
La bibliothèque contient un certain nombre d'implémentations de planification pour les tâches récurrentes. Voir Schedules
des cours.
Calendrier | Description |
---|---|
.daily(LocalTime ...) | Fonctionne tous les jours à des heures précises. En option, un fuseau horaire peut être spécifié. |
.fixedDelay(Duration) | L'heure d'exécution suivante est Duration après la dernière exécution terminée. Remarque : Cette Schedule planifie l'exécution initiale sur Instant.now() lorsqu'elle est utilisée dans startTasks(...) |
.cron(String) | Expression cron de style printemps (v5.3+). Le modèle - est interprété comme un horaire désactivé. |
Une autre option pour configurer les planifications consiste à lire les modèles de chaînes avec Schedules.parse(String)
.
Les modèles actuellement disponibles sont :
Modèle | Description |
---|---|
FIXED_DELAY|Ns | Identique à .fixedDelay(Duration) avec une durée définie sur N secondes. |
DAILY|12:30,15:30...(|time_zone) | Identique à .daily(LocalTime) avec un fuseau horaire optionnel (par exemple Europe/Rome, UTC) |
- | Horaire désactivé |
Plus de détails sur les formats de fuseau horaire peuvent être trouvés ici.
Un Schedule
peut être marqué comme désactivé. Le planificateur ne planifiera pas les exécutions initiales pour les tâches dont la planification est désactivée et supprimera toutes les exécutions existantes pour cette tâche.
Une instance de tâche peut avoir des données associées dans le champ task_data
. Le planificateur utilise un Serializer
pour lire et écrire ces données dans la base de données. Par défaut, la sérialisation Java standard est utilisée, mais un certain nombre d'options sont proposées :
GsonSerializer
JacksonSerializer
Pour la sérialisation Java, il est recommandé de spécifier un serialVersionUID
pour pouvoir faire évoluer la classe représentant les données. Si elle n'est pas spécifiée et que la classe change, la désérialisation échouera probablement avec une InvalidClassException
. Si cela se produit, recherchez et définissez explicitement le serialVersionUID
actuel généré automatiquement. Il sera alors possible d'apporter des modifications ininterrompues à la classe.
Si vous devez migrer de la sérialisation Java vers un GsonSerializer
, configurez le planificateur pour utiliser un SerializerWithFallbackDeserializers
:
. serializer ( new SerializerWithFallbackDeserializers ( new GsonSerializer (), new JavaSerializer ()))
Pour les applications Spring Boot, il existe un démarreur db-scheduler-spring-boot-starter
qui rend le câblage du planificateur très simple. (Voir exemple de projet complet).
DataSource
fonctionnelle avec un schéma initialisé. (Dans l'exemple, HSQLDB est utilisé et le schéma est automatiquement appliqué.)< dependency >
< groupId >com.github.kagkarlsson</ groupId >
< artifactId >db-scheduler-spring-boot-starter</ artifactId >
< version >15.0.0</ version >
</ dependency >
Task
en tant que beans Spring. S’ils sont récurrents, ils seront automatiquement récupérés et démarrés.Scheduler
dans les informations sur l'état de l'actionneur, vous devez activer l'indicateur d'état db-scheduler
. Informations sur la santé du printemps. La configuration se fait principalement via application.properties
. La configuration du nom du planificateur, du sérialiseur et du service d'exécution se fait en ajoutant un bean de type DbSchedulerCustomizer
à votre contexte Spring.
# application.properties example showing default values
db-scheduler.enabled=true
db-scheduler.heartbeat-interval=5m
db-scheduler.polling-interval=10s
db-scheduler.polling-limit=
db-scheduler.table-name=scheduled_tasks
db-scheduler.immediate-execution-enabled=false
db-scheduler.scheduler-name=
db-scheduler.threads=10
db-scheduler.priority-enabled=false
# Ignored if a custom DbSchedulerStarter bean is defined
db-scheduler.delay-startup-until-context-ready=false
db-scheduler.polling-strategy=fetch
db-scheduler.polling-strategy-lower-limit-fraction-of-threads=0.5
db-scheduler.polling-strategy-upper-limit-fraction-of-threads=3.0
db-scheduler.shutdown-max-wait=30m
Il est possible d'utiliser le Scheduler
pour interagir avec les futures exécutions persistantes. Pour les situations où une instance complète Scheduler
n'est pas nécessaire, un SchedulerClient plus simple peut être créé à l'aide de son générateur :
SchedulerClient . Builder . create ( dataSource , taskDefinitions ). build ()
Il permettra des opérations telles que :
Une seule table de base de données est utilisée pour suivre les futures exécutions de tâches. Lorsqu'une exécution de tâche est due, db-scheduler la sélectionne et l'exécute. Une fois l'exécution terminée, la Task
est consultée pour voir ce qu'il faut faire. Par exemple, une RecurringTask
est généralement reprogrammée dans le futur en fonction de sa Schedule
.
Le planificateur utilise le verrouillage optimiste ou la sélection pour mise à jour (en fonction de la stratégie d'interrogation) pour garantir qu'une et une seule instance du planificateur peut sélectionner et exécuter une exécution de tâche.
Le terme tâche récurrente est utilisé pour désigner les tâches qui doivent être exécutées régulièrement, selon un certain calendrier.
Lorsque l'exécution d'une tâche récurrente est terminée, un Schedule
est consulté pour déterminer quelle devrait être la prochaine heure d'exécution, et une future exécution de tâche est créée pour cette heure (c'est-à-dire qu'elle est reprogrammée ). L'heure choisie sera l'heure la plus proche selon le Schedule
, mais toujours dans le futur.
Il existe deux types de tâches récurrentes, la tâche récurrente statique régulière, où la Schedule
est définie de manière statique dans le code, et les tâches récurrentes dynamiques , où la Schedule
est définie au moment de l'exécution et conservée dans la base de données (ne nécessitant toujours qu'une seule table). .
La tâche récurrente statique est la plus courante et convient aux tâches d'arrière-plan régulières puisque le planificateur planifie automatiquement une instance de la tâche si elle n'est pas présente et met également à jour l'heure d'exécution suivante si la Schedule
est mise à jour.
Pour créer l'exécution initiale d'une tâche récurrente statique, le planificateur dispose d'une méthode startTasks(...)
qui prend une liste de tâches qui doivent être "démarrées" si elles n'ont pas déjà une exécution existante. Le délai d'exécution initial est déterminé par le Schedule
. Si la tâche a déjà une exécution future (c'est-à-dire a été démarrée au moins une fois auparavant), mais qu'un Schedule
mis à jour indique maintenant une autre heure d'exécution, l'exécution existante sera reprogrammée à la nouvelle heure d'exécution (à l'exception des tâches non déterministes ). (horaires tels que FixedDelay
où le nouveau temps d'exécution est plus éloigné dans le futur).
Créez en utilisant Tasks.recurring(..)
.
La tâche récurrente dynamique est un ajout ultérieur à db-scheduler et a été ajoutée pour prendre en charge les cas d'utilisation où plusieurs instances du même type de tâche (c'est-à-dire la même implémentation) avec des calendriers différents sont nécessaires. Le Schedule
est conservé dans task_data
aux côtés de toutes les données régulières. Contrairement à la tâche récurrente statique , la tâche dynamique ne planifiera pas automatiquement les instances de la tâche. Il appartient à l'utilisateur de créer des instances et de mettre à jour le planning de celles existantes si nécessaire (à l'aide de l'interface SchedulerClient
). Voir l'exemple RecurringTaskWithPersistentScheduleMain.java pour plus de détails.
Créez en utilisant Tasks.recurringWithPersistentSchedule(..)
.
Le terme tâche unique est utilisé pour les tâches qui ont un temps d'exécution unique. En plus d'encoder les données dans l' instanceId
d'une exécution de tâche, il est possible de stocker des données binaires arbitraires dans un champ séparé pour les utiliser au moment de l'exécution. Par défaut, la sérialisation Java est utilisée pour marshaler/démarshaler les données.
Créez en utilisant Tasks.oneTime(..)
.
Pour les tâches ne correspondant pas aux catégories ci-dessus, il est possible de personnaliser entièrement le comportement des tâches à l'aide de Tasks.custom(..)
.
Les cas d'utilisation peuvent être :
Pendant l'exécution, le planificateur met régulièrement à jour un temps de pulsation pour l'exécution de la tâche. Si une exécution est marquée comme en cours d'exécution, mais ne reçoit pas de mises à jour du temps de pulsation, elle sera considérée comme une exécution morte après le temps X. Cela peut par exemple se produire si la JVM exécutant le planificateur se ferme soudainement.
Lorsqu'une exécution morte est trouvée, la Task
est consultée pour voir ce qui doit être fait. Une RecurringTask
morte est généralement reprogrammée à now()
.
Alors que db-scheduler était initialement destiné aux cas d'utilisation à débit faible à moyen, il gère assez bien les cas d'utilisation à haut débit (plus de 1 000 exécutions/seconde) en raison du fait que son modèle de données est très simple, composé de un seul tableau des exécutions. Pour comprendre comment il fonctionnera, il est utile de considérer les instructions SQL qu'il exécute par lot d'exécutions.
La stratégie d'interrogation d'origine et par défaut, fetch-and-lock-on-execute
, effectuera les opérations suivantes :
select
un lot d'exécutions duesupdate
l'exécution avec picked=true
pour cette instance de planificateur. Peut manquer en raison de planificateurs concurrents.update
ou delete
l'enregistrement en fonction des gestionnaires.En somme par lot : 1 sélection, 2 * mises à jour par lot (hors échecs)
Dans la v10, une nouvelle stratégie d'interrogation ( lock-and-fetch
) a été ajoutée. Il utilise le fait que la plupart des bases de données prennent désormais en charge SKIP LOCKED
dans les instructions SELECT FOR UPDATE
(voir le blog du 2e quadrant). En utilisant une telle stratégie, il est possible de récupérer des exécutions pré-verrouillées, et ainsi d'obtenir une instruction en moins :
select for update .. skip locked
un lot d'exécutions dues. Ceux-ci seront déjà sélectionnés par l’instance du planificateur.update
ou delete
l'enregistrement en fonction des gestionnaires.En somme par lot : 1 sélection et mise à jour, 1 * mises à jour par lot (aucun échec)
Pour avoir une idée de ce à quoi s'attendre de db-scheduler, consultez les résultats des tests exécutés dans GCP ci-dessous. Les tests ont été exécutés avec quelques configurations différentes, mais chacune utilisant 4 instances de planificateur concurrentes exécutées sur des machines virtuelles distinctes. TPS est l'env. transactions par seconde, comme indiqué dans GCP.
Récupération de débit (ex/s) | Récupération TPS (estimations) | Verrouillage et récupération du débit (ex/s) | Verrouillage et récupération TPS (estimations) | |
---|---|---|---|---|
Postgres 4 cœurs, 25 Go de RAM, 4 x VM (2 cœurs) | ||||
20 fils, inférieur 4,0, supérieur 20,0 | 2000 | 9000 | 10600 | 11500 |
100 fils, inférieur 2.0, supérieur 6.0 | 2560 | 11000 | 11200 | 11200 |
Postgres 8 cœurs, 50 Go de RAM, 4xVM (4 cœurs) | ||||
50 fils, inférieur : 0,5, supérieur : 4,0 | 4000 | 22000 | 11840 | 10300 |
Observations pour ces tests :
fetch-and-lock-on-execute
lock-and-fetch
Actuellement, la stratégie d'interrogation lock-and-fetch
est implémentée uniquement pour Postgres. Les contributions ajoutant la prise en charge de davantage de bases de données sont les bienvenues.
Un certain nombre d'utilisateurs utilisent db-scheduler pour des cas d'utilisation à haut débit. Voir par exemple :
Il n'y a aucune garantie que tous les instants d'une planification pour une RecurringTask
seront exécutés. Le Schedule
est consulté une fois l'exécution de la tâche précédente terminée, et l'heure la plus proche dans le futur sera sélectionnée pour l'heure d'exécution suivante. Un nouveau type de tâche pourrait être ajouté à l'avenir pour fournir une telle fonctionnalité.
Les méthodes sur SchedulerClient
( schedule
, cancel
, reschedule
) s'exécuteront en utilisant une nouvelle Connection
à partir de la DataSource
fournie. Pour que l'action fasse partie d'une transaction, elle doit être prise en charge par le DataSource
fourni, par exemple en utilisant quelque chose comme TransactionAwareDataSourceProxy
de Spring.
Actuellement, la précision de db-scheduler dépend du pollingInterval
(10 s par défaut) qui spécifie la fréquence à laquelle rechercher dans le tableau les exécutions dues. Si vous savez ce que vous faites, le planificateur peut être invité au moment de l'exécution à "rechercher plus tôt" via scheduler.triggerCheckForDueExecutions()
. (Voir aussi enableImmediateExecution()
sur le Builder
)
Voir les versions pour les notes de version.
Mise à niveau vers 15.x
priority
de colonne et l'index priority_execution_time_idx
doivent être ajoutés au schéma de la base de données. Voir les définitions des tableaux pour postgresql, oracle ou mysql. À un moment donné, cette colonne deviendra obligatoire. Cela sera précisé dans les futures notes de version/mise à niveau.Mise à niveau vers 8.x
boolean isDeterministic()
pour indiquer s'ils produiront toujours les mêmes instants ou non.Mise à niveau vers 4.x
consecutive_failures
au schéma de base de données. Voir les définitions des tableaux pour postgresql, oracle ou mysql. null
est géré comme 0, donc pas besoin de mettre à jour les enregistrements existants.Mise à niveau vers 3.x
Tasks
Mise à niveau vers 2.x
task_data
au schéma de base de données. Voir les définitions des tableaux pour postgresql, oracle ou mysql. Conditions préalables
Suivez ces étapes :
Clonez le référentiel.
git clone https://github.com/kagkarlsson/db-scheduler
cd db-scheduler
Construire en utilisant Maven (ignorer les tests en ajoutant -DskipTests=true
)
mvn package
Spécification recommandée
Certains utilisateurs ont rencontré des échecs de test intermittents lors de l'exécution sur des machines virtuelles monocœur. Il est donc recommandé d’utiliser au minimum :
db-scheduler
quand il existe Quartz
? L'objectif de db-scheduler
est d'être non invasif et simple à utiliser, tout en résolvant le problème de persistance et le problème de coordination des clusters. Il était initialement destiné aux applications avec des schémas de base de données modestes, auxquelles l'ajout de 11 tables semblerait un peu excessif. Mise à jour : De plus, pour l'instant (2024), Quartz ne semble pas non plus être activement maintenu.
BAISER. Il s’agit du type le plus courant d’applications à état partagé.
Veuillez créer un problème avec la demande de fonctionnalité et nous pourrons en discuter ici. Si vous êtes impatient (ou avez envie de contribuer), les pull request sont les bienvenues :)
Oui. Il est utilisé dans la production de plusieurs entreprises et fonctionne jusqu'à présent sans problème.