Taskplaner für Java, der von der Notwendigkeit eines geclusterten java.util.concurrent.ScheduledExecutorService
inspiriert wurde, der einfacher als Quartz ist.
Daher auch von Benutzern geschätzt (cbarbosa2, rafaelhofmann, BukhariH):
Deine Freiheit rockt! Ich bin so froh, dass ich Quartz losgeworden bin und es durch Ihres ersetzt habe, was viel einfacher zu handhaben ist!
cbarbosa2
Siehe auch Warum nicht Quarz?
< dependency >
< groupId >com.github.kagkarlsson</ groupId >
< artifactId >db-scheduler</ artifactId >
< version >15.0.0</ version >
</ dependency >
Erstellen Sie die Tabelle scheduled_tasks
in Ihrem Datenbankschema. Siehe Tabellendefinition für Postgresql, Oracle, MSSQL oder MySQL.
Instanziieren und starten Sie den Planer, der dann alle definierten wiederkehrenden Aufgaben startet.
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 ();
Für weitere Beispiele lesen Sie weiter. Einzelheiten zum Innenleben finden Sie unter Funktionsweise. Wenn Sie eine Spring Boot-Anwendung haben, werfen Sie einen Blick auf Spring Boot Usage.
Liste der Organisationen, von denen bekannt ist, dass sie db-scheduler in der Produktion einsetzen:
Unternehmen | Beschreibung |
---|---|
Digipost | Anbieter digitaler Postfächer in Norwegen |
Vy-Gruppe | Einer der größten Transportkonzerne in den nordischen Ländern. |
Weise | Eine günstige und schnelle Möglichkeit, Geld ins Ausland zu senden. |
Becker Berufsausbildung | |
Monitoria | Website-Überwachungsdienst. |
Loadster | Lasttests für Webanwendungen. |
Statens Gemüse | Die norwegische öffentliche Straßenverwaltung |
Lichtjahr | Eine einfache und zugängliche Möglichkeit, Ihr Geld weltweit anzulegen. |
NAV | Die norwegische Arbeits- und Sozialverwaltung |
ModernLoop | Skalieren Sie mit den Personalanforderungen Ihres Unternehmens, indem Sie ModernLoop nutzen, um die Effizienz bei der Planung, Kommunikation und Koordination von Vorstellungsgesprächen zu steigern. |
Diffia | Norwegisches eHealth-Unternehmen |
Schwan | Swan hilft Entwicklern, Bankdienstleistungen einfach in ihr Produkt einzubetten. |
TOMRA | TOMRA ist ein norwegisches multinationales Unternehmen, das Rücknahmeautomaten für das Recycling entwickelt und herstellt. |
Öffnen Sie gerne eine PR, um Ihre Organisation zur Liste hinzuzufügen.
Siehe auch ausführbare Beispiele.
Definieren Sie eine wiederkehrende Aufgabe und planen Sie die erste Ausführung der Aufgabe beim Start mithilfe der Builder-Methode startTasks
. Nach Abschluss wird die Aufgabe gemäß dem definierten Zeitplan neu geplant (siehe vordefinierte Zeitplantypen).
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 ();
Informationen zu wiederkehrenden Aufgaben mit mehreren Instanzen und Zeitplänen finden Sie im Beispiel RecurringTaskWithPersistentScheduleMain.java.
Eine Instanz einer einmaligen Aufgabe hat einen einzigen Ausführungszeitpunkt, der irgendwann in der Zukunft liegt (dh einmalig). Die Instanz-ID muss innerhalb dieser Aufgabe eindeutig sein und kann zum Kodieren einiger Metadaten (z. B. einer ID) verwendet werden. Für komplexere Zustände werden benutzerdefinierte serialisierbare Java-Objekte unterstützt (wie im Beispiel verwendet).
Definieren Sie eine einmalige Aufgabe und starten Sie den Scheduler:
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 ();
... und dann wird irgendwann (zur Laufzeit) eine Ausführung mit dem SchedulerClient
geplant:
// 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 )));
Beispiel | Beschreibung |
---|---|
EnableImmediateExecutionMain.java | Wenn Ausführungen so geplant werden, dass sie now() oder früher ausgeführt werden, wird der lokale Scheduler darüber informiert und „wacht auf“, um früher als normalerweise nach neuen Ausführungen zu suchen (wie durch pollingInterval konfiguriert). |
MaxRetriesMain.java | So legen Sie ein Limit für die Anzahl der Wiederholungsversuche fest, die eine Ausführung haben kann. |
ExponentialBackoffMain.java | So verwenden Sie exponentielles Backoff als Wiederholungsstrategie anstelle der standardmäßigen festen Verzögerung. |
ExponentialBackoffWithMaxRetriesMain.java | Verwendung des exponentiellen Backoffs als Wiederholungsstrategie und Festlegung einer festen Grenze für die maximale Anzahl von Wiederholungsversuchen. |
TrackingProgressRecurringTaskMain.java | Wiederkehrende Jobs können task_data speichern, um den Status über Ausführungen hinweg beizubehalten. Dieses Beispiel zeigt wie. |
SpawningOtherTasksMain.java | Demonstriert die Aufgabenplanung von Instanzen einer anderen Instanz mithilfe von executionContext.getSchedulerClient() . |
SchedulerClientMain.java | Demonstriert einige der Funktionen des SchedulerClient . Planen, Abrufen geplanter Ausführungen usw. |
RecurringTaskWithPersistentScheduleMain.java | Wiederkehrende Aufträge mit mehreren Instanzen, bei denen der Schedule als Teil der task_data gespeichert wird. Geeignet zum Beispiel für Multi-Tenant-Anwendungen, bei denen jeder Mandant eine wiederkehrende Aufgabe haben soll. |
StatefulRecurringTaskWithPersistentScheduleMain.java | |
JsonSerializerMain.java | Überschreibt die Serialisierung von task_data von Java-Serialisierung (Standard) zu JSON. |
JobChainingUsingTaskDataMain.java | Jobverkettung, d. h. „Wenn die Ausführung dieser Instanz abgeschlossen ist, planen Sie eine weitere Aufgabe.“ |
JobChainingUsingSeparateTasksMain.java | Jobverkettung, wie oben. |
InterceptorMain.java | Verwenden von ExecutionInterceptor zum Einfügen von Logik vor und nach der Ausführung für alle ExecutionHandler . |
Beispiel | Beschreibung |
---|---|
GrundlegendeBeispiele | Eine grundlegende einmalige Aufgabe und eine wiederkehrende Aufgabe |
TransactionallyStagedJob | Beispiel für die transaktionale Bereitstellung eines Jobs, d. h. Sicherstellen, dass der Hintergrundjob ausgeführt wird , wenn die Transaktion festgeschrieben wird (zusammen mit anderen Datenbankänderungen). |
LongRunningJob | Langzeitjobs müssen Anwendungsneustarts überstehen und einen Neustart von Anfang an vermeiden. Dieses Beispiel zeigt, wie der Fortschritt beim Herunterfahren aufrechterhalten wird , und außerdem eine Technik, um den Job auf die nächtliche Ausführung zu beschränken. |
RecurringStateTracking | Eine wiederkehrende Aufgabe mit einem Status, der nach jeder Ausführung geändert werden kann. |
ParallelJobSpawner | Demonstriert, wie man einen wiederkehrenden Job verwendet, um einmalige Jobs zu erzeugen, z. B. für die Parallelisierung. |
JobChaining | Ein einmaliger Job mit mehreren Schritten . Der nächste Schritt wird geplant, nachdem der vorherige abgeschlossen ist. |
MultiInstanceRecurring | Zeigt, wie mehrere wiederkehrende Aufträge desselben Typs, aber möglicherweise unterschiedlicher Zeitpläne und Daten, erreicht werden können. |
Der Scheduler wird mit dem Builder Scheduler.create(...)
erstellt. Der Builder verfügt über sinnvolle Standardeinstellungen, die folgenden Optionen sind jedoch konfigurierbar.
.threads(int)
Anzahl der Threads. Standard 10
.
.pollingInterval(Duration)
Wie oft überprüft der Scheduler die Datenbank auf fällige Ausführungen? Standard 10s
.
.alwaysPersistTimestampInUTC()
Der Scheduler geht davon aus, dass Spalten für persistente Zeitstempel Instant
s und nicht LocalDateTime
s beibehalten, dh den Zeitstempel irgendwie an eine Zone binden. Einige Datenbanken bieten jedoch nur begrenzte Unterstützung für solche Typen (die keine Zoneninformationen enthalten) oder andere Besonderheiten, sodass „Immer in UTC speichern“ eine bessere Alternative ist. Verwenden Sie in solchen Fällen diese Einstellung, um Instants immer in UTC zu speichern. PostgreSQL- und Oracle-Schemata werden getestet, um Zoneninformationen beizubehalten. MySQL- und MariaDB -Schemas verwenden diese Einstellung nicht und sollten sie auch nicht verwenden. Hinweis: Aus Gründen der Abwärtskompatibilität wird bei „unbekannten“ Datenbanken standardmäßig davon ausgegangen, dass die Datenbank die Zeitzone beibehält. Informationen zu „bekannten“ Datenbanken finden Sie in der Klasse AutodetectJdbcCustomization
.
.enableImmediateExecution()
Wenn dies aktiviert ist, versucht der Scheduler, dem lokalen Scheduler
mitzuteilen, dass Ausführungen ausgeführt werden müssen, nachdem deren Ausführung now()
geplant ist, oder zu einem Zeitpunkt in der Vergangenheit. Hinweis: Wenn der Aufruf von schedule(..)
/ reschedule(..)
innerhalb einer Transaktion erfolgt, versucht der Scheduler möglicherweise, ihn auszuführen, bevor die Aktualisierung sichtbar ist (die Transaktion wurde nicht festgeschrieben). Es bleibt jedoch weiterhin bestehen, sodass es auch bei einem Fehlschlag vor dem nächsten polling-interval
ausgeführt wird. Sie können auch programmgesteuert eine frühe Prüfung auf fällige Ausführungen auslösen, indem Sie die Scheduler-Methode scheduler.triggerCheckForDueExecutions()
) verwenden. Standardmäßig false
.
.registerShutdownHook()
Registriert einen Shutdown-Hook, der beim Herunterfahren Scheduler.stop()
aufruft. Stop sollte immer aufgerufen werden, um ein ordnungsgemäßes Herunterfahren zu gewährleisten und tote Hinrichtungen zu vermeiden.
.shutdownMaxWait(Duration)
Wie lange der Scheduler wartet, bevor er Executor-Service-Threads unterbricht. Wenn Sie dies verwenden, überlegen Sie, ob es nicht möglich ist, stattdessen regelmäßig die Ausführung executionContext.getSchedulerState().isShuttingDown()
im ExecutionHandler zu überprüfen und eine lang laufende Aufgabe abzubrechen. Standardmäßig 30min
.
.enablePriority()
Es ist möglich, eine Priorität für Ausführungen zu definieren, die die Reihenfolge bestimmt, in der fällige Ausführungen aus der Datenbank abgerufen werden. Eine Ausführung mit einem höheren Wert für die Priorität wird vor einer Ausführung mit einem niedrigeren Wert ausgeführt (technisch gesehen erfolgt die Reihenfolge order by priority desc, execution_time asc
). Erwägen Sie die Verwendung von Prioritäten im Bereich von 0 bis 32000, da das Feld als SMALLINT
definiert ist. Wenn Sie einen größeren Wert benötigen, ändern Sie das Schema. Derzeit ist diese Funktion optional und die priority
wird nur von Benutzern benötigt, die sich dafür entscheiden, die Priorität über diese Konfigurationseinstellung zu aktivieren.
Legen Sie die Priorität pro Instanz mit dem TaskInstance.Builder
fest:
scheduler . schedule (
MY_TASK
. instance ( "1" )
. priority ( 100 )
. scheduledTo ( Instant . now ()));
Notiz:
(execution_time asc, priority desc)
hinzuzufügen (der den alten execution_time asc
ersetzt).null
für die Priorität kann je nach Datenbank unterschiedlich interpretiert werden (niedrig oder hoch). Wenn Sie mehr als 1000 Ausführungen/s ausführen, möchten Sie möglicherweise die lock-and-fetch
Polling-Strategie für einen geringeren Overhead und einen höheren Durchsatz verwenden (weiterlesen). Wenn nicht, ist das standardmäßige fetch-and-lock-on-execute
in Ordnung.
.pollUsingFetchAndLockOnExecute(double, double)
Verwenden Sie die Standardabfragestrategie „ fetch-and-lock-on-execute
.
Wenn es sich bei dem letzten Abruf aus der Datenbank um einen vollständigen Batch handelte ( executionsPerBatchFractionOfThreads
), wird ein neuer Abruf ausgelöst, wenn die Anzahl der verbleibenden Ausführungen kleiner oder gleich lowerLimitFractionOfThreads * nr-of-threads
ist. Abgerufene Ausführungen werden nicht gesperrt/ausgewählt, sodass der Scheduler bei der Ausführung mit anderen Instanzen um die Sperre konkurriert. Unterstützt von allen Datenbanken.
Standardwerte: 0,5, 3.0
.pollUsingLockAndFetch(double, double)
Verwenden Sie die Polling-Strategie lock-and-fetch
bei der select for update .. skip locked
.
Wenn der letzte Abruf aus der Datenbank ein vollständiger Batch war, wird ein neuer Abruf ausgelöst, wenn die Anzahl der verbleibenden Ausführungen kleiner oder gleich lowerLimitFractionOfThreads * nr-of-threads
ist. Die Anzahl der jedes Mal abgerufenen Ausführungen ist gleich (upperLimitFractionOfThreads * nr-of-threads) - nr-executions-left
. Abgerufene Ausführungen sind für diese Scheduler-Instanz bereits gesperrt/ausgewählt, wodurch eine UPDATE
Anweisung eingespart wird.
Stellen Sie für den normalen Gebrauch beispielsweise 0.5, 1.0
ein.
Für einen hohen Durchsatz (d. h. Threads beschäftigt halten) setzen Sie ihn beispielsweise auf 1.0, 4.0
. Derzeit werden Hearbeats für ausgewählte Ausführungen in der Warteschlange nicht aktualisiert (anwendbar, wenn upperLimitFractionOfThreads > 1.0
). Wenn sie länger als 4 * heartbeat-interval
(Standard 20m
) dort bleiben und nicht mit der Ausführung beginnen, werden sie als tot erkannt und wahrscheinlich wieder entsperrt (bestimmt durch DeadExecutionHandler
). Wird derzeit von Postgres unterstützt. SQL-Server unterstützt dies ebenfalls, aber Tests haben gezeigt, dass dies anfällig für Deadlocks ist und daher nicht empfohlen wird, bis es verstanden/behoben wird.
.heartbeatInterval(Duration)
Wie oft der Heartbeat-Zeitstempel für laufende Ausführungen aktualisiert werden soll. Standard 5m
.
.missedHeartbeatsLimit(int)
Wie viele Herzschläge dürfen ausgelassen werden, bevor die Hinrichtung als tot gilt? Standard 6
.
.addExecutionInterceptor(ExecutionInterceptor)
Fügt einen ExecutionInterceptor
hinzu, der Ausführungen Logik hinzufügen kann. Registrieren Sie für Spring Boot einfach eine Bean vom Typ ExecutionInterceptor
.
.addSchedulerListener(SchedulerListener)
Fügt einen SchedulerListener
hinzu, der Scheduler- und Ausführungsereignisse empfängt. Registrieren Sie für Spring Boot einfach eine Bean vom Typ SchedulerListener
.
.schedulerName(SchedulerName)
Name dieser Scheduler-Instanz. Der Name wird in der Datenbank gespeichert, wenn eine Ausführung von einem Planer ausgewählt wird. Standard <hostname>
.
.tableName(String)
Name der Tabelle, die zum Verfolgen von Aufgabenausführungen verwendet wird. Ändern Sie beim Erstellen der Tabelle den Namen in den Tabellendefinitionen entsprechend. Standardmäßige scheduled_tasks
.
.serializer(Serializer)
Serialisierungsimplementierung zur Verwendung beim Serialisieren von Aufgabendaten. Standardmäßig wird die Standard-Java-Serialisierung verwendet, aber db-scheduler bündelt auch einen GsonSerializer
und JacksonSerializer
. Siehe Beispiele für einen KotlinSerializer. Siehe auch zusätzliche Dokumentation unter Serializer.
.executorService(ExecutorService)
Falls angegeben, verwenden Sie diesen extern verwalteten Executor-Dienst, um Ausführungen auszuführen. Idealerweise sollte weiterhin die Anzahl der verwendeten Threads angegeben werden (zur Optimierung der Scheduler-Abfrage). Standardwert null
.
.deleteUnresolvedAfter(Duration)
Die Zeit, nach der Ausführungen mit unbekannten Aufgaben automatisch gelöscht werden. Dies können typischerweise alte wiederkehrende Aufgaben sein, die nicht mehr verwendet werden. Dieser Wert ist ungleich Null, um ein versehentliches Entfernen von Aufgaben aufgrund eines Konfigurationsfehlers (fehlende bekannte Aufgaben) und Probleme bei fortlaufenden Upgrades zu verhindern. Standard 14d
.
.jdbcCustomization(JdbcCustomization)
Der DB-Scheduler versucht, die verwendete Datenbank automatisch zu erkennen, um festzustellen, ob JDBC-Interaktionen angepasst werden müssen. Bei dieser Methode handelt es sich um eine Escape-Schraffur, die das explizite Festlegen JdbcCustomizations
ermöglicht. Standardmäßige automatische Erkennung.
.commitWhenAutocommitDisabled(boolean)
Standardmäßig wird für DataSource-Verbindungen kein Commit ausgegeben. Wenn die automatische Festschreibung deaktiviert ist, wird davon ausgegangen, dass Transaktionen von einem externen Transaktionsmanager abgewickelt werden. Legen Sie diese Eigenschaft auf true
fest, um dieses Verhalten zu überschreiben und dafür zu sorgen, dass der Scheduler immer Commits ausgibt. Standardmäßig false
.
.failureLogging(Level, boolean)
Konfiguriert, wie Aufgabenfehler protokolliert werden, d. h. Throwable
s, die von einem Aufgabenausführungshandler ausgelöst werden. Verwenden Sie die Protokollebene OFF
um diese Art der Protokollierung vollständig zu deaktivieren. Standard- WARN, true
.
Aufgaben werden mit einer der Builder-Klassen in Tasks
erstellt. Die Builder verfügen über sinnvolle Standardeinstellungen, die folgenden Optionen können jedoch überschrieben werden.
Option | Standard | Beschreibung |
---|---|---|
.onFailure(FailureHandler) | siehe Abschn. | Was tun, wenn ein ExecutionHandler eine Ausnahme auslöst? Standardmäßig werden wiederkehrende Aufgaben gemäß ihrem Schedule neu geplant. Einmalige Aufgaben werden alle 5 Minuten erneut ausgeführt. |
.onDeadExecution(DeadExecutionHandler) | ReviveDeadExecution | Was ist zu tun, wenn tote Ausführungen erkannt werden, z. B. eine Ausführung mit einem veralteten Heartbeat-Zeitstempel? Standardmäßig werden tote Ausführungen auf now() verschoben. |
.initialData(T initialData) | null | Die Daten, die beim ersten Planen einer wiederkehrenden Aufgabe verwendet werden sollen. |
Die Bibliothek enthält eine Reihe von Schedule-Implementierungen für wiederkehrende Aufgaben. Siehe Schedules
.
Zeitplan | Beschreibung |
---|---|
.daily(LocalTime ...) | Läuft jeden Tag zu bestimmten Zeiten. Optional kann eine Zeitzone angegeben werden. |
.fixedDelay(Duration) | Die nächste Ausführungszeit ist Duration nach der letzten abgeschlossenen Ausführung. Hinweis: Dieser Schedule plant die erste Ausführung für Instant.now() wenn er in startTasks(...) verwendet wird. |
.cron(String) | Cron-Ausdruck im Spring-Stil (v5.3+). Das - wird als deaktivierter Zeitplan interpretiert. |
Eine weitere Option zum Konfigurieren von Zeitplänen ist das Lesen von Zeichenfolgenmustern mit Schedules.parse(String)
.
Die derzeit verfügbaren Muster sind:
Muster | Beschreibung |
---|---|
FIXED_DELAY|Ns | Identisch mit .fixedDelay(Duration) wobei die Dauer auf N Sekunden eingestellt ist. |
DAILY|12:30,15:30...(|time_zone) | Wie .daily(LocalTime) mit optionaler Zeitzone (z. B. Europa/Rom, UTC) |
- | Deaktivierter Zeitplan |
Weitere Details zu den Zeitzonenformaten finden Sie hier.
Ein Schedule
kann als deaktiviert markiert werden. Der Planer plant die ersten Ausführungen für Aufgaben mit deaktiviertem Zeitplan nicht und entfernt alle vorhandenen Ausführungen für diese Aufgabe.
Einer Aufgabeninstanz können im Feld task_data
einige zugehörige Daten zugeordnet sein. Der Scheduler verwendet einen Serializer
um diese Daten zu lesen und in die Datenbank zu schreiben. Standardmäßig wird die Standard-Java-Serialisierung verwendet, es stehen jedoch eine Reihe von Optionen zur Verfügung:
GsonSerializer
JacksonSerializer
Für die Java-Serialisierung wird empfohlen, eine serialVersionUID
anzugeben, um die Klasse weiterentwickeln zu können, die die Daten darstellt. Wenn keine Angabe erfolgt und sich die Klasse ändert, schlägt die Deserialisierung wahrscheinlich mit einer InvalidClassException
fehl. Sollte dies passieren, suchen Sie die aktuelle automatisch generierte serialVersionUID
und legen Sie sie explizit fest. Es ist dann möglich, nicht unterbrechende Änderungen an der Klasse vorzunehmen.
Wenn Sie von der Java-Serialisierung zu einem GsonSerializer
migrieren müssen, konfigurieren Sie den Scheduler für die Verwendung eines SerializerWithFallbackDeserializers
:
. serializer ( new SerializerWithFallbackDeserializers ( new GsonSerializer (), new JavaSerializer ()))
Für Spring Boot-Anwendungen gibt es einen Starter db-scheduler-spring-boot-starter
die Scheduler-Verkabelung sehr einfach macht. (Siehe vollständiges Beispielprojekt).
DataSource
mit initialisiertem Schema. (Im Beispiel wird HSQLDB verwendet und das Schema wird automatisch angewendet.)< dependency >
< groupId >com.github.kagkarlsson</ groupId >
< artifactId >db-scheduler-spring-boot-starter</ artifactId >
< version >15.0.0</ version >
</ dependency >
Task
als Spring Beans verfügbar. Wenn sie wiederkehrend sind, werden sie automatisch abgeholt und gestartet.Scheduler
Status in Informationen zum Aktuatorzustand anzeigen möchten, müssen Sie db-scheduler
-Gesundheitsindikator aktivieren. Gesundheitsinformationen im Frühling. Die Konfiguration erfolgt hauptsächlich über application.properties
. Die Konfiguration von Scheduler-Name, Serializer und Executor-Service erfolgt durch Hinzufügen einer Bean vom Typ DbSchedulerCustomizer
zu Ihrem Spring-Kontext.
# 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
Es ist möglich, den Scheduler
zu verwenden, um mit den persistenten zukünftigen Ausführungen zu interagieren. Für Situationen, in denen keine vollständige Scheduler
-Instanz benötigt wird, kann mit seinem Builder ein einfacherer SchedulerClient erstellt werden:
SchedulerClient . Builder . create ( dataSource , taskDefinitions ). build ()
Es ermöglicht Vorgänge wie:
Eine einzelne Datenbanktabelle wird verwendet, um zukünftige Aufgabenausführungen zu verfolgen. Wenn eine Aufgabenausführung fällig ist, wählt der DB-Scheduler sie aus und führt sie aus. Wenn die Ausführung abgeschlossen ist, wird die Task
konsultiert, um zu sehen, was getan werden soll. Beispielsweise wird eine RecurringTask
in der Regel basierend auf ihrem Schedule
für die Zukunft neu geplant.
Der Scheduler verwendet optimistisches Sperren oder Select-for-Update (abhängig von der Polling-Strategie), um sicherzustellen, dass nur eine Scheduler-Instanz eine Aufgabenausführung auswählen und ausführen kann.
Der Begriff wiederkehrende Aufgabe wird für Aufgaben verwendet, die nach einem bestimmten Zeitplan regelmäßig ausgeführt werden sollten.
Wenn die Ausführung einer wiederkehrenden Aufgabe abgeschlossen ist, wird ein Schedule
herangezogen, um den nächsten Zeitpunkt für die Ausführung zu bestimmen, und eine zukünftige Aufgabenausführung wird für diesen Zeitpunkt erstellt (dh sie wird neu geplant ). Die gewählte Zeit ist die nächstgelegene Zeit gemäß Schedule
, liegt jedoch noch in der Zukunft.
Es gibt zwei Arten wiederkehrender Aufgaben: die reguläre statische wiederkehrende Aufgabe, bei der der Schedule
statisch im Code definiert wird, und die dynamischen wiederkehrenden Aufgaben, bei denen der Schedule
zur Laufzeit definiert und in der Datenbank gespeichert wird (wobei immer noch nur eine einzige Tabelle erforderlich ist). .
Die statische wiederkehrende Aufgabe ist die gebräuchlichste und eignet sich für reguläre Hintergrundjobs, da der Planer automatisch eine Instanz der Aufgabe plant, wenn diese nicht vorhanden ist, und auch die nächste Ausführungszeit aktualisiert, wenn der Schedule
aktualisiert wird.
Um die erste Ausführung für eine statische wiederkehrende Aufgabe zu erstellen, verfügt der Scheduler über eine Methode startTasks(...)
, die eine Liste von Aufgaben entgegennimmt, die „gestartet“ werden sollen, wenn für sie noch keine Ausführung vorhanden ist. Die anfängliche Ausführungszeit wird durch den Schedule
bestimmt. Wenn die Aufgabe bereits über eine zukünftige Ausführung verfügt (d. h. bereits mindestens einmal gestartet wurde), ein aktualisierter Schedule
jedoch nun eine andere Ausführungszeit angibt, wird die bestehende Ausführung auf die neue Ausführungszeit umgeplant (mit Ausnahme von nicht deterministisch). Zeitpläne wie FixedDelay
, bei denen die neue Ausführungszeit weiter in der Zukunft liegt).
Erstellen Sie mit Tasks.recurring(..)
.
Die dynamische wiederkehrende Aufgabe ist eine spätere Ergänzung zu db-scheduler und wurde hinzugefügt, um Anwendungsfälle zu unterstützen, bei denen mehrere Instanzen desselben Aufgabentyps (d. h. derselben Implementierung) mit unterschiedlichen Zeitplänen erforderlich sind. Der Schedule
wird zusammen mit allen regulären Daten in den task_data
gespeichert. Im Gegensatz zur statischen wiederkehrenden Aufgabe werden bei der dynamischen Aufgabe Instanzen der Aufgabe nicht automatisch geplant. Es ist Sache des Benutzers, Instanzen zu erstellen und bei Bedarf den Zeitplan für vorhandene Instanzen zu aktualisieren (mithilfe der SchedulerClient
Schnittstelle). Weitere Einzelheiten finden Sie im Beispiel „RecurringTaskWithPersistentScheduleMain.java“.
Erstellen Sie mit Tasks.recurringWithPersistentSchedule(..)
.
Der Begriff einmalige Aufgabe wird für Aufgaben verwendet, die einen einzigen Ausführungszeitpunkt haben. Zusätzlich zur Kodierung von Daten in die instanceId
einer Aufgabenausführung ist es möglich, beliebige Binärdaten in einem separaten Feld zur Verwendung zur Ausführungszeit zu speichern. Standardmäßig wird die Java-Serialisierung zum Marshallen/Unmarshalieren der Daten verwendet.
Erstellen Sie mit Tasks.oneTime(..)
.
Für Aufgaben, die nicht in die oben genannten Kategorien passen, ist es möglich, das Verhalten der Aufgaben mithilfe von Tasks.custom(..)
vollständig anzupassen.
Anwendungsfälle könnten sein:
Während der Ausführung aktualisiert der Scheduler regelmäßig eine Heartbeat-Zeit für die Aufgabenausführung. Wenn eine Ausführung als ausgeführt markiert ist, aber keine Updates zur Heartbeat-Zeit erhält, wird sie nach Zeit X als tote Ausführung betrachtet. Dies kann beispielsweise passieren, wenn die JVM, auf der der Scheduler ausgeführt wird, plötzlich beendet wird.
Wenn eine tote Ausführung gefunden wird, wird die Task
konsultiert, um zu sehen, was getan werden sollte. Eine tote RecurringTask
wird normalerweise auf now()
umgeplant.
Während der db-scheduler ursprünglich auf Anwendungsfälle mit niedrigem bis mittlerem Durchsatz ausgerichtet war, bewältigt er Anwendungsfälle mit hohem Durchsatz (1000+ Ausführungen/Sekunde) recht gut, da sein Datenmodell sehr einfach ist und aus folgenden Elementen besteht: eine einzige Tabelle mit Ausführungen. Um die Leistung zu verstehen, ist es hilfreich, die SQL-Anweisungen zu berücksichtigen, die pro Ausführungsstapel ausgeführt werden.
Die ursprüngliche und standardmäßige Polling-Strategie fetch-and-lock-on-execute
bewirkt Folgendes:
select
einen Stapel fälliger Ausführungen auspicked=true
zu update
. Kann aufgrund konkurrierender Terminplaner fehlen.update
oder delete
den Datensatz nach Abschluss der Ausführung je nach Handler.Insgesamt pro Batch: 1 Auswahl, 2 * Batch-Größenaktualisierungen (ohne Fehler)
In v10 wurde eine neue Polling-Strategie ( lock-and-fetch
) hinzugefügt. Es nutzt die Tatsache aus, dass die meisten Datenbanken mittlerweile SKIP LOCKED
in SELECT FOR UPDATE
-Anweisungen unterstützen (siehe 2ndquadrant-Blog). Mit einer solchen Strategie ist es möglich, vorab gesperrte Ausführungen abzurufen und so eine Anweisung weniger zu erhalten:
select for update .. skip locked
. Diese werden bereits von der Scheduler-Instanz ausgewählt.update
oder delete
den Datensatz entsprechend den Handlern.Insgesamt pro Batch: 1 Select-and-Update, 1 * Batch-Size-Updates (keine Fehler)
Um eine Vorstellung davon zu bekommen, was Sie von db-scheduler erwarten können, sehen Sie sich unten die Ergebnisse der in GCP durchgeführten Tests an. Die Tests wurden mit einigen unterschiedlichen Konfigurationen durchgeführt, wobei jedoch jeweils vier konkurrierende Scheduler-Instanzen verwendet wurden, die auf separaten VMs ausgeführt wurden. TPS ist die ca. Transaktionen pro Sekunde, wie in GCP angezeigt.
Durchsatzabruf (ex/s) | TPS-Abruf (Schätzungen) | Durchsatz Lock-and-Fetch (ex/s) | TPS-Lock-and-Fetch (Schätzungen) | |
---|---|---|---|---|
Postgres 4-Core 25 GB RAM, 4xVMs (2-Core) | ||||
20 Fäden, unten 4,0, oben 20,0 | 2000 | 9000 | 10600 | 11500 |
100 Fäden, unten 2,0, oben 6,0 | 2560 | 11000 | 11200 | 11200 |
Postgres 8-Core 50 GB RAM, 4xVMs (4-Core) | ||||
50 Fäden, unten: 0,5, oben: 4,0 | 4000 | 22000 | 11840 | 10300 |
Beobachtungen zu diesen Tests:
fetch-and-lock-on-execute
lock-and-fetch
Derzeit ist die Polling-Strategie lock-and-fetch
nur für Postgres implementiert. Beiträge zur Unterstützung weiterer Datenbanken sind willkommen.
Es gibt eine Reihe von Benutzern, die db-scheduler für Anwendungsfälle mit hohem Durchsatz verwenden. Siehe zum Beispiel:
Es gibt keine Garantie dafür, dass alle Zeitpunkte in einem Zeitplan für eine RecurringTask
ausgeführt werden. Der Schedule
wird nach Abschluss der vorherigen Aufgabenausführung konsultiert und der nächstgelegene Zeitpunkt in der Zukunft wird als nächster Ausführungszeitpunkt ausgewählt. Möglicherweise wird in Zukunft ein neuer Aufgabentyp hinzugefügt, um diese Funktionalität bereitzustellen.
Die Methoden auf SchedulerClient
( schedule
, cancel
, reschedule
) werden mit einer neuen Connection
aus der bereitgestellten DataSource
ausgeführt. Damit die Aktion Teil einer Transaktion ist, muss sie von der bereitgestellten DataSource
erledigt werden, beispielsweise mit etwas wie TransactionAwareDataSourceProxy
von Spring.
Derzeit hängt die Genauigkeit von db-scheduler vom pollingInterval
(Standard 10 Sekunden) ab, das angibt, wie oft in der Tabelle nach fälligen Ausführungen gesucht werden soll. Wenn Sie wissen, was Sie tun, kann der Scheduler zur Laufzeit über scheduler.triggerCheckForDueExecutions()
angewiesen werden, „frühzeitig zu schauen“. (Siehe auch enableImmediateExecution()
im Builder
)
Versionshinweise finden Sie in den Veröffentlichungen.
Upgrade auf 15.x
priority
und der Index priority_execution_time_idx
hinzugefügt werden. Siehe Tabellendefinitionen für Postgresql, Oracle oder MySQL. Irgendwann wird diese Spalte obligatorisch sein. Dies wird in zukünftigen Versions-/Upgrade-Hinweisen klargestellt.Upgrade auf 8.x
boolean isDeterministic()
implementieren, um anzugeben, ob sie immer die gleichen Zeitpunkte erzeugen oder nicht.Upgrade auf 4.x
consecutive_failures
zum Datenbankschema hinzu. Siehe Tabellendefinitionen für Postgresql, Oracle oder MySQL. null
wird als 0 behandelt, daher ist es nicht erforderlich, vorhandene Datensätze zu aktualisieren.Upgrade auf 3.x
Tasks
Upgrade auf 2.x
task_data
zum Datenbankschema hinzu. Siehe Tabellendefinitionen für Postgresql, Oracle oder MySQL. Voraussetzungen
Befolgen Sie diese Schritte:
Klonen Sie das Repository.
git clone https://github.com/kagkarlsson/db-scheduler
cd db-scheduler
Mit Maven erstellen (Tests durch Hinzufügen von -DskipTests=true
überspringen)
mvn package
Empfohlene Spezifikation
Bei einigen Benutzern kam es bei der Ausführung auf Single-Core-VMs zu zeitweiligen Testfehlern. Daher wird empfohlen, mindestens Folgendes zu verwenden:
db-scheduler
wenn es Quartz
gibt? Das Ziel von db-scheduler
besteht darin, nicht-invasiv und einfach zu verwenden zu sein, aber dennoch das Persistenzproblem und das Clusterkoordinationsproblem zu lösen. Es war ursprünglich auf Anwendungen mit bescheidenen Datenbankschemata ausgerichtet, bei denen das Hinzufügen von 11 Tabellen etwas übertrieben wirken würde. Update: Außerdem scheint Quartz zum jetzigen Zeitpunkt (2024) ebenfalls nicht aktiv gepflegt zu werden.
KUSS. Dies ist die häufigste Art von Shared-State-Anwendungen.
Bitte erstellen Sie ein Problem mit der Funktionsanfrage und wir können es dort besprechen. Wenn Sie ungeduldig sind (oder Lust haben, einen Beitrag zu leisten), sind Pull-Anfragen herzlich willkommen :)
Ja. Es ist bei zahlreichen Unternehmen in der Produktion im Einsatz und läuft bisher einwandfrei.