Как работать с Camunda 7

У camunda слишком много имен, в которых можно запутаться.
На текущий момент существует 2 версии камунды 7 и 8(камунда — это в целом набор разных продуктов, а не единое целое).

Между ними почти нет ничего общего, поэтому их следует различать.

9c4rg_ope3jvasefw7uug9v7n2i.png

В этой статье будут описаны проблемы специфичные в целом для 7 версии, но иногда будут комментарии как проблема решена в 8 версии.

Какие задачи решает Camunda

Camunda — это инструмент оркестрации для бизнес-процессов в bpmn нотации.

Основные возможности camunda это:

  1. Поддержка долго-живущих процессов и их хранение

  2. Планирование и выполнение процессов

  3. Версионирование bpmn-схем. За счет этого система остается в состоянии пригодным для внесения изменений в долгосрочной перспективе

  4. Устойчивость к ошибкам за счет повторного выполнения заданиий (at least once)

  5. Визуализация бизнес-процесса (bpmn-схема при этом является исполняемой и благодоря этому всегда в актуальном состоянии)

  6. История выполнения процессов — упрощение поддержки

Работа с контекстом

Как хранить

Camunda поддерживает несколько форматов для хранения контекста:

  1. JVM сериализация

  2. JSON

  3. XML

Основная проблема с форматом данных — это поддержка обратной совместимости. С JVM сериализацией это делать очень проблематично, поэтому проще брать JSON/XML.

Что в 8-ке

Контекст сохраняется только в JSON формате

Что хранить

Контекст процесса мощный инструмент без которого движок не будет работать.
В контекст не нужно добавлять все подряд, а тем более большие объекты.
Чем дольше живут ваши процессы и чем больше вы храните данных в контексте, тем быстрее вы столкнетесь с проблемами производительности.

Хорошей практикой является использование внешнего хранилища для ваших данных, а в контексте хранить только id.

Внешним хранилищем может быть таже БД, на которой работает движок при условии что данные лежат не в таблицах камунды.

Что в 8-ке

В 8-ке один инстанс процесса может занимать до 4 МБ в целом.

Область видимости переменных

В камунде область видимости переменных делится на глобальные и локальные.
Глобальные чаще всего инициируются из кода (при старте процессов, при корреляции и в делегах).
Это осложняет понимание процесса, так как не видно какими данными оперирует процесс.

Делегаты камунды можно представлять просто как функцию с входящими/исходящими переменными и тогда чтение bpmn-схемы станет проще. Но для этого нужно, чтобы переменные были отражены в самой bpmn схеме.
Это возможно только при использовании локальной области видимости в делегах и маппинга переменных на схеме.

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

Маппинг переменных это необходимая рутина, для ее упрощения работы с переменными в моделере можно использовать шаблоны, а так же библиотеку для делегатов.

Что в 8-ке

В стартовом событии процесса можно добавить маппинг входящих переменных. Это нужно для понимая процесса без кода

Проблемы с корреляцией

Camunda из коробки предлагает API для работы с корреляцией:

  1. Ожидание событий на bpmn-схеме

  2. Корреляция сообщений из bpmn-engine

Типичный сценарий ожидания событий

Типичный сценарий ожидания событий

Race condition

На этой схеме происходит обычная гонка. Между блоками «Добавить задание в очередь» и «Задание выполнено» есть задержка по времени, которая приводит к тому, что в момент уведомления о выполнении задания процесс еще не готов принимать сообщение (возникает ошибка MismatchingMessageCorrelationException).
По этой ошибке нельзя точно понять ждал ли кто-то событие вообще или оно давно не нужно.

Эта проблема даже описана в книге «Practical Process Automation» от авторов Camunda (Глава 9/BPMN and Being Ready to Receive).
В качестве решения этой проблемы в книге предлагается делать следующее:

  1. Перед корреляцией добавить небольшую задержку в несколько сотен миллисекунд (как временное решение)

  2. Использовать буфер для сообщений (готовой реализации в 7-ке нет)

То есть в 7-ке из коробки эта проблема не решается.

Корнем этой проблемы является невозможность достоверно узнать существует ли подписка на событие в момент корреляции.

Решение номер один — это подписку на событие сделать глобальной. В этом случае она будет активна всегда.

В таком сценарии возможна ошибка OptimisticLockingException, которую можно просто ретраить

Подписка на событие активна всегда

Подписка на событие активна всегда

Решение номер два — это отдельная таблица для подписок на события.

Внешнее хранилище подписок

Внешнее хранилище подписок

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

Параметры корреляции

Есть несколько признаков, по которым можно сопоставить событие и процесс ожидающий его:

  1. ID инcтанса процесса

  2. Бизнес-ключ процесса

  3. Сопоставление переменных процесса

Последний вариант нужно исключать сразу, так как он будет сильно бить по перфомансу БД.
Первые два очень скудные и не подойдут для всех интеграций.

Единственным вариантом здесь является использование собственного механизма подписок описанного выше.

Что в 8-ке

Для решения проблемы готовности процесса принимать события был добавлен встроенный буфер сообщений.

Для решения проблемы с параметрами корреляции в подписке теперь нужно явно указывать ключ корреляции.

Выполнение джоб

В камунде два основных способа выполнения пользовательского кода: делегаты и externalTopic.

Делегаты выполняют весь код в рамках одной транзакции (пессимистичная блокировка).
Время работы делегата влияет на пропускную способность системы.
Enterprise мир суров и в некоторых системах задержка одного запроса может доходить до нескольких минут.
Для таких систем использование делегатов противопоказано, так как все начнет упираться в нехватку коннектов к БД.

ExternalTopic работает через оптимистичную блокировку и такие проблемные интеграции нужно делать через этот механизм.
В документации камунды под ExternalTopic обычно понимается какой-то внешний обработчик, но в реальности это делать не обязательно.

Обработчик может находится в том же приложении, что и bpmn-движок (из коробки оно так не работает и нужно написать это с нуля).

В плане выполнения джоб camunda — это очередь поверх БД.
У каждой джобы есть заданное кол-во попыток на выполнение. Блокировка джоб происходит на уровне данных, а не через механизмы в БД (в таблице джоб есть время до которого задание блокируется).
Время блокировки нужно настраивать, чтобы не было ошибочного и параллельного выполнения одной и той же джобы.

В каждом инстансе приложения есть ровно один планировщик заданий, который ищет следующие джобы для выполнения.
Планировщик блокирует пачку джоб для работ и отправляет их в threadExecutor.

Аналогичный механизм нужно реализовать и для externalTopic для обработки таких джоб внутри приложения.

Для БД PostgreSQL есть локальная оптимизация в виде активации флага ensureJobDueDateNotNull, которая немного оптимизирует запрос на поиск джоб для выполнения

Что делать с историей

История камунды полезная фича, которая упрощает поддержку. Но история сильно бьет по перфомансу.
На маленьких объемах история не доставит проблем, но на больших будет очень сильно влиять на производительность.

Для сохранения истории есть паттерн по ее вынесению во внешнее хранилище описанный в статье Как сохранять историю процессов в Camunda без вреда для них.

Это решение не идеальное, но рабочее и дешевое.
Лучшим вариантом будет хранение истории в clickhouse или других хранилищах подходящих под эти цели (это решение более трудоемко).

Нужно так же не забыть:

  1. Выбрать корректный уровень истории под ваши нужды

  2. Настроить ttl в процессах

  3. Включить очистку истории (при использовании внешнего хранилища, очистку можно включить 24/7)

Масштабирование

Если соблюдать рекомендации из этой статьи, то проблемы с перфомансом немного отойдут в сторону, но не исчезнут совсем.
Глобального решения проблемы тут нет, но можно просто скопировать архитектуру из 8-ки.

Одна из оптимизаций, которую там реализовали — это построение кластера из нескольких брокеров.
Тоже самое можно провернуть и с 7-кой, так как неважно на каком брокере будет запущен экземпляр процесса и будет выполняться (у каждого брокера должна быть своя БД).
В случае с 7-кой механизм репликации между брокерами не нужен, так как есть стандартные паттерны репликации БД.

Кластер Camunda 7 + внешнее хранилище истории + excamad = Победа

Кластер Zeebe

Кластер Zeebe

Что не так с Camunda 8

  1. Самая главная проблема — это изменение лицензионной политики. Начиная с версии 8.6 камунда становится платной

  2. Из-за того что все воркеры внешние, то увеличивается задержка времени работы процессы, так как на каждую джобу происходит минимум 2 сетевых запроса (получение джоб воркером и завершение джобы)

  3. Из-за резкого изменения лицензии open source инструменты не успели появиться и навряд ли появятся

Материалы:

Используйте Camunda, если она может решить ваши проблемы, а не создать их.

Аве, оркестрация!

© Habrahabr.ru