Quartz net что это
Дополнительные статьи
Действия по расписанию и Quartz.NET
Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core
Quartz.NET представляет открытый фреймворк для выполнения действий по расписанию в среде ASP.NET. Используем его в своем приложении. Для начала нам надо добавить Nuget-пакет, который называется по имени фреймворка:
Что надо для работы с Quartz.NET? Надо, во-первых, определить работу, которая будет выполняться. И, во-вторых, надо определить триггер, который будет управлять выполнением работы, запускать ее, конфигурировать.
В зависимости от почтового сервера настройки объекта SmtpClient, который отправляет письмо, могут различаться.
Следующим шагом идет создание и конфигурация триггера, который запускает работу. Здесь возможны различные опции. Но в данном случае указывается, что триггер будет называться «trigger1» (произвольное название) и будет принадлежать к группе «group1». Он будет запускать работу сразу после начала выполнения и будет выполнять ее раз в минуту. То есть пользователю будут раз в минуту приходить письма от администратора. И эти действия будут повторяться бесконечно.
И в конце происходит запуск всего действия:
Несколько слов по поводу возможных настроек отправки. Вместо минутного интервала выполнения мы можем задать еще ряд других:
WithInterval(milliseconds) : интервал выполнения в миллисекундах
WithIntervalInSeconds(seconds) : интервал в секундах
WithIntervalInHours(hours) : интервал в часах
WithRepeatCount(number) : определяет количество повторов
StartNow() : запуск сразу же после начала выполнения
StartAt() : определяет время, когда триггер начинает запускать работу
EndAt : определяет время, когда триггер перестает запускать работу
И чтобы работа начала выполняться по расписанию со стартом приложения, изменим класс Global.asax.cs:
И после старта приложения раз в минуту начнут отправляться письма.
Как запустить фоновый процесс в Asp.net
Мне понадобилось запустить фоновый процесс в ASP.NET. Возник вопрос: как лучше это сделать? Немного погуглив в блоге SCOTT HANSELMAN, я нашел запись «How to run Background Tasks in ASP.NET». Статья не очень новая – 2014 года, но вполне актуальная, поэтому я решил перевести ее на русский язык.
Несколько лет назад Phil Haack написал великолепную статью об опасностях выполнения фоновых задач в ASP.NET. Он выделил три основных риска, связанных с запуском фонового процесса:
Есть множество отличных вариантов запуска задач в фоновом режиме. И не просто абстрактных методик, а готовых библиотек.
Какие-то ASP.NET приложения могут работать на Ваших собственных серверах под IIS, какие-то размещаться в Azure.
Для запуска фоновых задач можно выделить несколько вариантов:
WEBBACKGROUNDER
Как написано на сайте: «WebBackgrounder — это проверка концепции совместимого с веб-фермами менеджера фоновых задач, который должен работать только с веб-приложениями ASP.NET». Его код не менялся уже много лет, но NuGet пакет был скачан более полумиллиона раз.
Проект позволяет работать только с одной задачей, управлять повторяющимися задачам в фоновом режиме во время работы веб-приложения.
Это явно библиотека не для всех случаев жизни. Но если во время работы ASP.NET приложения необходимо запускать всего одну задачу, то WebBackgrounder — все что Вам нужно.
QBWI управляет по расписанию задачами, которые должны запускаться в фоновом режиме, независимо от любого запроса. Разница с обычным элементом ThreadPool заключается в том, что ASP.NET автоматически отслеживает сколько рабочих элементов, зарегистрированных с помощью API, запущено в настоящий момент, а в случае выключения домена приложения среда выполнения ASP.NET пытается отсрочить его, давая возможность завершить работу запущенным фоновым задачам.
Необходимо учитывать, что среда выполнения ASP.NET может отсрочить выключение домена приложения всего на 90 секунд, давая возможность завершить задачи. Поэтому, если Вы не можете завершить задачи за это время, то вам необходимо использовать другие, более надежные средства.
API очень простое — используется «Func ». Далее небольшой пример, который запускает фоновый процесс из контроллера MVC:
FLUENTSCHEDULER
FluentScheduler — более продвинутый и сложный менеджер задач с fluent интерфейсом. С его помощью вы действительно можете управлять процессом запуска задачи.
Также FluentScheduler поддерживает IoC и может легко подключаться с помощью Вашей любимой Dependency Injection библиотеки, просто реализуя его ITaskFactory интерфейс.
Для обработки исключений, возникающих в фоновых задачах, можно использовать событие JobException объекта JobManager. Событие позволяет получить информацию о возникшем в задаче исключении.
QUARTZ.NET
Для запуска менеджера внутри Application_Start необходимо вызвать JobScheduler.Start(). Для начала работы можно прочитать Действия по расписанию и Quartz.NET (перевод Scheduled Tasks In ASP.NET With Quartz.Net).
Проект имеет довольно приличную документацию, которую стоит прочитать перед началом использования (как минимум, стоит просмотреть Tutorials for Developing with Quartz).
HANGFIRE
И последний в обзоре, но, безусловно, непоследний по функциональности, наиболее продвинутый из всех Hangfire. Это действительно очень продвинутый фреймворк для фоновых задач в ASP.NET. Опционально он может использовать Redis, SQL Server, SQL Azure, MSMQ или RabbitMQ для повышения надежности выполнения задач.
Документация Hangfire действительно превосходна. Хотелось бы, чтобы каждый проект с открытым исходным кодом имел такую документацию.
Одной из самых эффектных функций Hangfire является встроенная аналитическая панель, которая позволяет просматривать расписания, выполняющиеся задания, успешные и неуспешно завершенные задания.
Hangfire позволяет легко определить задачи типа «запустить и забыть», информация о которых будет храниться в базе данных:
Можно отсрочить выполнение задачи:
Или запустить задачу в CRON стиле
Работать с Hangfire очень удобно. Hangfire имеет хорошую документацию и обучающие руководства, основанные на реальных примерах.
Hangfire — это целая экосистема для работы с фоновыми задачами в ASP.NET приложениях.
Библиотеки доступны в виде открытых исходных кодов или Nuget пакетов.
Итоги (лично от себя)
Выбирая библиотеку для себя, я, с одной стороны, хочу иметь приличную функциональность, а с другой, пока не хочу использовать, например, базу данных для хранения информации о запущенных задачах. Поэтому простые решения типа WebBackgrounder или QueueBackgroundWorkItem я даже не стал рассматривать.
Я уже знаю, что мне нужно запускать больше одного процесса, и работать процессы могут долго (ограничение в 90 секунд на завершение в QueueBackgroundWorkItem). FluentScheduler выглядит неплохо, но хотелось большего. Hangfire – отличное решение, но, вроде, сразу требует использования базы данных для хранения очереди задач. Да и не совсем там все бесплатно – есть и платная версия.
В итоге я пока выбрал Quartz.NET: вполне приличная документация, достаточное количество примеров, чтобы быстро начать использовать, а также расширяемый функционал, который можно добавлять по мере возрастания потребностей.
Если вы знаете другие библиотеки для запуска фоновых задач или имеете опыт решения подобных задач – делитесь в комментариях.
Изображение с hangfire.io
Принципы работы
Эта задача будет сериализована вместе со значениями входных параметров и сохранена в БД:
Данной информации достаточно, чтобы вызвать метод MethodToRun в отдельном процессе через Reflection, при условии доступа к сборке HangClient, в которой он объявлен. Естественно, совершенно необязательно держать код для фонового выполнения в одной сборке с клиентом, в общем случае схема зависимостей такая:
Клиент и сервер должны иметь доступ к общей сборке, при этом для встроенного веб-интерфейса (о нем чуть ниже) доступ необязателен. При необходимости возможно заменить реализацию уже сохраненной в БД задачи — путем замены сборки, на которую ссылается приложение-сервер. Это удобно для повторяемых по расписанию задач, но, конечно же, работает при условии полного совпадения контракта MethodToRun в старой и новой сборках. Единственное ограничение на метод — наличие public модификатора.
Необходимо создать объект и вызвать его метод? Hangfire сделает это за нас:
И даже получит экземпляр EmailSender через DI-контейнер при необходимости.
Развернуть сервер (например в отдельном Windows Service) проще некуда:
После старта сервиса наш Hangfire-сервер начнет подтягивать задачи из БД и выполнять их.
Необязательным для использования, но полезным и очень приятным является встроенный web dashboard, позволяющий управлять обработкой задач:
Внутренности и возможности Hangfire-сервера
Прежде всего, сервер содержит свой пул потоков, реализованный через Task Parallel Library. А в основе лежит всем известный Task.WaitAll (см. класс BackgroundProcessingServer).
Горизонтальное масштабирование? Web Farm? Web Garden? Поддерживается:
You don’t want to consume additional Thread Pool threads with background processing – Hangfire Server uses custom, separate and limited thread pool.
You are using Web Farm or Web Garden and don’t want to face with synchronization issues – Hangfire Server is Web Garden/Web Farm friendly by default.
Мы можем создать произвольное количество Hangfire-серверов и не думать об их синхронизации — Hangfire гарантирует, что одна задача будет выполнена одним и только одним сервером. Пример реализации — использование sp_getapplock (см. класс SqlServerDistributedLock).
Как уже отмечалось, Hangfire-сервер не требователен к процессу-хосту и может быть развернут где угодно от Console App до Azure Web Site. Однако, он не всемогущ, поэтому при хостинге в ASP.NET следует учитывать ряд общих особенностей IIS, таких как process recycling, авто-старт (startMode=«AlwaysRunning» ) и т.п. Впрочем, документация планировщика предоставляет исчерпывающую информацию и на этот случай.
Кстати! Не могу не отметить качество документации — оно выше всяких похвал и находится где-то в районе идеального. Исходный код Hangfire окрыт и качественно оформлен, нет никаких препятствий к тому, чтобы поднять локальный сервер и походить по коду отладчиком.
Повторяемые и отложенные задачи
Hangfire позволяет создавать повторяемые задачи с минимальным интервалом в минуту:
Запустить задачу вручную или удалить:
Отложить выполнение задачи:
Создание повторяющейся И отложенной задачи возможно при помощи CRON expressions (поддержка реализована через проект NCrontab). К примеру, следующая задача будет выполняться каждый день в 2:15 ночи:
Микрообзор Quartz.NET
Класс-задача в Quartz.NET должен реализовывать интерфейс IJob:
Основные характеристики двух рассмотренных планировщиков сведены в таблицу:
Характеристика | Hangfire | Quartz.NET |
---|---|---|
Неограниченное количество клиентов и серверов | Да | Да |
Исходный код | github.com/HangfireIO | github.com/quartznet/quartznet |
NuGet-пакет | Hangfire | Quartz |
Лицензия | LGPL v3 | Apache License 2.0 |
Где хостим | Web, Windows, Azure | Web, Windows, Azure |
Хранилище задач | SQL Server (по-умолчанию), ряд СУБД через расширения, Redis (в платной версии) | In-memory, ряд БД (SQL Server, MySQL, Oracle. ) |
Реализация многопоточности | TPL | Thread, Monitor |
Web-интерфейс | Да | Нет. Планируется в будущих версиях. |
Отложенные задачи | Да | Да |
Повторяемые задачи | Да (минимальный интервал 1 минута) | Да (минимальный интервал 1 миллисекунда) |
Cron Expressions | Да | Да |
UPDATE: Как справедливо заметил ShurikEv в комментариях, web-interface для Quartz.NET существует: github.com/guryanovev/CrystalQuartz
Про (не)нагрузочное тестирование
Необходимо было проверить, как справится Hangfire с большим количеством задач. Сказано-сделано, и я написал простейшего клиента, добавляющего задачи с интервалом в 0,2 с. Каждая задача записывает строку с отладочной информацией в БД. Поставив на клиенте ограничение в 100К задач, я запустил 2 экземпляра клиента и один сервер, причем сервер — с профайлером (dotMemory). Спустя 6 часов, меня уже ожидало 200К успешно выполненных задач в Hangfire и 200К добавленных строк в БД. На скриншоте приведены результаты профилирования — 2 снимка состояния памяти «до» и «после» выполнения:
На следующих этапах работало уже 20 процессов-клиентов и 20 процессов-серверов, а время выполнения задачи было увеличено и стало случайной величиной. Вот только на Hangfire это не отражалось вообще никак:
Фреймворк Quartz: решаем вопрос запуска задач
Представим достаточно популярную задачу – утилизацию вычислительных ресурсов в дни с минимальной нагрузкой. Допустим, у нас есть какой-то сайт, с которым обычно работают по будням. И есть тяжёлые фоновые задачи, которые хотим запускать в выходные, чтобы не пересекаться с клиентами.
Но тут возникает небольшая проблема – выходные в РФ совсем не ограничиваются субботой и воскресеньем. Да и суббота и воскресенье не всегда могут быть выходными.
Попробуем решить эту задачу с помощью Quartz – одной из самых навороченных библиотек для запуска задач по расписанию и, конечно, Spring.
Естественно, мы напишем приложение на Spring Boot. Создадим его с помощью Spring Initializr.
pom.xml:
Надо найти одновременно простое, полное и поддерживаемое API для получения списка выходных дней. Воспользуемся не самым простым для интеграции (это XML) http://xmlcalendar.ru/, но и не самым сложным с точки зрения полноты данных.
Данное API возвращает XML следующего вида:
src/test/resources/calendar.xml:
Немножко разобравшись с форматом ответа, получается следующий алгоритм определения выходного дня:
Напишем с помощью Jackson маппер данного XML. Итак, для начала настроим Jackson:
pom.xml:
src/main/java/ru/otus/springquartzexample/config/ApplicationConfig.java:
Напишем классы для элементов:
src/main/java/ru/otus/springquartzexample/xmlcalendar/CalendarElement.java:
src/main/ru/otus/springquartzexample/xmlcalendar/DayElement.java:
Да, не удивляйтесь, что здесь присутствуют аннотации для Json. Всё-таки, это Jackson.
Чтобы убедиться, что мы всё сделали правильно, напишем небольшой тест:
src/main/java/ru/otus/springquartzexample/xmlcalendar/CalendarElementTest.java:
Ну и напишем клиента, который получает соответствующий календарь:
И куда без теста:
Теперь пришло время написать нашу бизнес-логику. Для приличия создадим интерфейс сервиса:
src/main/java/ru/otus/springquartzexample/service/RussianHolidaysService.java:
Но неприлично реализуем его прямо в клиенте:
Ну и куда без тестов:
Здесь мы не будем рассматривать особенности кэширования вызовов методов, хотя со Spring можно сделать кэширование очень просто.
Пришло время разобраться c Quartz
Начиная со Spring Boot 2.0, для него существует отдельный стартер:
Главная сущность, которую мы хотели:
src/main/java/ru/otus/springquartzexample/quartz/VeryHardJob.java:
Quartz — очень сложный фреймворк. Он хранит выполненные работы в БД и для того, чтобы его настроить, необходимо создать множество бинов, один из которых главный – Quartz Sheduler.
Spring Boot спасает нас от подобной необходимости, имеет настроенные бины, включая хранение в памяти данных работ.
Поэтому мы просто настроим только несколько бинов, а благодаря автоконфигурации они будут использованы.
src/main/java/ru/otus/springquartzexample/quartz/RussianHolidaysQuartzCalendar.java:
Запустив это в будний день, мы увидим:
Подробности реализации этой задачи вы можете посмотреть здесь.
Quartz в ASP.NET Core
Вступление
Знаю, что на эту тему есть очень много статей и своего рода туториоалов, я уже и не говорю об официальной документации, но при работе над своим последним проектом я столкнулся с очень занятной проблемой, о которой мало где говорится. Речь сегодня пойдет о проблеме использования Dependency Injection и Quartz в проекте на платформе ASP.NET Core.
Началось всё с того, что я не думал, что могут возникнуть какие-то проблемы и скажу сразу, что пробовал использовать различные подходы: добавлял все классы, которые включал в себя Quartz в services и юзать их через DI — мимо (но не полностью, как потом оказалось), пробовал добавить HostedService — тоже не работало (в конце прикреплю несколько хороших ссылок на полезные статьи о работе с Quartz) и так далее. Я уже думал, что у меня проблема с триггером — тоже нет. В этой короткой статье я попытаюсь помочь тем, у кого, возможно, была такая же проблема и надеюсь мое решение поможет им в дальнейшей работе. Под конец вступления хочу добавить, что буду весьма признателен если в комментариях те, кто хорошо знаком с технологией, дадут несколько советов, которые помогут улучшить то, что я предложил.
Quartz
Создадим проект (или возьмём готовый — неважно) и добавим в него две папки и несколько классов:
В интерфейсе IEmailSender, который будет служить примером, создадим один метод для отправки писем на почту:
Теперь опишем класс, который будет реализовывать этот интерфейс:
Теперь опишем классы DataJob.cs, DataScheduler.cs, JobFactory.cs. Класс DataJob будет реализовывать интерфейс IJob.
Как видим у нас поле типа IServiceScopeFactory, отдда мы будем доставать сервисы напрямую из Startup. Именно этот подход помог решить мне мою проблему, идём далее и опишем клас DataScheduler в котором будем в Sheduler самого кварца добавлять job и trigger:
И теперь клас JobFactory, который реализовывает интерфейс IJobFactory:
Как видим, я, фактически, все зависимости получаю сразу напрямую из serviceScopeFactory. Всё почти готово, осталось изменить класс Program:
И добавить в Startup в метод ConfigureServices следующее:
Готово. Теперь при запуске приложение мы создаем задачу, которая будет срабатывать каждую минуту. Значение можно поменять в DataScheduler.Start (также можно указывать в секундах, часах или использовать CRON). Для каждой новой задачи при таком подходе нужно создавать новый клас, который будет реализовывать IJob и прописывать новую задачу DataScheduler. Также можно и создавать отдельный Scheduler клас для новой задачи.
Буду очень рад, если смог кому-то помочь, а вот пару полезных статей о Quartz и его использовании: