Agendador de tarefas para Java que foi inspirado na necessidade de um java.util.concurrent.ScheduledExecutorService
em cluster mais simples que o Quartz.
Como tal, também apreciado pelos utilizadores (cbarbosa2, rafaelhofmann, BukhariH):
Sua liberdade é demais! Estou tão feliz por ter me livrado do Quartz e substituído pelo seu, que é muito mais fácil de manusear!
cbarbosa2
Veja também por que não o Quartzo?
< dependency >
< groupId >com.github.kagkarlsson</ groupId >
< artifactId >db-scheduler</ artifactId >
< version >15.0.0</ version >
</ dependency >
Crie a tabela scheduled_tasks
em seu esquema de banco de dados. Veja a definição da tabela para postgresql, oracle, mssql ou mysql.
Instancie e inicie o agendador, que iniciará quaisquer tarefas recorrentes 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 mais exemplos, continue lendo. Para obter detalhes sobre o funcionamento interno, consulte Como funciona. Se você tiver um aplicativo Spring Boot, dê uma olhada em Spring Boot Usage.
Lista de organizações conhecidas por executar o db-scheduler em produção:
Empresa | Descrição |
---|---|
Digipost | Fornecedor de caixas de correio digitais na Noruega |
Grupo Vy | Um dos maiores grupos de transporte dos países nórdicos. |
Sábio | Uma maneira barata e rápida de enviar dinheiro para o exterior. |
Becker Educação Profissional | |
Monitoria | Serviço de monitoramento de sites. |
Carregador | Teste de carga para aplicações web. |
Estado Vegvesen | A Administração Norueguesa de Estradas Públicas |
Ano-luz | Uma maneira simples e acessível de investir seu dinheiro globalmente. |
NAV | A Administração Norueguesa do Trabalho e Bem-Estar |
Loop moderno | Dimensione de acordo com as necessidades de contratação da sua empresa usando ModernLoop para aumentar a eficiência no agendamento de entrevistas, comunicação e coordenação. |
Difia | Empresa norueguesa de eHealth |
Cisne | Swan ajuda os desenvolvedores a incorporar facilmente serviços bancários em seus produtos. |
TOMRA | A TOMRA é uma empresa multinacional norueguesa que projeta e fabrica máquinas de venda reversa para reciclagem. |
Sinta-se à vontade para abrir um PR para adicionar sua organização à lista.
Veja também exemplos executáveis.
Defina uma tarefa recorrente e agende a primeira execução da tarefa na inicialização usando o método construtor startTasks
. Após a conclusão, a tarefa será reprogramada de acordo com o cronograma definido (ver tipos de agendamento 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 tarefas recorrentes com diversas instâncias e agendamentos, consulte o exemplo RecurringTaskWithPersistentScheduleMain.java.
Uma instância de uma tarefa única tem um único tempo de execução em algum momento no futuro (ou seja, não recorrente). O ID da instância deve ser único nesta tarefa e pode ser usado para codificar alguns metadados (por exemplo, um ID). Para estados mais complexos, são suportados objetos Java serializáveis personalizados (conforme usado no exemplo).
Defina uma tarefa única e inicie o agendador:
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 ();
... e então em algum momento (em tempo de execução), uma execução é agendada usando o 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 )));
Exemplo | Descrição |
---|---|
EnableImmediateExecutionMain.java | Ao agendar execuções para serem executadas now() ou antes, o Scheduler local será avisado sobre isso e "acordará" para verificar novas execuções mais cedo do que normalmente faria (conforme configurado por pollingInterval . |
MaxRetriesMain.java | Como definir um limite para o número de tentativas que uma execução pode ter. |
ExponencialBackoffMain.java | Como usar a espera exponencial como estratégia de nova tentativa em vez do atraso fixo como padrão. |
ExponentialBackoffWithMaxRetriesMain.java | Como usar a espera exponencial como estratégia de novas tentativas e um limite rígido para o número máximo de novas tentativas. |
TrackingProgressRecurringTaskMain.java | Trabalhos recorrentes podem armazenar task_data como uma forma de persistir o estado entre as execuções. Este exemplo mostra como. |
GerandoOtherTasksMain.java | Demonstra instâncias de agendamento de tarefas de outra usando executionContext.getSchedulerClient() . |
SchedulerClientMain.java | Demonstra alguns dos recursos do SchedulerClient . Agendamento, busca de execuções agendadas, etc. |
RecurringTaskWithPersistentScheduleMain.java | Tarefas recorrentes de várias instâncias em que o Schedule é armazenado como parte de task_data . Por exemplo, adequado para aplicações multilocatários onde cada locatário deve ter uma tarefa recorrente. |
StatefulRecurringTaskWithPersistentScheduleMain.java | |
JsonSerializerMain.java | Substitui a serialização de task_data da serialização Java (padrão) para JSON. |
JobChainingUsingTaskDataMain.java | Encadeamento de tarefas, ou seja, "quando esta instância terminar de ser executada, agende outra tarefa. |
JobChainingUsingSeparateTasksMain.java | Encadeamento de tarefas, como acima. |
InterceptorMain.java | Usando ExecutionInterceptor para injetar lógica antes e depois da execução para todos os ExecutionHandler . |
Exemplo | Descrição |
---|---|
Exemplos básicos | Uma tarefa única básica e uma tarefa recorrente |
TransacionalmenteStagedJob | Exemplo de preparação transacional de um trabalho, ou seja, garantir que o trabalho em segundo plano seja executado se a transação for confirmada (junto com outras modificações do banco de dados). |
LongRunningJob | Os trabalhos de longa duração precisam sobreviver às reinicializações do aplicativo e evitar a reinicialização desde o início. Este exemplo demonstra como persistir o progresso no desligamento e, adicionalmente, uma técnica para limitar a execução do trabalho todas as noites. |
Rastreamento de estado recorrente | Uma tarefa recorrente com estado que pode ser modificado após cada execução. |
ParallelJobSpawner | Demonstra como usar um trabalho recorrente para gerar trabalhos únicos, por exemplo, para paralelização. |
Encadeamento de tarefas | Um trabalho único com várias etapas . A próxima etapa é agendada após a conclusão da anterior. |
MultiInstanceRecorrente | Demonstra como realizar vários trabalhos recorrentes do mesmo tipo, mas com agendas e dados potencialmente diferentes. |
O planejador é criado usando o construtor Scheduler.create(...)
. O construtor possui padrões razoáveis, mas as opções a seguir são configuráveis.
.threads(int)
Número de tópicos. Padrão 10
.
.pollingInterval(Duration)
Com que frequência o agendador verifica o banco de dados em busca de execuções devidas. Padrão 10s
.
.alwaysPersistTimestampInUTC()
O Agendador assume que as colunas para carimbos de data e hora persistentes persistem Instant
s, não LocalDateTime
s, ou seja, de alguma forma vinculam o carimbo de data e hora a uma zona. No entanto, alguns bancos de dados têm suporte limitado para esses tipos (que não possuem informações de zona) ou outras peculiaridades, tornando "armazenar sempre em UTC" uma alternativa melhor. Nesses casos, use esta configuração para sempre armazenar Instantes em UTC. Os esquemas PostgreSQL e Oracle são testados para preservar as informações da zona. Os esquemas MySQL e MariaDB não usam e devem usar esta configuração. NB: Para compatibilidade com versões anteriores, o comportamento padrão para bancos de dados "desconhecidos" é assumir que o banco de dados preserva o fuso horário. Para bancos de dados "conhecidos", consulte a classe AutodetectJdbcCustomization
.
.enableImmediateExecution()
Se estiver habilitado, o agendador tentará sugerir ao Scheduler
local que há execuções a serem executadas depois de programadas para serem executadas now()
, ou em um momento no passado. NB: Se a chamada para schedule(..)
/ reschedule(..)
ocorrer dentro de uma transação, o agendador pode tentar executá-la antes que a atualização seja visível (a transação não foi confirmada). Ele ainda persiste, portanto, mesmo que seja uma falha, ele será executado antes do próximo polling-interval
. Você também pode acionar programaticamente uma verificação antecipada de execuções vencidas usando o método Scheduler scheduler.triggerCheckForDueExecutions()
). Padrão false
.
.registerShutdownHook()
Registra um gancho de desligamento que chamará Scheduler.stop()
no desligamento. Stop deve sempre ser chamado para um encerramento normal e para evitar execuções mortas.
.shutdownMaxWait(Duration)
Quanto tempo o agendador esperará antes de interromper os encadeamentos do serviço executor. Se você estiver usando isso, considere se é possível verificar regularmente executionContext.getSchedulerState().isShuttingDown()
no ExecutionHandler e abortar a tarefa de longa execução. Padrão 30min
.
.enablePriority()
É possível definir uma prioridade de execuções que determina a ordem em que as execuções devidas são buscadas no banco de dados. Uma execução com um valor de prioridade mais alto será executada antes de uma execução com um valor mais baixo (tecnicamente, a ordem será order by priority desc, execution_time asc
). Considere usar prioridades no intervalo de 0 a 32.000, pois o campo é definido como SMALLINT
. Se precisar de um valor maior, modifique o esquema. Por enquanto, esse recurso é opcional e priority
da coluna só é necessária para usuários que optam por ativar a prioridade por meio desta configuração.
Defina a prioridade por instância usando TaskInstance.Builder
:
scheduler . schedule (
MY_TASK
. instance ( "1" )
. priority ( 100 )
. scheduledTo ( Instant . now ()));
Observação:
(execution_time asc, priority desc)
(substituindo o antigo execution_time asc
).null
para prioridade pode ser interpretado de forma diferente dependendo do banco de dados (baixo ou alto). Se você estiver executando >1000 execuções/s, talvez queira usar a estratégia de pesquisa lock-and-fetch
para menor sobrecarga e maior rendimento (leia mais). Caso contrário, o fetch-and-lock-on-execute
padrão funcionará bem.
.pollUsingFetchAndLockOnExecute(double, double)
Use a estratégia de pesquisa padrão fetch-and-lock-on-execute
.
Se a última busca do banco de dados foi um lote completo ( executionsPerBatchFractionOfThreads
), uma nova busca será acionada quando o número de execuções restantes for menor ou igual a lowerLimitFractionOfThreads * nr-of-threads
. As execuções buscadas não são bloqueadas/selecionadas, portanto, o agendador competirá com outras instâncias pelo bloqueio quando for executado. Suportado por todos os bancos de dados.
Padrões: 0,5, 3.0
.pollUsingLockAndFetch(double, double)
Use a estratégia de pesquisa lock-and-fetch
que usa select for update .. skip locked
para menos sobrecarga.
Se a última busca do banco de dados foi um lote completo, uma nova busca será acionada quando o número de execuções restantes for menor ou igual a lowerLimitFractionOfThreads * nr-of-threads
. O número de execuções buscadas a cada vez é igual a (upperLimitFractionOfThreads * nr-of-threads) - nr-executions-left
. As execuções buscadas já estão bloqueadas/selecionadas para esta instância do agendador, salvando assim uma instrução UPDATE
.
Para uso normal, defina como, por exemplo, 0.5, 1.0
.
Para alto rendimento (ou seja, manter threads ocupados), defina como, por exemplo, 1.0, 4.0
. Atualmente, os heartbeats não são atualizados para execuções escolhidas na fila (aplicável se upperLimitFractionOfThreads > 1.0
). Se eles permanecerem lá por mais de 4 * heartbeat-interval
(padrão 20m
), sem iniciar a execução, serão detectados como mortos e provavelmente serão desbloqueados novamente (determinado por DeadExecutionHandler
). Atualmente suportado pelo postgres . O sql-server também suporta isso, mas os testes mostraram que isso é propenso a conflitos e, portanto, não é recomendado até que seja compreendido/resolvido.
.heartbeatInterval(Duration)
Com que frequência atualizar o carimbo de data/hora da pulsação para execuções em execução. Padrão 5m
.
.missedHeartbeatsLimit(int)
Quantos batimentos cardíacos podem ser perdidos antes que a execução seja considerada morta. Padrão 6
.
.addExecutionInterceptor(ExecutionInterceptor)
Adiciona um ExecutionInterceptor
que pode injetar lógica em torno das execuções. Para Spring Boot, basta registrar um Bean do tipo ExecutionInterceptor
.
.addSchedulerListener(SchedulerListener)
Adiciona um SchedulerListener
que receberá eventos relacionados ao Scheduler e à Execução. Para Spring Boot, basta registrar um Bean do tipo SchedulerListener
.
.schedulerName(SchedulerName)
Nome desta instância do agendador. O nome é armazenado no banco de dados quando uma execução é escolhida por um agendador. Padrão <hostname>
.
.tableName(String)
Nome da tabela usada para rastrear execuções de tarefas. Altere o nome nas definições da tabela adequadamente ao criar a tabela. scheduled_tasks
padrão.
.serializer(Serializer)
Implementação do serializador a ser usada ao serializar dados de tarefas. O padrão é usar a serialização Java padrão, mas db-scheduler também agrupa um GsonSerializer
e JacksonSerializer
. Veja exemplos de um KotlinSerializer. Consulte também a documentação adicional em Serializadores.
.executorService(ExecutorService)
Se especificado, use este serviço executor gerenciado externamente para executar execuções. Idealmente, o número de threads que ele usará ainda deve ser fornecido (para otimizações de pesquisa do agendador). Padrão null
.
.deleteUnresolvedAfter(Duration)
O tempo após o qual as execuções com tarefas desconhecidas são excluídas automaticamente. Normalmente, essas tarefas podem ser antigas e recorrentes que não estão mais em uso. Isso é diferente de zero para evitar a remoção acidental de tarefas por meio de um erro de configuração (tarefas conhecidas ausentes) e problemas durante atualizações contínuas. Padrão 14d
.
.jdbcCustomization(JdbcCustomization)
O db-scheduler tenta detectar automaticamente o banco de dados usado para ver se alguma interação jdbc precisa ser personalizada. Este método é uma saída de escape para permitir a configuração explícita JdbcCustomizations
. Detecção automática padrão.
.commitWhenAutocommitDisabled(boolean)
Por padrão, nenhuma confirmação é emitida em conexões DataSource. Se a confirmação automática estiver desabilitada, presume-se que as transações sejam tratadas por um gerenciador de transações externo. Defina esta propriedade como true
para substituir esse comportamento e fazer com que o Agendador sempre emita commits. Padrão false
.
.failureLogging(Level, boolean)
Configura como registrar falhas de tarefas, ou seja, Throwable
s lançados de um manipulador de execução de tarefas. Use o nível de log OFF
para desativar completamente esse tipo de registro. WARN, true
.
As tarefas são criadas usando uma das classes construtoras em Tasks
. Os construtores têm padrões razoáveis, mas as opções a seguir podem ser substituídas.
Opção | Padrão | Descrição |
---|---|---|
.onFailure(FailureHandler) | veja desc. | O que fazer quando um ExecutionHandler lança uma exceção. Por padrão, as tarefas recorrentes são reprogramadas de acordo com seu Schedule . As tarefas únicas são repetidas novamente em 5 minutos. |
.onDeadExecution(DeadExecutionHandler) | ReviveDeadExecution | O que fazer quando uma execução morta é detectada, ou seja, uma execução com um carimbo de data/hora de pulsação obsoleto. Por padrão, as execuções mortas são reprogramadas para now() . |
.initialData(T initialData) | null | Os dados a serem usados na primeira vez que uma tarefa recorrente for agendada. |
A biblioteca contém várias implementações de cronograma para tarefas recorrentes. Consulte Schedules
das aulas.
Agendar | Descrição |
---|---|
.daily(LocalTime ...) | Funciona todos os dias em horários específicos. Opcionalmente, um fuso horário pode ser especificado. |
.fixedDelay(Duration) | O próximo tempo de execução é Duration após a última execução concluída. Nota: Este Schedule agenda a execução inicial para Instant.now() quando usado em startTasks(...) |
.cron(String) | Expressão cron estilo Spring (v5.3+). O padrão - é interpretado como um agendamento desabilitado. |
Outra opção para configurar agendamentos é ler padrões de string com Schedules.parse(String)
.
Os padrões atualmente disponíveis são:
Padrão | Descrição |
---|---|
FIXED_DELAY|Ns | O mesmo que .fixedDelay(Duration) com duração definida como N segundos. |
DAILY|12:30,15:30...(|time_zone) | O mesmo que .daily(LocalTime) com fuso horário opcional (por exemplo, Europa/Roma, UTC) |
- | Agenda desativada |
Mais detalhes sobre os formatos de fuso horário podem ser encontrados aqui.
Uma Schedule
pode ser marcada como desativada. O agendador não agendará as execuções iniciais para tarefas com agendamento desabilitado e removerá quaisquer execuções existentes para essa tarefa.
Uma instância de tarefa pode ter alguns dados associados no campo task_data
. O agendador usa um Serializer
para ler e gravar esses dados no banco de dados. Por padrão, a serialização Java padrão é usada, mas diversas opções são fornecidas:
GsonSerializer
JacksonSerializer
Para serialização Java é recomendado especificar um serialVersionUID
para poder evoluir a classe que representa os dados. Se não for especificado e a classe for alterada, a desserialização provavelmente falhará com um InvalidClassException
. Caso isso aconteça, encontre e defina explicitamente o serialVersionUID
atual gerado automaticamente. Será então possível fazer alterações ininterruptas na classe.
Se você precisar migrar da serialização Java para um GsonSerializer
, configure o agendador para usar SerializerWithFallbackDeserializers
:
. serializer ( new SerializerWithFallbackDeserializers ( new GsonSerializer (), new JavaSerializer ()))
Para aplicativos Spring Boot, existe um starter db-scheduler-spring-boot-starter
que torna a fiação do agendador muito simples. (Veja exemplo de projeto completo).
DataSource
funcional com esquema inicializado. (No exemplo, HSQLDB é usado e o esquema é aplicado automaticamente.)< dependency >
< groupId >com.github.kagkarlsson</ groupId >
< artifactId >db-scheduler-spring-boot-starter</ artifactId >
< version >15.0.0</ version >
</ dependency >
Task
como beans Spring. Se forem recorrentes, serão automaticamente selecionados e iniciados.Scheduler
nas informações de integridade do atuador, você precisa ativar o indicador de integridade db-scheduler
. Informações de saúde da primavera. A configuração é feita principalmente por meio de application.properties
. A configuração do nome do agendador, serializador e serviço do executor é feita adicionando um bean do tipo DbSchedulerCustomizer
ao seu 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
É possível utilizar o Scheduler
para interagir com as execuções futuras persistentes. Para situações onde uma instância completa Scheduler
não é necessária, um SchedulerClient mais simples pode ser criado usando seu construtor:
SchedulerClient . Builder . create ( dataSource , taskDefinitions ). build ()
Permitirá operações como:
Uma única tabela de banco de dados é usada para rastrear futuras execuções de tarefas. Quando a execução de uma tarefa é vencida, o db-scheduler a seleciona e a executa. Terminada a execução, a Task
é consultada para saber o que deve ser feito. Por exemplo, um RecurringTask
normalmente é reprogramado no futuro com base em seu Schedule
.
O agendador usa bloqueio otimista ou seleção para atualização (dependendo da estratégia de pesquisa) para garantir que uma e apenas uma instância do agendador escolha e execute uma execução de tarefa.
O termo tarefa recorrente é usado para tarefas que devem ser executadas regularmente, de acordo com algum cronograma.
Quando a execução de uma tarefa recorrente termina, um Schedule
é consultado para determinar qual deve ser o próximo horário de execução, e uma futura tarefa-execução é criada para esse horário (ou seja, ela é reprogramada ). O horário escolhido será o horário mais próximo de acordo com o Schedule
, mas ainda no futuro.
Existem dois tipos de tarefas recorrentes, a tarefa recorrente estática regular, onde o Schedule
é definido estaticamente no código, e as tarefas recorrentes dinâmicas , onde o Schedule
é definido em tempo de execução e persistido no banco de dados (ainda exigindo apenas uma única tabela) .
A tarefa recorrente estática é a mais comum e adequada para trabalhos regulares em segundo plano, pois o agendador agenda automaticamente uma instância da tarefa se ela não estiver presente e também atualiza o próximo tempo de execução se o Schedule
for atualizado.
Para criar a execução inicial de uma tarefa estática recorrente, o escalonador possui um método startTasks(...)
que pega uma lista de tarefas que devem ser "iniciadas" caso ainda não tenham uma execução existente. O tempo de execução inicial é determinado pelo Schedule
. Se a tarefa já tiver uma execução futura (ou seja, tiver sido iniciada pelo menos uma vez antes), mas um Schedule
atualizado indicar agora outro tempo de execução, a execução existente será reprogramada para o novo tempo de execução (com exceção de tarefas não determinísticas). cronogramas como FixedDelay
, onde o novo tempo de execução está mais distante no futuro).
Crie usando Tasks.recurring(..)
.
A tarefa dinâmica recorrente é uma adição posterior ao db-scheduler e foi adicionada para suportar casos de uso onde há necessidade de múltiplas instâncias do mesmo tipo de tarefa (ou seja, mesma implementação) com agendamentos diferentes. O Schedule
é persistido em task_data
junto com quaisquer dados regulares. Ao contrário da tarefa recorrente estática , a dinâmica não agendará automaticamente instâncias da tarefa. Cabe ao usuário criar instâncias e atualizar o agendamento das existentes, se necessário (usando a interface SchedulerClient
). Consulte o exemplo RecurringTaskWithPersistentScheduleMain.java para obter mais detalhes.
Crie usando Tasks.recurringWithPersistentSchedule(..)
.
O termo tarefa única é usado para tarefas que possuem um único tempo de execução. Além de codificar dados no instanceId
de uma execução de tarefa, é possível armazenar dados binários arbitrários em um campo separado para uso em tempo de execução. Por padrão, a serialização Java é usada para empacotar/desempacotar os dados.
Crie usando Tasks.oneTime(..)
.
Para tarefas que não se enquadram nas categorias acima, é possível personalizar totalmente o comportamento das tarefas usando Tasks.custom(..)
.
Os casos de uso podem ser:
Durante a execução, o agendador atualiza regularmente o tempo de pulsação para a execução da tarefa. Se uma execução for marcada como em execução, mas não estiver recebendo atualizações no tempo de pulsação, ela será considerada uma execução morta após o tempo X. Isso pode acontecer, por exemplo, se a JVM que executa o agendador for encerrada repentinamente.
Quando uma execução morta é encontrada, a Task
é consultada para ver o que deve ser feito. Um RecurringTask
morto normalmente é reprogramado para now()
.
Embora o db-scheduler inicialmente fosse direcionado a casos de uso de baixo a médio rendimento, ele lida muito bem com casos de uso de alto rendimento (mais de 1.000 execuções/segundo) devido ao fato de seu modelo de dados ser muito simples, consistindo em uma única tabela de execuções. Para entender seu desempenho, é útil considerar as instruções SQL que ele executa por lote de execuções.
A estratégia de pesquisa original e padrão, fetch-and-lock-on-execute
, fará o seguinte:
select
um lote de execuções devidasupdate
a execução picked=true
para esta instância do agendador. Pode perder devido a agendadores concorrentes.update
ou delete
o registro de acordo com os manipuladores.No total por lote: 1 seleção, 2 * atualizações de tamanho de lote (excluindo erros)
Na v10, uma nova estratégia de pesquisa ( lock-and-fetch
) foi adicionada. Ele utiliza o fato de que a maioria dos bancos de dados agora tem suporte para SKIP LOCKED
em instruções SELECT FOR UPDATE
(consulte o blog do 2ndquadrant). Utilizando tal estratégia, é possível buscar execuções pré-bloqueadas, obtendo assim uma instrução a menos:
select for update .. skip locked
um lote de execuções devidas. Eles já serão escolhidos pela instância do agendador.update
ou delete
o registro de acordo com os manipuladores.No total por lote: 1 seleção e atualização, 1 * atualizações de tamanho de lote (sem falhas)
Para ter uma ideia do que esperar do db-scheduler, veja os resultados dos testes executados no GCP abaixo. Os testes foram executados com algumas configurações diferentes, mas cada uma usando quatro instâncias de agendadores concorrentes executadas em VMs separadas. TPS é o aprox. transações por segundo, conforme mostrado no GCP.
Busca de rendimento (ex/s) | Busca TPS (estimativas) | Bloqueio e busca de rendimento (ex/s) | Bloqueio e busca de TPS (estimativas) | |
---|---|---|---|---|
Postgres 4 núcleos, 25 GB de RAM, 4xVMs (2 núcleos) | ||||
20 fios, inferior 4,0, superior 20,0 | 2000 | 9.000 | 10600 | 11.500 |
100 fios, inferior 2,0, superior 6,0 | 2560 | 11.000 | 11200 | 11200 |
Postgres 8 núcleos, 50 GB de RAM, 4xVMs (4 núcleos) | ||||
50 fios, inferior: 0,5, superior: 4,0 | 4000 | 22.000 | 11840 | 10300 |
Observações para estes testes:
fetch-and-lock-on-execute
lock-and-fetch
Atualmente, a estratégia de pesquisa lock-and-fetch
é implementada apenas para Postgres. Contribuições adicionando suporte para mais bancos de dados são bem-vindas.
Vários usuários estão usando o db-scheduler para casos de uso de alto rendimento. Veja por exemplo:
Não há garantias de que todos os instantes de um agendamento de RecurringTask
serão executados. O Schedule
é consultado após o término da execução da tarefa anterior, e o horário futuro mais próximo será selecionado para o próximo horário de execução. Um novo tipo de tarefa poderá ser adicionado no futuro para fornecer tal funcionalidade.
Os métodos em SchedulerClient
( schedule
, cancel
, reschedule
) serão executados usando uma nova Connection
do DataSource
fornecido. Para que a ação faça parte de uma transação, ela deve ser cuidada pelo DataSource
fornecido, por exemplo, usando algo como TransactionAwareDataSourceProxy
do Spring.
Atualmente, a precisão do db-scheduler depende do pollingInterval
(padrão 10s), que especifica com que frequência procurar na tabela as execuções devidas. Se você sabe o que está fazendo, o agendador pode ser instruído em tempo de execução para "olhar antecipadamente" por meio de scheduler.triggerCheckForDueExecutions()
. (Veja também enableImmediateExecution()
no Builder
)
Consulte os lançamentos para obter notas de lançamento.
Atualizando para 15.x
priority
da coluna e o índice priority_execution_time_idx
devem ser adicionados ao esquema do banco de dados. Veja as definições de tabela para postgresql, oracle ou mysql. Em algum momento, esta coluna se tornará obrigatória. Isso ficará claro em futuras notas de lançamento/atualização.Atualizando para 8.x
boolean isDeterministic()
para indicar se produzirão sempre os mesmos instantes ou não.Atualizando para 4.x
consecutive_failures
ao esquema do banco de dados. Veja as definições de tabela para postgresql, oracle ou mysql. null
é tratado como 0, portanto não há necessidade de atualizar os registros existentes.Atualizando para 3.x
Tasks
Atualizando para 2.x
task_data
ao esquema do banco de dados. Veja as definições de tabela para postgresql, oracle ou mysql. Pré-requisitos
Siga estas etapas:
Clone o repositório.
git clone https://github.com/kagkarlsson/db-scheduler
cd db-scheduler
Construa usando Maven (pule os testes adicionando -DskipTests=true
)
mvn package
Especificação recomendada
Alguns usuários experimentaram falhas de teste intermitentes ao executar em VMs de núcleo único. Portanto, recomenda-se usar no mínimo:
db-scheduler
quando existe Quartz
? O objetivo do db-scheduler
é ser não invasivo e simples de usar, mas ainda assim resolver o problema de persistência e o problema de coordenação de cluster. Ele foi originalmente direcionado a aplicativos com esquemas de banco de dados modestos, aos quais adicionar 11 tabelas seria um pouco exagerado. Atualização: Além disso, a partir de agora (2024), o Quartz também não parece ser mantido ativamente.
BEIJO. É o tipo mais comum de aplicativos de estado compartilhado.
Crie um problema com a solicitação de recurso e poderemos discuti-lo lá. Se você está impaciente (ou deseja contribuir), solicitações pull são muito bem-vindas :)
Sim. Ele é usado na produção em diversas empresas e até agora tem funcionado perfeitamente.