Programador de tareas para Java que se inspiró en la necesidad de un java.util.concurrent.ScheduledExecutorService
agrupado más simple que Quartz.
Como tal, también apreciado por los usuarios (cbarbosa2, rafaelhofmann, BukhariH):
¡Tu libertad es genial! ¡Me alegro mucho de haberme deshecho de Quartz y haberlo reemplazado por el tuyo, que es mucho más fácil de manejar!
cbarbosa2
Vea también ¿por qué no Quartz?
< dependency >
< groupId >com.github.kagkarlsson</ groupId >
< artifactId >db-scheduler</ artifactId >
< version >15.0.0</ version >
</ dependency >
Cree la tabla scheduled_tasks
en su esquema de base de datos. Consulte la definición de la tabla para postgresql, oracle, mssql o mysql.
Cree una instancia e inicie el programador, que luego iniciará las tareas recurrentes definidas.
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 ();
Para más ejemplos, continúa leyendo. Para obtener detalles sobre el funcionamiento interno, consulte Cómo funciona. Si tiene una aplicación Spring Boot, consulte Uso de Spring Boot.
Lista de organizaciones que se sabe que ejecutan db-scheduler en producción:
Compañía | Descripción |
---|---|
Digipost | Proveedor de buzones de correo digitales en Noruega |
Grupo Vy | Uno de los mayores grupos de transporte de los países nórdicos. |
Inteligente | Una forma económica y rápida de enviar dinero al extranjero. |
Educación profesional de Becker | |
Monitoria | Servicio de seguimiento de sitios web. |
cargador | Pruebas de carga para aplicaciones web. |
Statens vegvesen | La Administración de Carreteras Públicas de Noruega |
Año luz | Una forma sencilla y accesible de invertir su dinero a nivel mundial. |
NAV | La Administración Noruega de Trabajo y Bienestar |
Bucle moderno | Escale según las necesidades de contratación de su empresa utilizando ModernLoop para aumentar la eficiencia en la programación, comunicación y coordinación de entrevistas. |
Diffia | Empresa noruega de eSalud |
Cisne | Swan ayuda a los desarrolladores a integrar fácilmente servicios bancarios en sus productos. |
TOMRA | TOMRA es una empresa multinacional noruega que diseña y fabrica máquinas expendedoras inversas para reciclaje. |
No dude en abrir un PR para agregar su organización a la lista.
Vea también ejemplos ejecutables.
Defina una tarea recurrente y programe la primera ejecución de la tarea al inicio utilizando el método de creación startTasks
. Al finalizar, la tarea se reprogramará de acuerdo con el cronograma definido (consulte los tipos de cronograma predefinidos).
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 ();
Para tareas recurrentes con múltiples instancias y programaciones, consulte el ejemplo RecurringTaskWithPersistentScheduleMain.java.
Una instancia de una tarea única tiene un tiempo de ejecución único en algún momento en el futuro (es decir, no recurrente). El ID de instancia debe ser único dentro de esta tarea y puede usarse para codificar algunos metadatos (por ejemplo, un ID). Para estados más complejos, se admiten objetos java serializables personalizados (como se usa en el ejemplo).
Defina una tarea única e inicie el programador:
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 ();
... y luego, en algún momento (en tiempo de ejecución), se programa una ejecución utilizando 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 )));
Ejemplo | Descripción |
---|---|
Habilitar ejecución inmediata principal.java | Al programar ejecuciones para que se ejecuten now() o antes, el Scheduler local recibirá una pista sobre esto y se "despertará" para verificar si hay nuevas ejecuciones antes de lo normal (según lo configurado por pollingInterval . |
MaxRetriesMain.java | Cómo establecer un límite en la cantidad de reintentos que puede tener una ejecución. |
ExponencialBackoffMain.java | Cómo utilizar el retroceso exponencial como estrategia de reintento en lugar del retraso fijo como es predeterminado. |
ExponentialBackoffWithMaxRetriesMain.java | Cómo utilizar el retroceso exponencial como estrategia de reintento y un límite estricto en el número máximo de reintentos. |
TrackingProgressRecurringTaskMain.java | Los trabajos recurrentes pueden almacenar task_data como una forma de conservar el estado entre ejecuciones. Este ejemplo muestra cómo. |
DesoveOtras tareasMain.java | Demuestra instancias de programación de tareas de otra mediante el uso executionContext.getSchedulerClient() . |
ProgramadorClienteMain.java | Demuestra algunas de las capacidades de SchedulerClient . Programar, recuperar ejecuciones programadas, etc. |
Tarea recurrente con programación persistenteMain.java | Trabajos recurrentes de instancias múltiples donde la Schedule se almacena como parte de task_data . Por ejemplo, es adecuado para aplicaciones multiinquilino donde cada inquilino debe tener una tarea recurrente. |
StatefulRecurringTaskWithPersistentScheduleMain.java | |
JsonSerializerMain.java | Anula la serialización de task_data de la serialización de Java (predeterminada) a JSON. |
Encadenamiento de trabajosUsingTaskDataMain.java | Encadenamiento de trabajos, es decir, "cuando esta instancia termine de ejecutarse, programe otra tarea". |
Encadenamiento de trabajosUsando tareas separadasMain.java | Encadenamiento de trabajos, como arriba. |
InterceptorMain.java | Usar ExecutionInterceptor para inyectar lógica antes y después de la ejecución para todos ExecutionHandler . |
Ejemplo | Descripción |
---|---|
Ejemplos básicos | Una tarea básica única y una tarea recurrente |
Trabajo transaccional por etapas | Ejemplo de preparación transaccional de un trabajo, es decir, asegurarse de que el trabajo en segundo plano se ejecute si la transacción se confirma (junto con otras modificaciones de la base de datos). |
Trabajo de larga duración | Los trabajos de larga duración deben sobrevivir a los reinicios de la aplicación y evitar reiniciarse desde el principio. Este ejemplo demuestra cómo persistir el progreso durante el apagado y, además, una técnica para limitar la ejecución del trabajo todas las noches. |
Seguimiento de estado recurrente | Una tarea recurrente con estado que se puede modificar después de cada ejecución. |
Generador de trabajos paralelos | Demuestra cómo utilizar un trabajo recurrente para generar trabajos únicos, por ejemplo, para paralelización. |
Encadenamiento de trabajos | Un trabajo único con múltiples pasos . El siguiente paso está programado después de que se complete el anterior. |
Multiinstancia recurrente | Demuestra cómo lograr múltiples trabajos recurrentes del mismo tipo, pero con horarios y datos potencialmente diferentes. |
El planificador se crea utilizando el constructor Scheduler.create(...)
. El constructor tiene valores predeterminados razonables, pero las siguientes opciones son configurables.
.threads(int)
Número de hilos. Predeterminado 10
.
.pollingInterval(Duration)
Con qué frecuencia el programador verifica la base de datos para ver si hay ejecuciones debidas. 10s
por defecto.
.alwaysPersistTimestampInUTC()
El Programador supone que las columnas para marcas de tiempo persistentes persisten Instant
s, no LocalDateTime
s, es decir, de alguna manera vinculan la marca de tiempo a una zona. Sin embargo, algunas bases de datos tienen soporte limitado para este tipo de tipos (que no tienen información de zona) u otras peculiaridades, lo que hace que "almacenar siempre en UTC" sea una mejor alternativa. En tales casos, utilice esta configuración para almacenar siempre los instantáneos en UTC. Los esquemas de PostgreSQL y Oracle se prueban para preservar la información de zona. MySQL y MariaDB -schemas no usan ni deben usar esta configuración. NB: Para compatibilidad con versiones anteriores, el comportamiento predeterminado para bases de datos "desconocidas" es asumir que la base de datos conserva la zona horaria. Para bases de datos "conocidas", consulte la clase AutodetectJdbcCustomization
.
.enableImmediateExecution()
Si esto está habilitado, el programador intentará indicarle al Scheduler
local que hay ejecuciones que deben ejecutarse después de que estén programadas para ejecutarse now()
o en un momento anterior. NB: Si la llamada a schedule(..)
/ reschedule(..)
ocurre desde dentro de una transacción, el programador podría intentar ejecutarla antes de que la actualización sea visible (la transacción no se ha confirmado). Sin embargo, todavía persiste, por lo que incluso si falla, se ejecutará antes del siguiente polling-interval
. También puede activar mediante programación una verificación anticipada de las ejecuciones vencidas utilizando el método Scheduler ( scheduler.triggerCheckForDueExecutions()
). Por defecto false
.
.registerShutdownHook()
Registra un gancho de apagado que llamará Scheduler.stop()
al apagar. Siempre se debe solicitar la detención para un cierre elegante y para evitar ejecuciones muertas.
.shutdownMaxWait(Duration)
Cuánto tiempo esperará el programador antes de interrumpir los subprocesos del servicio ejecutor. Si se encuentra usando esto, considere si es posible verificar periódicamente executionContext.getSchedulerState().isShuttingDown()
en ExecutionHandler y cancelar la tarea de larga duración. Predeterminado 30min
.
.enablePriority()
Es posible definir una prioridad para las ejecuciones que determina el orden en que las ejecuciones debidas se recuperan de la base de datos. Una ejecución con un valor de prioridad más alto se ejecutará antes que una ejecución con un valor más bajo (técnicamente, el orden será order by priority desc, execution_time asc
). Considere usar prioridades en el rango 0-32000 ya que el campo está definido como SMALLINT
. Si necesita un valor mayor, modifique el esquema. Por ahora, esta característica es voluntaria y priority
de columna solo la necesitan los usuarios que eligen habilitar la prioridad a través de esta configuración.
Establezca la prioridad por instancia utilizando TaskInstance.Builder
:
scheduler . schedule (
MY_TASK
. instance ( "1" )
. priority ( 100 )
. scheduledTo ( Instant . now ()));
Nota:
(execution_time asc, priority desc)
(reemplazando el execution_time asc
).null
de prioridad puede interpretarse de forma diferente según la base de datos (baja o alta). Si está ejecutando >1000 ejecuciones/s, es posible que desee utilizar la estrategia de sondeo lock-and-fetch
para una menor sobrecarga y un mayor rendimiento (leer más). De lo contrario, la fetch-and-lock-on-execute
estará bien.
.pollUsingFetchAndLockOnExecute(double, double)
Utilice la estrategia de sondeo predeterminada fetch-and-lock-on-execute
.
Si la última recuperación de la base de datos fue un lote completo ( executionsPerBatchFractionOfThreads
), se activará una nueva recuperación cuando el número de ejecuciones restantes sea menor o igual a lowerLimitFractionOfThreads * nr-of-threads
. Las ejecuciones recuperadas no están bloqueadas/seleccionadas, por lo que el programador competirá con otras instancias por el bloqueo cuando se ejecute. Soportado por todas las bases de datos.
Valores predeterminados: 0,5, 3.0
.pollUsingLockAndFetch(double, double)
Utilice la estrategia de sondeo lock-and-fetch
que utiliza select for update .. skip locked
para reducir los gastos generales.
Si la última recuperación de la base de datos fue un lote completo, se activará una nueva recuperación cuando el número de ejecuciones restantes sea menor o igual a lowerLimitFractionOfThreads * nr-of-threads
. El número de ejecuciones obtenidas cada vez es igual a (upperLimitFractionOfThreads * nr-of-threads) - nr-executions-left
. Las ejecuciones recuperadas ya están bloqueadas/seleccionadas para esta instancia del programador, lo que ahorra una declaración UPDATE
.
Para uso normal, configúrelo en, por ejemplo, 0.5, 1.0
.
Para un alto rendimiento (es decir, mantener los subprocesos ocupados), configúrelo, por ejemplo, en 1.0, 4.0
. Actualmente, los latidos no se actualizan para las ejecuciones seleccionadas en la cola (aplicable si upperLimitFractionOfThreads > 1.0
). Si permanecen allí durante más de 4 * heartbeat-interval
(predeterminado 20m
), sin iniciar la ejecución, se detectarán como muertos y probablemente se desbloquearán nuevamente (determinado por DeadExecutionHandler
). Actualmente soportado por postgres . sql-server también admite esto, pero las pruebas han demostrado que esto es propenso a interbloqueos y, por lo tanto, no se recomienda hasta que se comprenda o se resuelva.
.heartbeatInterval(Duration)
Con qué frecuencia actualizar la marca de tiempo de latido para ejecutar ejecuciones. Por defecto 5m
.
.missedHeartbeatsLimit(int)
Cuántos latidos pueden faltar antes de que la ejecución se considere muerta. Predeterminado 6
.
.addExecutionInterceptor(ExecutionInterceptor)
Agrega un ExecutionInterceptor
que puede inyectar lógica en torno a las ejecuciones. Para Spring Boot, simplemente registre un Bean de tipo ExecutionInterceptor
.
.addSchedulerListener(SchedulerListener)
Agrega un SchedulerListener
que recibirá eventos relacionados con el Programador y la Ejecución. Para Spring Boot, simplemente registre un Bean de tipo SchedulerListener
.
.schedulerName(SchedulerName)
Nombre de esta instancia del planificador. El nombre se almacena en la base de datos cuando un programador selecciona una ejecución. <hostname>
predeterminado.
.tableName(String)
Nombre de la tabla utilizada para realizar un seguimiento de las ejecuciones de tareas. Cambie el nombre en las definiciones de la tabla en consecuencia al crear la tabla. scheduled_tasks
predeterminadas.
.serializer(Serializer)
Implementación del serializador que se utilizará al serializar datos de tareas. De forma predeterminada se utiliza la serialización Java estándar, pero db-scheduler también incluye GsonSerializer
y JacksonSerializer
. Vea ejemplos de un KotlinSerializer. Consulte también la documentación adicional en Serializadores.
.executorService(ExecutorService)
Si se especifica, utilice este servicio ejecutor administrado externamente para ejecutar ejecuciones. Idealmente, aún se debe proporcionar la cantidad de subprocesos que utilizará (para optimizaciones de sondeo del programador). null
predeterminado.
.deleteUnresolvedAfter(Duration)
El tiempo después del cual se eliminan automáticamente las ejecuciones con tareas desconocidas. Por lo general, pueden ser tareas recurrentes antiguas que ya no se utilizan. Esto es distinto de cero para evitar la eliminación accidental de tareas debido a un error de configuración (faltan tareas conocidas) y problemas durante las actualizaciones continuas. Predeterminado 14d
.
.jdbcCustomization(JdbcCustomization)
db-scheduler intenta detectar automáticamente la base de datos utilizada para ver si es necesario personalizar alguna interacción jdbc. Este método es una trampilla de escape para permitir configurar JdbcCustomizations
explícitamente. Detección automática predeterminada.
.commitWhenAutocommitDisabled(boolean)
De forma predeterminada, no se emite ninguna confirmación en DataSource Connections. Si la confirmación automática está deshabilitada, se supone que las transacciones las maneja un administrador de transacciones externo. Establezca esta propiedad en true
para anular este comportamiento y hacer que el Programador siempre emita confirmaciones. Por defecto false
.
.failureLogging(Level, boolean)
Configura cómo registrar errores de tareas, es decir, mensajes Throwable
lanzados desde un controlador de ejecución de tareas. Utilice el nivel de registro OFF
para desactivar completamente este tipo de registro. Predeterminado WARN, true
.
Las tareas se crean utilizando una de las clases de creación en Tasks
. Los constructores tienen valores predeterminados sensatos, pero las siguientes opciones se pueden anular.
Opción | Por defecto | Descripción |
---|---|---|
.onFailure(FailureHandler) | ver descripción | Qué hacer cuando un ExecutionHandler genera una excepción. De forma predeterminada, las tareas recurrentes se reprograman de acuerdo con su Schedule . Las tareas únicas se vuelven a intentar en 5 minutos. |
.onDeadExecution(DeadExecutionHandler) | ReviveDeadExecution | Qué hacer cuando se detecta una ejecución inactiva , es decir, una ejecución con una marca de tiempo de latido obsoleto. De forma predeterminada, las ejecuciones inactivas se reprograman para now() . |
.initialData(T initialData) | null | Los datos que se utilizarán la primera vez que se programe una tarea recurrente . |
La biblioteca contiene una serie de implementaciones de programación para tareas recurrentes. Ver Schedules
de clases.
Cronograma | Descripción |
---|---|
.daily(LocalTime ...) | Se ejecuta todos los días en horarios específicos. Opcionalmente se puede especificar una zona horaria. |
.fixedDelay(Duration) | El siguiente tiempo de ejecución es Duration después de la última ejecución completa. Nota: Este Schedule programa la ejecución inicial en Instant.now() cuando se usa en startTasks(...) |
.cron(String) | Expresión cron estilo primavera (v5.3+). El - se interpreta como un horario deshabilitado. |
Otra opción para configurar horarios es leer patrones de cadenas con Schedules.parse(String)
.
Los patrones disponibles actualmente son:
Patrón | Descripción |
---|---|
FIXED_DELAY|Ns | Igual que .fixedDelay(Duration) con una duración establecida en N segundos. |
DAILY|12:30,15:30...(|time_zone) | Igual que .daily(LocalTime) con zona horaria opcional (por ejemplo, Europa/Roma, UTC) |
- | Horario discapacitado |
Puede encontrar más detalles sobre los formatos de zona horaria aquí.
Un Schedule
se puede marcar como deshabilitado. El programador no programará las ejecuciones iniciales de las tareas con una programación deshabilitada y eliminará cualquier ejecución existente para esa tarea.
Una instancia de tarea puede tener algunos datos asociados en el campo task_data
. El programador utiliza un Serializer
para leer y escribir estos datos en la base de datos. De forma predeterminada, se utiliza la serialización Java estándar, pero se proporcionan varias opciones:
GsonSerializer
JacksonSerializer
Para la serialización de Java, se recomienda especificar un serialVersionUID
para poder evolucionar la clase que representa los datos. Si no se especifica y la clase cambia, es probable que la deserialización falle con una InvalidClassException
. Si esto sucede, busque y configure explícitamente el serialVersionUID
actual generado automáticamente. Entonces será posible realizar cambios continuos en la clase.
Si necesita migrar de la serialización de Java a GsonSerializer
, configure el programador para usar SerializerWithFallbackDeserializers
:
. serializer ( new SerializerWithFallbackDeserializers ( new GsonSerializer (), new JavaSerializer ()))
Para las aplicaciones Spring Boot, existe un iniciador db-scheduler-spring-boot-starter
que hace que el cableado del programador sea muy simple. (Ver proyecto de ejemplo completo).
DataSource
funcional con el esquema inicializado. (En el ejemplo se utiliza HSQLDB y el esquema se aplica automáticamente).< dependency >
< groupId >com.github.kagkarlsson</ groupId >
< artifactId >db-scheduler-spring-boot-starter</ artifactId >
< version >15.0.0</ version >
</ dependency >
Task
como Spring beans. Si son recurrentes, se recogerán e iniciarán automáticamente.Scheduler
en la información de estado del actuador, debe habilitar el indicador de estado db-scheduler
. Información de salud de primavera. La configuración se realiza principalmente a través de application.properties
. La configuración del nombre del programador, el serializador y el servicio ejecutor se realiza agregando un bean de tipo DbSchedulerCustomizer
a su contexto 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
Es posible utilizar el Scheduler
para interactuar con las ejecuciones futuras persistentes. Para situaciones en las que no se necesita una instancia completa Scheduler
, se puede crear un SchedulerClient más simple utilizando su constructor:
SchedulerClient . Builder . create ( dataSource , taskDefinitions ). build ()
Permitirá operaciones como:
Se utiliza una única tabla de base de datos para realizar un seguimiento de futuras ejecuciones de tareas. Cuando vence la ejecución de una tarea, db-scheduler la selecciona y la ejecuta. Cuando finaliza la ejecución, se consulta la Task
para ver qué se debe hacer. Por ejemplo, una RecurringTask
normalmente se reprograma en el futuro según su Schedule
.
El programador utiliza bloqueo optimista o selección para actualización (según la estrategia de sondeo) para garantizar que una y solo una instancia del programador pueda seleccionar y ejecutar una ejecución de tarea.
El término tarea recurrente se utiliza para tareas que deben ejecutarse periódicamente, según un cronograma.
Cuando finaliza la ejecución de una tarea recurrente, se consulta un Schedule
para determinar cuál debe ser la próxima hora de ejecución y se crea una ejecución de tarea futura para ese momento (es decir, se reprograma ). La hora elegida será la más cercana según el Schedule
, pero aún en el futuro.
Hay dos tipos de tareas recurrentes, la tarea recurrente estática normal, donde la Schedule
se define estáticamente en el código, y las tareas recurrentes dinámicas , donde la Schedule
se define en tiempo de ejecución y persiste en la base de datos (aún requiere solo una tabla). .
La tarea recurrente estática es la más común y adecuada para trabajos en segundo plano regulares, ya que el programador programa automáticamente una instancia de la tarea si no está presente y también actualiza el siguiente tiempo de ejecución si se actualiza el Schedule
.
Para crear la ejecución inicial para una tarea recurrente estática, el programador tiene un método startTasks(...)
que toma una lista de tareas que deben "iniciarse" si aún no tienen una ejecución existente. El tiempo de ejecución inicial está determinado por el Schedule
. Si la tarea ya tiene una ejecución futura (es decir, se ha iniciado al menos una vez antes), pero una Schedule
actualizada ahora indica otro tiempo de ejecución, la ejecución existente se reprogramará al nuevo tiempo de ejecución (con la excepción de no determinista) . programaciones como FixedDelay
donde el nuevo tiempo de ejecución está más lejano en el futuro).
Crear usando Tasks.recurring(..)
.
La tarea dinámica recurrente es una adición posterior a db-scheduler y se agregó para admitir casos de uso en los que se necesitan múltiples instancias del mismo tipo de tarea (es decir, la misma implementación) con diferentes cronogramas. El Schedule
persiste en task_data
junto con cualquier dato normal. A diferencia de la tarea recurrente estática , la dinámica no programará automáticamente instancias de la tarea. Depende del usuario crear instancias y actualizar la programación de las existentes si es necesario (utilizando la interfaz SchedulerClient
). Consulte el ejemplo RecurringTaskWithPersistentScheduleMain.java para obtener más detalles.
Cree usando Tasks.recurringWithPersistentSchedule(..)
.
El término tarea única se utiliza para tareas que tienen un único tiempo de ejecución. Además de codificar datos en el instanceId
de una ejecución de tarea, es posible almacenar datos binarios arbitrarios en un campo separado para usarlos en el momento de la ejecución. De forma predeterminada, la serialización de Java se utiliza para ordenar/desordenar los datos.
Cree usando Tasks.oneTime(..)
.
Para las tareas que no se ajustan a las categorías anteriores, es posible personalizar completamente el comportamiento de las tareas usando Tasks.custom(..)
.
Los casos de uso podrían ser:
Durante la ejecución, el programador actualiza periódicamente el tiempo de latido para la ejecución de la tarea. Si una ejecución está marcada como en ejecución, pero no recibe actualizaciones del tiempo de latido, se considerará una ejecución inactiva después del tiempo X. Esto puede suceder, por ejemplo, si la JVM que ejecuta el programador sale repentinamente.
Cuando se encuentra una ejecución inactiva, se consulta la Task
para ver qué se debe hacer. Una RecurringTask
inactiva normalmente se reprograma para now()
.
Si bien db-scheduler inicialmente estaba dirigido a casos de uso de rendimiento bajo a medio, maneja bastante bien casos de uso de alto rendimiento (más de 1000 ejecuciones/segundo) debido al hecho de que su modelo de datos es muy simple y consiste en una única tabla de ejecuciones. Para comprender cómo funcionará, resulta útil considerar las declaraciones SQL que ejecuta por lote de ejecuciones.
La estrategia de sondeo original y predeterminada, fetch-and-lock-on-execute
, hará lo siguiente:
select
un lote de ejecuciones debidasupdate
la ejecución a picked=true
para esta instancia del programador. Puede perderse debido a la competencia de programadores.update
o delete
el registro según los controladores.En suma por lote: 1 selección, 2 * actualizaciones de tamaño de lote (excluidas las faltas)
En v10, se agregó una nueva estrategia de sondeo ( lock-and-fetch
). Aprovecha el hecho de que la mayoría de las bases de datos ahora admiten SKIP LOCKED
en las declaraciones SELECT FOR UPDATE
(consulte el blog del segundo cuadrante). Usando esta estrategia, es posible recuperar ejecuciones prebloqueadas y, por lo tanto, obtener una declaración menos:
select for update .. skip locked
un lote de ejecuciones pendientes. Estos ya serán seleccionados por la instancia del programador.update
o delete
el registro según los controladores.En total por lote: 1 selección y actualización, 1 * actualizaciones del tamaño del lote (sin errores)
Para tener una idea de qué esperar de db-scheduler, consulte los resultados de las pruebas ejecutadas en GCP a continuación. Las pruebas se ejecutaron con algunas configuraciones diferentes, pero cada una utilizó 4 instancias de programador en competencia que se ejecutan en máquinas virtuales separadas. TPS es el aprox. transacciones por segundo como se muestra en GCP.
Recuperación de rendimiento (ex/s) | Recuperación de TPS (estimaciones) | Bloqueo y recuperación de rendimiento (ex/s) | Bloqueo y recuperación de TPS (estimaciones) | |
---|---|---|---|---|
Postgres 4 núcleos 25 GB de RAM, 4xVM (2 núcleos) | ||||
20 hilos, inferior 4.0, superior 20.0 | 2000 | 9000 | 10600 | 11500 |
100 hilos, inferior 2.0, superior 6.0 | 2560 | 11000 | 11200 | 11200 |
Postgres 8 núcleos 50 GB de RAM, 4xVM (4 núcleos) | ||||
50 hilos, inferior: 0,5, superior: 4,0 | 4000 | 22000 | 11840 | 10300 |
Observaciones para estas pruebas:
fetch-and-lock-on-execute
lock-and-fetch
Actualmente, la estrategia de sondeo lock-and-fetch
se implementa solo para Postgres. Se aceptan contribuciones que agreguen soporte para más bases de datos.
Hay varios usuarios que utilizan db-scheduler para casos de uso de alto rendimiento. Ver por ejemplo:
No hay garantías de que se ejecuten todos los instantes de una programación para una RecurringTask
. El Schedule
se consulta después de que finaliza la ejecución de la tarea anterior y se seleccionará el momento más cercano en el futuro para el siguiente tiempo de ejecución. Es posible que en el futuro se agregue un nuevo tipo de tarea para proporcionar dicha funcionalidad.
Los métodos en SchedulerClient
( schedule
, cancel
, reschedule
) se ejecutarán utilizando una nueva Connection
desde el DataSource
proporcionado. Para que la acción sea parte de una transacción, la DataSource
proporcionada debe encargarse de ella, por ejemplo, usando algo como TransactionAwareDataSourceProxy
de Spring.
Actualmente, la precisión de db-scheduler depende del pollingInterval
(10 predeterminado) que especifica con qué frecuencia buscar en la tabla las ejecuciones debidas. Si sabe lo que está haciendo, es posible que se le indique al programador en tiempo de ejecución que "busque temprano" a través de scheduler.triggerCheckForDueExecutions()
. (Ver también enableImmediateExecution()
en el Builder
)
Consulte las versiones para ver las notas de la versión.
Actualización a 15.x
priority
de columna y el índice priority_execution_time_idx
al esquema de la base de datos. Consulte las definiciones de las tablas para postgresql, oracle o mysql. En algún momento, esta columna será obligatoria. Esto quedará claro en futuras notas de versión/actualización.Actualización a 8.x
boolean isDeterministic()
para indicar si siempre producirán los mismos instantes o no.Actualización a 4.x
consecutive_failures
al esquema de la base de datos. Consulte las definiciones de las tablas para postgresql, oracle o mysql. null
se maneja como 0, por lo que no es necesario actualizar los registros existentes.Actualizando a 3.x
Tasks
.Actualizando a 2.x
task_data
al esquema de la base de datos. Consulte las definiciones de las tablas para postgresql, oracle o mysql. Requisitos previos
Siga estos pasos:
Clona el repositorio.
git clone https://github.com/kagkarlsson/db-scheduler
cd db-scheduler
Compile con Maven (omita las pruebas agregando -DskipTests=true
)
mvn package
Especificaciones recomendadas
Algunos usuarios han experimentado fallas de prueba intermitentes al ejecutar máquinas virtuales de un solo núcleo. Por ello, se recomienda utilizar un mínimo de:
db-scheduler
cuando existe Quartz
? El objetivo de db-scheduler
es no ser invasivo y fácil de usar, pero aun así resolver el problema de persistencia y el problema de coordinación del clúster. Originalmente estaba dirigido a aplicaciones con esquemas de bases de datos modestos, a las que agregar 11 tablas sería un poco excesivo. Actualización: Además, a partir de ahora (2024), Quartz tampoco parece recibir mantenimiento activo.
BESO. Es el tipo más común de estado compartido que tienen las aplicaciones.
Cree un problema con la solicitud de función y podremos discutirlo allí. Si está impaciente (o tiene ganas de contribuir), las solicitudes de extracción son bienvenidas :)
Sí. Se utiliza en la producción de varias empresas y hasta ahora ha funcionado sin problemas.