Планировщик задач для Java, который был вдохновлен необходимостью кластерного java.util.concurrent.ScheduledExecutorService
более простого, чем Quartz.
Таким образом, пользователи (cbarbosa2, rafaelhofmann, BukhariH) также оценили:
Ваша библиотека потрясающая! Я так рад, что избавился от кварца и заменил его вашим, с которым намного проще обращаться!
cbarbosa2
См. также, почему не Кварц?
< dependency >
< groupId >com.github.kagkarlsson</ groupId >
< artifactId >db-scheduler</ artifactId >
< version >15.0.0</ version >
</ dependency >
Создайте таблицу scheduled_tasks
в схеме базы данных. См. определение таблицы для postgresql, oracle, mssql или mysql.
Создайте экземпляр и запустите планировщик, который затем запустит любые определенные повторяющиеся задачи.
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 ();
Чтобы увидеть больше примеров, продолжайте читать. Подробную информацию о внутренней работе см. в разделе «Как это работает». Если у вас есть приложение Spring Boot, ознакомьтесь с разделом «Использование Spring Boot».
Список организаций, которые, как известно, используют db-scheduler в производстве:
Компания | Описание |
---|---|
Дигипост | Поставщик цифровых почтовых ящиков в Норвегии |
Вы Групп | Одна из крупнейших транспортных групп в странах Северной Европы. |
Мудрый | Дешевый и быстрый способ отправить деньги за границу. |
Профессиональное образование Беккера | |
Монитория | Сервис мониторинга сайта. |
Погрузчик | Нагрузочное тестирование веб-приложений. |
Статенс вегвесен | Норвежское управление дорог общего пользования |
Световой год | Простой и доступный способ инвестировать свои деньги по всему миру. |
НАВ | Норвежское управление труда и социального обеспечения |
МодернЛуп | Масштабируйтесь в соответствии с потребностями вашей компании в найме, используя ModernLoop, чтобы повысить эффективность планирования собеседований, общения и координации. |
Диффия | Норвежская компания электронного здравоохранения |
Лебедь | Swan помогает разработчикам легко встраивать банковские услуги в свои продукты. |
ТОМРА | TOMRA — норвежская транснациональная компания, которая разрабатывает и производит реверсивные торговые автоматы для переработки. |
Не стесняйтесь открыть PR, чтобы добавить свою организацию в список.
См. также работоспособные примеры.
Определите повторяющуюся задачу и запланируйте ее первое выполнение при запуске, используя метод компоновщика startTasks
. По завершении задача будет перепланирована в соответствии с заданным расписанием (см. предварительно определенные типы расписаний).
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 ();
Для повторяющихся задач с несколькими экземплярами и расписаниями см. пример RecurringTaskWithPersistentScheduleMain.java.
Экземпляр одноразовой задачи имеет одно время выполнения некоторое время в будущем (т. е. неповторяющееся). Идентификатор экземпляра должен быть уникальным в пределах этой задачи и может использоваться для кодирования некоторых метаданных (например, идентификатора). Для более сложного состояния поддерживаются пользовательские сериализуемые объекты Java (как показано в примере).
Определите одноразовую задачу и запустите планировщик:
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 ();
... а затем в какой-то момент (во время выполнения) выполнение планируется с использованием 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 )));
Пример | Описание |
---|---|
EnableImmediateExecutionMain.java | При планировании выполнения now() или ранее локальному Scheduler будет сообщено об этом, и он «проснется», чтобы проверить наличие новых выполнений раньше, чем обычно (как настроено pollingInterval . |
MaxRetriesMain.java | Как установить ограничение на количество повторных попыток выполнения. |
ЭкспоненциальныйBackoffMain.java | Как использовать экспоненциальную отсрочку в качестве стратегии повтора вместо фиксированной задержки, как по умолчанию. |
ExponentialBackoffWithMaxRetriesMain.java | Как использовать экспоненциальную отсрочку в качестве стратегии повторных попыток и жесткое ограничение максимального количества повторных попыток. |
Отслеживание прогрессаRecurringTaskMain.java | Повторяющиеся задания могут хранить task_data как способ сохранения состояния во время выполнения. Этот пример показывает, как это сделать. |
СозданиеOtherTasksMain.java | Демонстрирует экземпляры планирования задач другого с помощью executionContext.getSchedulerClient() . |
SchedulerClientMain.java | Демонстрирует некоторые возможности SchedulerClient . Планирование, получение запланированных исполнений и т. д. |
RecurringTaskWithPersistentScheduleMain.java | Многоэкземплярные повторяющиеся задания, в которых Schedule хранится как часть task_data . Например, подходит для мультитенантных приложений, где у каждого арендатора должна быть повторяющаяся задача. |
StatefulRecurringTaskWithPersistentScheduleMain.java | |
JsonSerializerMain.java | Переопределяет сериализацию task_data из Java-сериализации (по умолчанию) в JSON. |
JobChainingUsingTaskDataMain.java | Цепочка заданий, то есть «когда выполнение этого экземпляра завершится, запланируйте другую задачу. |
JobChainingUsingSeparateTasksMain.java | Цепочка заданий, как указано выше. |
ПерехватчикMain.java | Использование ExecutionInterceptor для внедрения логики до и после выполнения всех ExecutionHandler . |
Пример | Описание |
---|---|
Основные примеры | Базовая одноразовая задача и повторяющаяся задача |
Транзакционно постановочное задание | Пример транзакционной подготовки задания, т.е. обеспечения выполнения фонового задания, если транзакция фиксируется (наряду с другими модификациями базы данных). |
LongRunningJob | Длительно выполняемые задания должны выдерживать перезапуск приложения и избегать перезапуска с самого начала. В этом примере показано, как сохранить прогресс при завершении работы, а также метод ограничения выполнения задания в ночное время. |
Рекуррингстатетрекинг | Повторяющаяся задача, состояние которой можно изменить после каждого запуска. |
ParallelJobSpawner | Демонстрирует, как использовать повторяющееся задание для создания одноразовых заданий, например, для распараллеливания. |
Цепочка заданий | Одноразовая работа, состоящая из нескольких этапов . Следующий шаг назначается после завершения предыдущего. |
МультиэкземплярRecurring | Демонстрирует, как выполнить несколько повторяющихся заданий одного и того же типа, но потенциально с разными расписаниями и данными. |
Планировщик создается с помощью построителя Scheduler.create(...)
. В конструкторе есть разумные настройки по умолчанию, но следующие параметры можно настроить.
.threads(int)
Количество потоков. По умолчанию 10
.
.pollingInterval(Duration)
Как часто планировщик проверяет базу данных на предмет своевременного выполнения. По умолчанию 10s
.
.alwaysPersistTimestampInUTC()
Планировщик предполагает, что столбцы для сохраняющихся меток времени сохраняют Instant
s, а не LocalDateTime
s, т.е. каким-то образом привязывают метку времени к зоне. Однако некоторые базы данных имеют ограниченную поддержку таких типов (которые не содержат информации о зоне) или других особенностей, что делает вариант «всегда хранить в формате UTC» лучшей альтернативой. В таких случаях используйте этот параметр, чтобы всегда хранить мгновенные сообщения в формате UTC. Схемы PostgreSQL и Oracle проверены на сохранение информации о зоне. Схемы MySQL и MariaDB не поддерживают и должны использовать этот параметр. Примечание. В целях обратной совместимости поведение по умолчанию для «неизвестных» баз данных заключается в предположении, что база данных сохраняет часовой пояс. Для «известных» баз данных см. класс AutodetectJdbcCustomization
.
.enableImmediateExecution()
Если эта опция включена, планировщик попытается намекнуть локальному Scheduler
, что есть выполнения, которые должны быть выполнены после того, как они запланированы на now()
или какое-то время в прошлом. Примечание. Если вызов schedule(..)
/ reschedule(..)
происходит внутри транзакции, планировщик может попытаться запустить его до того, как обновление станет видимым (транзакция не зафиксирована). Однако он по-прежнему сохраняется, поэтому даже если это промах, он будет запущен до следующего polling-interval
. Вы также можете программно запустить раннюю проверку сроков выполнения, используя метод scheduler.triggerCheckForDueExecutions()
) . По умолчанию false
.
.registerShutdownHook()
Регистрирует перехватчик завершения работы, который будет вызывать Scheduler.stop()
при завершении работы. Остановку всегда следует вызывать для корректного завершения работы и во избежание мертвых исполнений.
.shutdownMaxWait(Duration)
Как долго планировщик будет ждать, прежде чем прерывать потоки службы исполнителя. Если вы обнаружите, что используете это, подумайте, можно ли вместо этого регулярно проверять executionContext.getSchedulerState().isShuttingDown()
в ExecutionHandler и прерывать длительную задачу. По умолчанию 30min
.
.enablePriority()
Можно определить приоритет выполнения, который определяет порядок, в котором необходимые выполнения извлекаются из базы данных. Выполнение с более высоким значением приоритета будет выполняться раньше выполнения с более низким значением (технически порядок будет order by priority desc, execution_time asc
). Рассмотрите возможность использования приоритетов в диапазоне 0–32000, поскольку поле определено как SMALLINT
. Если вам нужно большее значение, измените схему. На данный момент эта функция является добровольной , и priority
столбца необходим только тем пользователям, которые решили включить приоритет с помощью этого параметра конфигурации.
Установите приоритет для каждого экземпляра с помощью TaskInstance.Builder
:
scheduler . schedule (
MY_TASK
. instance ( "1" )
. priority ( 100 )
. scheduledTo ( Instant . now ()));
Примечание:
(execution_time asc, priority desc)
(заменив старый execution_time asc
).null
для приоритета может интерпретироваться по-разному в зависимости от базы данных (низкий или высокий). Если вы выполняете > 1000 выполнений в секунду, возможно, вы захотите использовать стратегию опроса lock-and-fetch
для снижения накладных расходов и повышения пропускной способности (подробнее). В противном случае подойдет стандартная fetch-and-lock-on-execute
.
.pollUsingFetchAndLockOnExecute(double, double)
Используйте стратегию опроса по умолчанию fetch-and-lock-on-execute
.
Если последняя выборка из базы данных была полной пакетной ( executionsPerBatchFractionOfThreads
), новая выборка будет запущена, когда количество оставшихся выполнений станет меньше или равно lowerLimitFractionOfThreads * nr-of-threads
. Выбранные исполнения не блокируются/не выбираются, поэтому планировщик будет конкурировать с другими экземплярами за блокировку при выполнении. Поддерживается всеми базами данных.
По умолчанию: 0,5, 3.0
.pollUsingLockAndFetch(double, double)
Используйте стратегию опроса lock-and-fetch
, которая использует select for update .. skip locked
для уменьшения накладных расходов.
Если последняя выборка из базы данных была полным пакетом, новая выборка будет запущена, когда количество оставшихся выполнений станет меньше или равно lowerLimitFractionOfThreads * nr-of-threads
. Количество выполнений, извлекаемых каждый раз, равно (upperLimitFractionOfThreads * nr-of-threads) - nr-executions-left
. Выбранные исполнения уже заблокированы/выбраны для этого экземпляра планировщика, что позволяет сохранить один оператор UPDATE
.
Для обычного использования установите, например, 0.5, 1.0
.
Для высокой пропускной способности (т. е. чтобы потоки были заняты) установите, например, 1.0, 4.0
. В настоящее время контрольные сигналы не обновляются для выбранных исполнений в очереди (применимо, если upperLimitFractionOfThreads > 1.0
). Если они остаются там более 4 * heartbeat-interval
(по умолчанию 20m
), не начав выполнение, они будут обнаружены как мертвые и, вероятно, будут снова разблокированы (определяется DeadExecutionHandler
). В настоящее время поддерживается postgres . sql-server также поддерживает это, но тестирование показало, что это подвержено взаимоблокировкам и поэтому не рекомендуется, пока не будет понято/разрешено.
.heartbeatInterval(Duration)
Как часто обновлять временную метку пульса для выполнения выполнения. По умолчанию 5m
.
.missedHeartbeatsLimit(int)
Сколько ударов сердца может быть пропущено, прежде чем казнь будет считаться мертвой. По умолчанию 6
.
.addExecutionInterceptor(ExecutionInterceptor)
Добавляет ExecutionInterceptor
, который может вводить логику выполнения. Для Spring Boot просто зарегистрируйте Bean типа ExecutionInterceptor
.
.addSchedulerListener(SchedulerListener)
Добавляет SchedulerListener
, который будет получать события, связанные с планировщиком и выполнением. Для Spring Boot просто зарегистрируйте Bean типа SchedulerListener
.
.schedulerName(SchedulerName)
Имя этого экземпляра планировщика. Имя сохраняется в базе данных, когда планировщик выбирает выполнение. По умолчанию <hostname>
.
.tableName(String)
Имя таблицы, используемой для отслеживания выполнения задач. Измените имя в определениях таблиц соответствующим образом при создании таблицы. scheduled_tasks
по умолчанию.
.serializer(Serializer)
Реализация сериализатора, используемая при сериализации данных задачи. По умолчанию используется стандартная сериализация Java, но db-scheduler также включает в себя GsonSerializer
и JacksonSerializer
. См. примеры для KotlinSerializer. См. также дополнительную документацию в разделе Сериализаторы.
.executorService(ExecutorService)
Если указано, используйте эту службу исполнителя, управляемую извне, для запуска выполнения. В идеале количество потоков, которые он будет использовать, все равно должно быть указано (для оптимизации опроса планировщика). По умолчанию null
.
.deleteUnresolvedAfter(Duration)
Время, по истечении которого выполнения с неизвестными задачами автоматически удаляются. Обычно это могут быть старые повторяющиеся задачи, которые больше не используются. Это значение не равно нулю, чтобы предотвратить случайное удаление задач из-за ошибки конфигурации (отсутствие известных задач) и проблем во время последовательных обновлений. По умолчанию 14d
.
.jdbcCustomization(JdbcCustomization)
db-scheduler пытается автоматически определить используемую базу данных, чтобы определить, нужно ли настраивать какие-либо jdbc-взаимодействия. Этот метод представляет собой запасной вариант, позволяющий явно устанавливать JdbcCustomizations
. Автоопределение по умолчанию.
.commitWhenAutocommitDisabled(boolean)
По умолчанию для соединений с источниками данных фиксация не выполняется. Если автоматическая фиксация отключена, предполагается, что транзакции обрабатываются внешним менеджером транзакций. Установите для этого свойства значение true
, чтобы переопределить это поведение и планировщик всегда будет выполнять фиксации. По умолчанию false
.
.failureLogging(Level, boolean)
Настраивает способ регистрации сбоев задач, т. е. Throwable
, выдаваемых обработчиком выполнения задачи. Используйте уровень журнала OFF
чтобы полностью отключить этот тип журнала. WARN, true
.
Задачи создаются с использованием одного из классов-строителей в Tasks
. У разработчиков есть разумные настройки по умолчанию, но следующие параметры можно переопределить.
Вариант | По умолчанию | Описание |
---|---|---|
.onFailure(FailureHandler) | см. описание. | Что делать, если ExecutionHandler генерирует исключение. По умолчанию повторяющиеся задачи перепланируются в соответствии с их Schedule . Одноразовые задачи повторяются через 5 минут. |
.onDeadExecution(DeadExecutionHandler) | ReviveDeadExecution | Что делать, если обнаружено неактивное выполнение , т. е. выполнение с устаревшей меткой времени контрольного сигнала. По умолчанию мертвые выполнения переносятся на now() . |
.initialData(T initialData) | null | Данные, которые будут использоваться при первом планировании повторяющейся задачи . |
Библиотека содержит ряд реализаций Schedule для повторяющихся задач. Смотрите Schedules
занятий.
Расписание | Описание |
---|---|
.daily(LocalTime ...) | Запускается каждый день в определенное время. При желании можно указать часовой пояс. |
.fixedDelay(Duration) | Следующее время выполнения — это Duration после последнего завершенного выполнения. Примечание. В этом Schedule первоначальное выполнение запланировано на Instant.now() при использовании в startTasks(...) |
.cron(String) | Выражение cron в стиле Spring (v5.3+). Паттерн - интерпретируется как отключенный график. |
Другой вариант настройки расписаний — чтение шаблонов строк с помощью Schedules.parse(String)
.
На данный момент доступны следующие шаблоны:
Шаблон | Описание |
---|---|
FIXED_DELAY|Ns | То же, что и .fixedDelay(Duration) с длительностью N секунд. |
DAILY|12:30,15:30...(|time_zone) | То же, что .daily(LocalTime) с дополнительным часовым поясом (например, Европа/Рим, UTC). |
- | Отключенное расписание |
Более подробную информацию о форматах часовых поясов можно найти здесь.
Schedule
можно пометить как отключенное. Планировщик не будет планировать начальные выполнения задач с отключенным расписанием и удалит все существующие выполнения этой задачи.
Экземпляр задачи может иметь некоторые связанные данные в поле task_data
. Планировщик использует Serializer
для чтения и записи этих данных в базу данных. По умолчанию используется стандартная сериализация Java, но предусмотрен ряд опций:
GsonSerializer
JacksonSerializer
Для сериализации Java рекомендуется указать serialVersionUID
, чтобы иметь возможность развивать класс, представляющий данные. Если не указано и класс изменяется, десериализация, скорее всего, завершится с ошибкой InvalidClassException
. Если это произойдет, найдите и явно установите текущий автоматически сгенерированный serialVersionUID
. Тогда можно будет вносить некритичные изменения в класс.
Если вам нужно перейти с сериализации Java на GsonSerializer
, настройте планировщик на использование SerializerWithFallbackDeserializers
:
. serializer ( new SerializerWithFallbackDeserializers ( new GsonSerializer (), new JavaSerializer ()))
Для приложений Spring Boot существует стартер db-scheduler-spring-boot-starter
который упрощает подключение планировщика. (См. полный пример проекта).
DataSource
с инициализированной схемой. (В примере используется HSQLDB, и схема применяется автоматически.)< dependency >
< groupId >com.github.kagkarlsson</ groupId >
< artifactId >db-scheduler-spring-boot-starter</ artifactId >
< version >15.0.0</ version >
</ dependency >
Task
как bean-компоненты Spring. Если они повторяются, они будут автоматически выбраны и запущены.Scheduler
в информации о работоспособности привода, вам необходимо включить индикатор работоспособности db-scheduler
. Весенняя информация о здоровье. Конфигурация в основном выполняется через application.properties
. Конфигурация имени планировщика, сериализатора и службы-исполнителя выполняется путем добавления компонента типа DbSchedulerCustomizer
в контекст 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
Scheduler
можно использовать для взаимодействия с сохраняющимися будущими выполнениями. В ситуациях, когда полный экземпляр Scheduler
не требуется, можно создать более простой SchedulerClient с помощью его компоновщика:
SchedulerClient . Builder . create ( dataSource , taskDefinitions ). build ()
Это позволит выполнять такие операции, как:
Одна таблица базы данных используется для отслеживания будущих выполнения задач. Когда наступает срок выполнения задачи, db-scheduler выбирает ее и выполняет. Когда выполнение завершено, Task
обращаются, чтобы узнать, что следует сделать. Например, RecurringTask
обычно переносится на будущее на основе его Schedule
.
Планировщик использует оптимистическую блокировку или выбор для обновления (в зависимости от стратегии опроса), чтобы гарантировать, что один и только один экземпляр планировщика сможет выбрать и запустить выполнение задачи.
Термин повторяющаяся задача используется для задач, которые должны выполняться регулярно, в соответствии с некоторым графиком.
Когда выполнение повторяющейся задачи завершено, Schedule
обращаются, чтобы определить, каким должно быть следующее время выполнения, и на это время создается будущее выполнение задачи (т. е. оно переносится ). Выбранное время будет ближайшим по Schedule
, но еще в будущем.
Существует два типа повторяющихся задач: обычная статическая повторяющаяся задача, где Schedule
определяется статически в коде, и динамические повторяющиеся задачи, где Schedule
определяется во время выполнения и сохраняется в базе данных (по-прежнему требуется только одна таблица). .
Статическая повторяющаяся задача является наиболее распространенной и подходит для обычных фоновых заданий, поскольку планировщик автоматически планирует экземпляр задачи, если она отсутствует, а также обновляет следующее время выполнения, если Schedule
обновляется.
Чтобы создать начальное выполнение статической повторяющейся задачи, в планировщике есть метод startTasks(...)
, который принимает список задач, которые следует «запустить», если у них еще нет существующего выполнения. Начальное время выполнения определяется Schedule
. Если задача уже имеет будущее выполнение (т. е. была запущена хотя бы один раз ранее), но обновленное Schedule
теперь указывает на другое время выполнения, существующее выполнение будет перенесено на новое время выполнения (за исключением недетерминированных расписания, такие как FixedDelay
, где новое время выполнения находится в более отдаленном будущем).
Создайте с помощью Tasks.recurring(..)
.
Динамическая повторяющаяся задача является более поздним дополнением к db-scheduler и была добавлена для поддержки случаев использования, когда требуется несколько экземпляров задачи одного и того же типа (т. е. одной и той же реализации) с разными расписаниями. Schedule
сохраняется в task_data
вместе с любыми обычными данными. В отличие от статической повторяющейся задачи, динамическая не планирует автоматически экземпляры задачи. Пользователь может создавать экземпляры и при необходимости обновлять расписание существующих (с помощью интерфейса SchedulerClient
). Дополнительные сведения см. в примере RecurringTaskWithPersistentScheduleMain.java.
Создайте с помощью Tasks.recurringWithPersistentSchedule(..)
.
Термин «одноразовая задача» используется для задач, имеющих одно время выполнения. Помимо кодирования данных в instanceId
выполнения задачи, можно хранить произвольные двоичные данные в отдельном поле для использования во время выполнения. По умолчанию для маршалирования/демаршалирования данных используется сериализация Java.
Создайте с помощью Tasks.oneTime(..)
.
Для задач, не соответствующих вышеуказанным категориям, можно полностью настроить поведение задач с помощью Tasks.custom(..)
.
Варианты использования могут быть:
Во время выполнения планировщик регулярно обновляет контрольное время для выполнения задачи. Если выполнение помечено как выполняемое, но не получает обновлений контрольного времени, оно будет считаться неработающим по истечении времени X. Это может произойти, например, если JVM, на которой работает планировщик, внезапно завершает работу.
Когда обнаруживается мертвое выполнение, Task
обращаются, чтобы узнать, что следует сделать. Неработающая RecurringTask
обычно переносится на now()
.
Хотя db-scheduler изначально был нацелен на сценарии использования с низкой и средней пропускной способностью, он довольно хорошо справляется с вариантами использования с высокой пропускной способностью (1000+ выполнений в секунду) благодаря тому, что его модель данных очень проста и состоит из единая таблица казней. Чтобы понять, как он будет работать, полезно рассмотреть операторы SQL, которые он выполняет в каждом пакете выполнения.
Исходная и используемая по умолчанию стратегия опроса fetch-and-lock-on-execute
выполняет следующие действия:
select
пакет должных исполненийupdate
выполнение до picked=true
для этого экземпляра планировщика. Может пропустить из-за конкурирующих планировщиков.update
или delete
запись в соответствии с обработчиками.В сумме за пакет: 1 выбор, 2 * обновления размера пакета (исключая промахи)
В v10 была добавлена новая стратегия опроса ( lock-and-fetch
). Он использует тот факт, что большинство баз данных теперь поддерживают SKIP LOCKED
в операторах SELECT FOR UPDATE
(см. блог 2ndquadrant). Используя такую стратегию, можно получить предварительно заблокированные исполнения и, таким образом, получить на один оператор меньше:
select for update .. skip locked
пакет должных исполнений. Они уже будут выбраны экземпляром планировщика.update
или delete
запись в соответствии с обработчиками.В сумме за пакет: 1 выборка и обновление, 1 * обновление размера пакета (без промахов).
Чтобы получить представление о том, чего ожидать от db-scheduler, см. результаты тестов, проведенных в GCP, ниже. Тесты проводились с несколькими различными конфигурациями, но каждая из них использовала четыре конкурирующих экземпляра планировщика, работающие на отдельных виртуальных машинах. TPS — это ок. транзакций в секунду, как показано в GCP.
Выборка пропускной способности (ex/s) | Выборка TPS (оценка) | Блокировка и выборка пропускной способности (ex/s) | Блокировка и выборка TPS (оценка) | |
---|---|---|---|---|
Postgres 4 ядра, 25 ГБ ОЗУ, 4xVM (2 ядра) | ||||
20 потоков, нижняя 4.0, верхняя 20.0 | 2000 г. | 9000 | 10600 | 11500 |
100 потоков, нижняя 2.0, верхняя 6.0 | 2560 | 11000 | 11200 | 11200 |
Postgres 8 ядер, 50 ГБ ОЗУ, 4xVM (4 ядра) | ||||
50 потоков, нижняя: 0,5, верхняя: 4,0 | 4000 | 22000 | 11840 | 10300 |
Наблюдения по этим тестам:
fetch-and-lock-on-execute
lock-and-fetch
В настоящее время стратегия опроса lock-and-fetch
реализована только для Postgres. Приветствуются вклады, добавляющие поддержку большего количества баз данных.
Некоторые пользователи используют db-scheduler для задач с высокой пропускной способностью. См. например:
Нет никаких гарантий, что все моменты в расписании RecurringTask
будут выполнены. К Schedule
обращаются после завершения выполнения предыдущей задачи, и для следующего времени выполнения будет выбрано ближайшее время в будущем. В будущем для обеспечения такой функциональности может быть добавлен новый тип задач.
Методы SchedulerClient
( schedule
, cancel
, reschedule
) будут запускаться с использованием нового Connection
из предоставленного DataSource
. Чтобы действие было частью транзакции, о нем должен позаботиться предоставленный DataSource
, например, с использованием чего-то вроде TransactionAwareDataSourceProxy
в Spring.
В настоящее время точность db-scheduler зависит от pollingInterval
(по умолчанию 10 с), который определяет, как часто просматривать таблицу для своевременного выполнения. Если вы знаете, что делаете, во время выполнения планировщику может быть дано указание «смотреть раньше» через scheduler.triggerCheckForDueExecutions()
. (См. также enableImmediateExecution()
в Builder
)
См. выпуски для примечаний к выпуску.
Обновление до 15.x
priority
столбца и индекс priority_execution_time_idx
. См. определения таблиц для postgresql, oracle или mysql. В какой-то момент этот столбец станет обязательным. Это будет пояснено в будущих примечаниях к выпуску/обновлению.Обновление до 8.x
boolean isDeterministic()
чтобы указать, будут ли они всегда создавать одни и те же моменты времени или нет.Обновление до 4.x
consecutive_failures
в схему базы данных. См. определения таблиц для postgresql, oracle или mysql. null
обрабатывается как 0, поэтому нет необходимости обновлять существующие записи.Обновление до 3.x
Tasks
.Обновление до 2.x
task_data
в схему базы данных. См. определения таблиц для postgresql, oracle или mysql. Предварительные условия
Выполните следующие действия:
Клонируйте репозиторий.
git clone https://github.com/kagkarlsson/db-scheduler
cd db-scheduler
Сборка с использованием Maven (пропустите тесты, добавив -DskipTests=true
)
mvn package
Рекомендуемая спецификация
Некоторые пользователи сталкивались с периодическими сбоями тестирования при работе на одноядерных виртуальных машинах. Поэтому рекомендуется использовать как минимум:
db-scheduler
когда есть Quartz
? Цель db-scheduler
— быть неинвазивным и простым в использовании, но при этом решать проблему персистентности и проблему координации кластера. Первоначально он был нацелен на приложения со скромными схемами баз данных, к которым добавление 11 таблиц было бы немного излишним. Обновление: Кроме того, на данный момент (2024 г.) Quartz, похоже, также не поддерживается активно.
ЦЕЛОВАТЬ. Это наиболее распространенный тип приложений с общим состоянием.
Пожалуйста, создайте проблему с запросом функции, и мы сможем обсудить ее там. Если вы нетерпеливы (или хотите внести свой вклад), запросы на включение приветствуются :)
Да. Он используется в производстве ряда компаний и до сих пор работает без сбоев.