Flowable Async Executor (также известный как Job Executor) — это ключевой компонент Flowable. По сути, это многократно используемый, автономный компонент, работающий внутри различных движков Flowable и обеспечивающий асинхронное выполнение логики.

В последние несколько недель мы усердно работали над внедрением «следующего поколения» архитектуры Async Executor. Мы провели бенчмарки этой реализации (спойлер: результаты отличные) и с нетерпением хотим поделиться ими с сообществом. Материала много, и этот пост — первый в серии:

  • В первой части, которую вы сейчас читаете, мы объясним назначение Async Executor.

  • Во второй части мы рассмотрим различные подкомпоненты Async Executor и расскажем, как их настраивать.

  • В третьей части мы опубликуем и обсудим результаты бенчмарков с использованием различных концепций и параметров, рассмотренных ранее.

  • И, наконец, в последней части мы рассмотрим эволюцию архитектуры за прошедшие годы и расскажем, как мы пришли к новой улучшенной архитектуре.

Прежде чем углубляться, давайте сначала определим, что мы имеем в виду под «Async Executor».

Назначение Async Executor

Async Executor используется практически в любой реальной BPMN- или CMMN-модели. Будь то пользовательские процессы или крупные (микро)сервисные оркестрации, скорее всего, вы будете использовать Async Executor. Если выше мы намекнули, что завершили более масштабируемую и производительную реализацию… это отличная новость для всех. Более быстрый Async Executor означает более высокую пропускную способность процессов, кейсов, транзакций. А это важно для каждого.

Основная задача Async Executor — забирать задания (jobs) и выполнять их. Чтобы понять, что такое задание, рассмотрим следующую BPMN-модель (принцип тот же для CMMN).

В этой модели есть два элемента, которые используют Async Executor: граничное событие таймера на первой пользовательской задаче (Task A) и сервисная задача (Async Service Task) в середине.

Когда запускается экземпляр этой BPMN-модели, создаётся новая пользовательская задача Task A. На этой задаче есть граничное событие таймера, которое прерывает задачу и переводит процесс по пути эскалации. За кулисами, благодаря этому событию, одновременно с созданием задачи создаётся таймерное задание (timer job).

Async Executor теперь отвечает за следующее:

  • Определить, что таймерное событие должно сработать, когда наступит срок таймера.

  • Асинхронно выполнить прерывание пользовательской задачи и перевести процесс по пути эскалации.

Обнаружение заданий называется в терминологии Flowable «acquiring» (запомните это слово, оно пригодится позже). И выборка, и выполнение заданий происходят асинхронно, отсюда и название Async Executor. В этом примере это значит, что когда срабатывает таймерное событие, задача Task A удаляется, а задача Escalate создаётся в фоновом режиме. Пользователь этого не инициирует — он увидит изменения только после обновления списка задач: старая задача исчезнет, а новая появится.

Если Task A завершается пользователем, а таймер не сработал, экземпляр процесса продолжается и доходит до Async Service Task. Предположим, что для этой задачи установлен флаг async=true.

В этот момент Async Executor:

  • Выполняет логику в сервисной задаче.

  • После успешного завершения задачи процесс продолжается дальше.

Обратите внимание: async не значит, что задача уходит на выполнение, а процесс идет дальше. Процесс стоит и ждет, пока асинхронная задача завершится.
(Прим. переводчика)

Оба действия происходят асинхронно, в фоне. В терминологии Flowable, когда достигается сервисная задача, движок создаёт асинхронное задание — это запрос на асинхронную работу (job), который содержит инструкции для выполнения логики и продолжения экземпляра.

Однако Async Executor сначала должен выбрать (acquire) это задание. После этого оно планируется на выполнение. В этом примере, когда пользователь завершает Task A, список задач будет пуст до тех пор, пока сервисная задача не будет обработана и Async Executor не переведёт процесс в Task B.

Требования к асинхронным заданиям

Представьте, что сервисная задача — это что-то, что занимает время (например, генерация документа на основе введённых данных). Если бы мы не сделали сервисную задачу асинхронной, пользователь бы видел индикатор загрузки, пока выполняется логика и не станет доступна следующая задача. Уже только это делает асинхронность большим плюсом для пользовательского опыта.

Асинхронность полезна не только для моделей с участием людей. Для сервисной оркестрации, когда через BPMN интегрируются несколько сервисов, асинхронные вызовы часто дают прирост производительности. Например, BPMN-модель ниже состоит только из вызовов других систем.

Если сделать эти задачи асинхронными, четыре пути будут выполняться независимо друг от друга, в фоне. Если бы они были синхронными, общее время выполнения было бы равно сумме времени выполнения всех путей. При асинхронном выполнении общее время будет определяться максимальным временем самого медленного пути (при условии идеального параллелизма).

Конечно, если бы требования ограничивались только асинхронным выполнением работы и определением момента срабатывания таймера, было бы достаточно обычного пула потоков и стандартных средств выполнения из JDK.

Однако для сценариев, реализуемых с помощью Flowable, требуется дополнительная функциональность:

  • Задания должны сохраняться и переживать перезапуск: если экземпляр Flowable выходит из строя (например, из-за сбоя системы или по другой причине), задание не должно теряться.

  • Более того, оно должно автоматически подхватываться, когда экземпляр снова становится доступен.

  • В случае сбоев, вызванных как самим Flowable, так и внешними системами, задание должно автоматически повторяться без необходимости ручного вмешательства.

  • Движок Flowable может масштабировать нагрузку за счёт добавления новых экземпляров. Если на одном экземпляре Async Executor не справляется с асинхронной работой, другой экземпляр в кластере должен подхватить это задание и выполнить его. Это означает, что механизм выборки заданий устроен так, чтобы корректно работать в многонодовой конфигурации.

Из этого списка очевидно, что простого пула потоков недостаточно.

Async Executor присутствует в Flowable с самого начала (ещё с первого релиза Activiti, откуда был сделан форк Flowable). На самом деле, разработчики ядра Flowable работают над Async Executor и совершенствуют его уже более десяти лет. За это время архитектура эволюционировала, всегда сохраняя обратную совместимость с предыдущими версиями.

Новая архитектура Async Executor

Понадобилось множество прототипов, бесчисленные часы обсуждений и не меньшее количество экспериментов с бенчмарками, прежде чем мы пришли к текущей реализации. В лучших традициях open source вы уже можете увидеть весь код в следующем pull request:

https://github.com/flowable/flowable-engine/pull/2844/commits

Как и в предыдущих поколениях, нас подталкивали вперёд как сообщество, так и корпоративные пользователи, которые использовали Flowable в очень интересных и требовательных сценариях, что привело к переосмыслению архитектуры. Для нас эта новая архитектура — уже четвёртое поколение Async Executor. О предыдущих поколениях и о том, как мы пришли к текущей реализации, мы расскажем подробнее в третьей части этой серии.

Не раскрывая всех деталей (мы всё же хотим, чтобы вы прочитали следующие части ;-)), новая архитектура в первую очередь направлена на снижение конкуренции и конфликтов за таблицы заданий. А также на поиск дополнительных улучшений по ходу работы. Благодаря внедрению техники, которую мы назвали global acquire lock, нам удалось значительно расширить возможности Async Executor. Следующая схема даёт общее (маркетинговое) представление о реализации:

Пока не беспокойтесь о том, что означают символы справа — мы подробно разберём их в следующей части этой серии. Сейчас достаточно знать, что каждый прямоугольник — это Async Executor, который содержит (слева направо): потоки выборки, внутреннюю очередь и пул потоков для выполнения.

В ходе различных экспериментов мы выяснили, что конкуренция за таблицы была главной причиной снижения производительности, особенно при добавлении дополнительных экземпляров движка Flowable. Часто именно выборка заданий была корнем проблемы. Вкратце (все подробности будут в части 4), новая архитектура построена на использовании global acquire lock, чтобы только один экземпляр мог получать доступ к данным в определённый момент времени. А чтобы потоки выполнения не простаивали без работы — за счёт настройки различных параметров Async Executor, о которых пойдёт речь во второй части — это значительно увеличивает пропускную способность.

Что дальше

В этой первой части серии мы подготовили базу для следующих, более детальных публикаций. Теперь, когда основы разобраны, можно переходить к действительно важным вещам: настройкам и цифрам производительности!

Подробнее

Хотите узнать больше про Async Executor уже сейчас?

Следующие две статьи подробно (технически) объясняют, как работает Async Executor. Они были написаны некоторое время назад, но до сих пор отлично раскрывают многие аспекты использования асинхронных задач в ваших моделях.

Об авторе:

Joram Barrez Principal Software Architect

Ключевой разработчик Flowable с более чем десятилетним опытом работы с open source и построения масштабируемых процессных движков. Сооснователь проекта Activiti (на базе которого создан Flowable), а до этого был участником команды JBoss jBPM.

BPM Developers — про бизнес-процессы: новости, гайды, полезная информация и юмор.

Комментарии (0)