Кварцевые характеристики
Кажется, существует ограничение на количество заданий, которые планировщик Quartz может запускать в секунду. В нашем сценарии у нас около 20 рабочих мест в секунду, работающих в режиме 24x7, и кварц хорошо работал до 10 рабочих мест в секунду (с 100 потоками кварца и 100 пулами подключений к базе данных для поддерживаемого JDBC JobStore), однако, когда мы увеличили его до 20 рабочих мест в секунду, кварц стал очень медленным, и его запущенные рабочие места были очень запоздалыми по сравнению с их фактическим запланированным временем, вызвавшим много промахов и в конечном итоге значительно снижающим общую производительность системы. Интересным фактом является то, что JobExecutionContext.getScheduledFireTime().getTime()
для таких отложенных триггеров составляет 10-20 и еще больше минут после их графического времени.
Сколько заданий планировщик кварца может выполняться в секунду, не влияя на запланированное время выполнения заданий и каково должно быть оптимальное количество кварцевых потоков для такой нагрузки?
Или я что-то пропустил?
Подробная информация о том, чего мы хотим достичь:
У нас есть почти 10 тыс. позиций (категория из 2 или более категорий, в данном случае у нас есть 2 категории), по которым нам нужно выполнить некоторую обработку с заданной частотой, например. 15,30,60... минут, и эти предметы должны обрабатываться с этой частотой с заданным дросселем в минуту. например скажем, в течение 60 минут частота 5 тыс. единиц для каждой категории должна обрабатываться с дроссельной заслонкой по 500 штук в минуту. Таким образом, в идеале эти предметы должны обрабатываться в течение первых 10 (5000/500) минут каждого часа дня с каждой минутой, имея 500 обрабатываемых предметов, которые распределяются равномерно через каждую секунду минуты, поэтому у нас будет около 8- 9 единиц в секунду для одной категории.
Теперь для этого мы использовали Quartz в качестве планировщика, который запускает задания для обработки этих элементов. Однако мы не обрабатываем каждый элемент с помощью метода Job.execute, потому что потребуется 5-50 секунд (усреднение до 30 секунд) на обработку элемента, которая связана с вызовом webservice. Мы скорее нажимаем сообщение для каждой обработки элементов в очереди JMS, а отдельные серверные машины обрабатывают эти задания. Я заметил, что время, затраченное на метод Job.execute, не более 30 миллисекунд.
Сведения о сервере:
Solaris Sparc 64-разрядный сервер с 8/16 ядрами/потоками cpu для планировщика с ОЗУ 16 ГБ, и у нас есть две такие машины в кластере планировщика.
Ответы
Ответ 1
В предыдущем проекте я столкнулся с той же проблемой. В нашем случае кварц выполнил гранулярность секунды. Подсезонное планирование было растянутым, и, как вы наблюдаете, часто повторяются осечки, и система становится ненадежной.
Решил эту проблему, создав 2 уровня планирования: Quartz запланировал задание "набор" из n последовательных заданий. С кластеризованным кварцем это означает, что данный сервер в системе получит это задание "set" для выполнения. Затем n задач в наборе берутся с помощью "микропланировщика": в основном механизм синхронизации, который использовал собственный JDK API для дополнительного времени задания до степени гранулярности 10 мс.
Для обработки отдельных заданий мы использовали проект мастера-мастера, где мастер выполнял запланированную доставку (дросселирование) заданий в многопоточный пул работников.
Если бы мне пришлось сделать это снова сегодня, я бы опирался на ScheduledThreadPoolExecutor для управления "микропланированием". Для вашего случая это выглядело бы примерно так:
ScheduledThreadPoolExecutor scheduledExecutor;
...
scheduledExecutor = new ScheduledThreadPoolExecutor(THREAD_POOL_SIZE);
...
// Evenly spread the execution of a set of tasks over a period of time
public void schedule(Set<Task> taskSet, long timePeriod, TimeUnit timeUnit) {
if (taskSet.isEmpty()) return; // or indicate some failure ...
long period = TimeUnit.MILLISECOND.convert(timePeriod, timeUnit);
long delay = period/taskSet.size();
long accumulativeDelay = 0;
for (Task task:taskSet) {
scheduledExecutor.schedule(task, accumulativeDelay, TimeUnit.MILLISECOND);
accumulativeDelay += delay;
}
}
Это дает вам общее представление о том, как использовать объект JDK для задач микрозаписей. (Отказ от ответственности: вам нужно сделать это надежным для среды prod, например, проверить неудачные задачи, управлять повторами (если поддерживается) и т.д.).
При некоторой настройке тестирования + мы нашли оптимальный баланс между заданиями кварца и количеством заданий в одном запланированном наборе.
Таким образом мы получили 100-процентное увеличение производительности. Полоса пропускания сети была нашим фактическим пределом.
Ответ 2
Прежде всего проверьте Как повысить производительность JDBC-JobStore? в документации по Quartz.
Как вы можете догадаться, есть абсолютная величина и определенная метрика. Все зависит от вашей настройки. Однако здесь несколько советов:
-
20 заданий в секунду - около 100 запросов к базе данных в секунду, включая обновления и блокировку. Это довольно много!
-
Подумайте о распределении вашей настройки Quartz в кластере. Однако, если база данных является узким местом, она вам не поможет. Возможно, TerracottaJobStore
придет на помощь?
-
Имея K
ядра в системе, все меньше K
будет недоиспользовать вашу систему. Если ваши рабочие места интенсивно загружаются, K
в порядке. Если они вызывают внешние веб-сервисы, блокируя или спя, рассмотрите гораздо большие значения. Однако более 100-200 потоков значительно замедлит работу вашей системы из-за переключения контекста.
-
Вы пробовали профилировать? Что делает ваша машина большую часть времени? Можете ли вы отправить дамп потока? Я подозреваю, что производительность низкой базы данных, а не процессора, но зависит от вашего использования.
Ответ 3
Вы должны ограничить количество потоков между n
и n*3
, где n
- количество доступных процессоров. Скручивание большего количества потоков приведет к многому переключению контекста, поскольку большинство из них будут блокироваться большую часть времени.
Что касается рабочих мест в секунду, это действительно зависит от того, как долго выполняются задания и как часто они блокируются для таких операций, как сеть и диск io.
Кроме того, нужно подумать, что, возможно, кварц - это не тот инструмент, который вам нужен. Если вы отправляете 1-2 миллиона рабочих мест в день, вы можете захотеть просмотреть собственное решение. Что вы делаете с 2 миллионами рабочих мест в день?
Еще один вариант, который очень плохо подходит для решения проблемы, но иногда работает... на каком сервере он работает? Это старый сервер? Возможно, это наткнется на плунжер или другие спецификации, и это даст вам дополнительную "умф". Конечно, это не самое лучшее решение, потому что это задерживает проблему, а не адреса, но если вы находитесь в хрусте, это может помочь.
Ответ 4
В ситуациях с большим количеством заданий в секунду убедитесь, что ваш сервер sql использует блокировку строк, а не блокировку таблицы. В mysql это делается с использованием механизма хранения InnoDB, а не для механизма хранения MyISAM по умолчанию, который обеспечивает только блокировку таблицы.
Ответ 5
Принципиально подход к выполнению 1 пункта за один раз обречен и неэффективен, когда вы имеете дело с таким большим количеством вещей, которые нужно сделать за такое короткое время. Вам нужно сгруппировать вещи - предлагаемый подход использования набора заданий, который затем микропланирует каждую отдельную работу, является первым шагом, но это все равно означает выполнение почти ничего почти ничего за работу. Лучше было бы улучшить ваш веб-сервис, чтобы вы могли рассказать ему обрабатывать N элементов за раз, а затем вызывать его с наборами элементов для обработки. И еще лучше избегать делать подобные вещи через webservices и обрабатывать их все внутри базы данных, как множества, к которым подходят базы данных. Любая работа, которая обрабатывает один элемент за один раз, принципиально является нескромной конструкцией.