Как мы используем Camunda в Банке ДОМ.РФ

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

Привет, Хабр! Наше подразделение занимается автоматизацией бизнес-процессов обслуживания клиентов в офисах и отделениях банка. Такие бизнес-процессы специфичны для конкретной организации, связаны с соблюдением внутренних регламентов и нормативных документов банка, должны полностью соответствовать часто меняющимся требованиям законодательства и всем инструкциям и правилам ЦБ РФ. Так, например, за процедурой открытия вклада стоит множество взаимодействий различных классов систем и компонентов, начиная от сбора и хранения согласий клиента на обработку персональных данных, интеграций с госсервисами и системами сбора сведений и заканчивая формированием приходно-кассовых ордеров. 

Подобные процессы мы реализуем с помощью движка Camunda, который позволяет прототипировать и визуализировать сложную последовательность шагов и моделировать flow совместно с бизнес-заказчиком, а также обладает множеством инструментов для реализации интеграций, оркестрации, мониторинга, сбора бизнес-метрик и тестирования. + быстро и относительно незатратно менять процессы (можно привести пример с запретом перевода валюты в другие банки). Вдобавок Camunda является open-source решением, что крайне важно в текущих реалиях и т.д. 

Впервые в наше подразделение Camunda попала в рамках закупки некоторого функционала у вендора. Реализация вендора была закрытой, сам движок инкапсулировался в каждый отдельный микросервис и был окружён различными самописными расширениями для централизованной разработки bpmn-процессов в браузере и сбора данных от всех запущенных инстансов. 

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

Архитектура 

Мы провели сравнение Camunda 8 и Camunda 7 по нашим внутренним критериям, проанализировали варианты развёртывания Camunda 7 (https://docs.camunda.org/manual/7.19/introduction/architecture/) и остановились на кластерной модели Camunda 7 с рекомендованной разработчиками Camunda архитектурой развёртывания c использованием удаленного движка, а точнее Camunda Run в качестве workflow-движка. 

Далее мы развернули отдельный инстанс СУБД Postgres только под задачи Camunda, несколько экземпляров Camunda Run (воркеры) в кластере за балансировщиком для исполнения процессов, а также отдельный независимый экземпляр Camunda Run исключительно для задач администрирования и мониторинга. 

Настройки Camunda Run для администрирования и мониторинга процессов: 

camunda.bpm: 

  job-execution: 

    enabled: false  

Настройки узлов Camunda Run (воркеров) за балансировщиком 

camunda.bpm: 

  job-execution: 

    max-wait: 10000 

    backoff-time-in-millis: 50 

    max-backoff: 250 

    backoff-decrease-threshold: 100 

    wait-increase-factor: 2 

 
Из коробки Camunda не умеет работать с Kafka, но большая часть наших процессов и внутренних систем базируются на event-driven паттерне, поэтому после первого этапа выбора архитектуры и развёртывания системы нам было необходимо подружить Camunda с Kafka: 

  • для возможности автоматического запуска определённых бизнес-процессов по мере появления потребности 

  • для запроса действий в других системах в рамках исполнения различных бизнес-задач 

  • для обеспечения интеграционных взаимодействий 

    Укрупнённо план выглядел следующим образом: 

 

Где...  

Микросервисы активации процессов  

  • являются держателями спроектированных bpmn-диаграмм 

  • могут вести учёт и версионирование диаграмм 

  • взаимодействуют с Kafka и умеют соотносить нужный процесс с тем или иным событием 

  • обеспечивают идемпотентность на случай дублей или сбоев 

Микросервисы обработки внешних задач 

  • используются вместо делегатов 

  • обращаются к Camunda с заданной частотой для получения задач 

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

  • могут запускать другие bpmn-процессы (через Kafka и взаимодействия с микросервисами активации процессов) 

  • позволяют извлечь необходимые процессу данные из других систем 

Часть бизнес-процесса открытия вклада, завязанная на асинхронный обмен событиями по Kafka, после этапа прототипирования выглядела следующим образом: 

 

Здесь стоит сказать, что для взаимодействия с Kafka мы используем модель, предложенную Крисом Ричардсоном, 

Асинхронное API наших сервисов состоит из каналов и соответствующих типов сообщений, таких как: 

  • входящие команды, 

  • ответы на командные запросы и 

  • события 

Схема асинхронного API сервиса из книги Ричардсона приведена ниже: 

 

Таким образом, возвращаясь к bpmn-схеме, кубик  «Отправка запроса открытия вклада» должен быть реализован отправкой командного события в топик Kafka. 

Внешняя задача для данного шага была реализована с использованием базового класса обработки задач, наследующего интерфейс библиотеки Camunda  

org.camunda.bpm.client.task.ExternalTaskHandler 

с преобразованием атрибутов объекта ExternalTask в расширенный под наши потребности DTO: 

/** 
* Обобщенное выполнение задачи. 
* 
* @param externalTask        внешняя задача 
* @param externalTaskService сервис внешних задач 
*/ 
@Override 
public void execute(@NonNull final ExternalTask externalTask, 
                    @NonNull final ExternalTaskService externalTaskService) { 
    final TaskBO task = ExternalTaskToTaskBOMapper.EXTERNAL_TASK_TO_TASK_BO_MAPPER.map(externalTask); 

 

Для сквозной и межсистемной трассировки мы передаём в хедеры сообщения в Kafka корреляционный ключ инстанса работающего бизнес-процесса (correlationId), и этот же идентификатор вернётся нам в хедерах reply-события для связки ответа микросервиса открытия вклада с конкретным исполняющимся процессом.  

/** 
* Отправка сообщения в топик  
* 
* @param task    DTO-представление внешней задачи 
* @param message DTO для отправки в топик Kafka 
*/ 
public void sendCommand(@NonNull final TaskBO task, 
                        @NonNull final Object message) { 
    kafkaProducer.sendCommand(task.getBusinessKey(), message, task.getHeaders()); 
} 

 

Кубик получения результата открытия депозита из топика Kafka также реализован внешней задачей. 

Мы реализовали подписку на заданный reply-топик микросервиса открытия депозитов и ожидали в нём появления ответного события по первоначальному командному запросу. 

String processCorrelationId = externalTask.getVariable(CORRELATION_ID); 
correlationIds.add(processCorrelationId); 
executor.execute(() -> { 
    try (KafkaConsumer<String, String> consumer = new KafkaConsumer<>(defaultConsumerProperties)) { 
        consumer.subscribe(Collections.singletonList(externalTask.getVariable(KAFKA_TOPIC))); 
        ... 
    } 
    ... 

Пока инстанс микросервиса обработки внешних задач был один – всё работало отлично, каждое входящее событие из reply-топика Kafka фильтровалось consumer-ом по бизнес-ключу и хедерам, и в случае совпадения значения correlationId в хедере с любым элементом внутреннего списка микросервиса correlationIds, задача успешно завершалась. Однако, как только мы пытались масштабироваться и увеличить количество инстансов микросервисов обработки задач – часть reply-событий для одного инстанса-подписчика попадали в обработку другого инстанса-подписчика, correlationId входящего события отсутствовал во внутреннем списке инстанса correlationIds, и исполнение внешних задач в процессе отваливалось по таймауту. 

Для исправления данной ситуации потребовалось переопределить свойства подписчика и помещать его в уникальную группу для того чтобы каждый инстанс микросервиса исполнения внешних задач получал каждое событие из нашего reply-топика Kafka: 

Properties consumerProperties = new Properties(defConsumerProperties); 
consumerProperties.putAll(defConsumerProperties); 
consumerProperties.put(ConsumerConfig.GROUP_ID_CONFIG, KAFKA_TASK_GROUP_ID + UUID.randomUUID()); 

И далее применить данные свойства к consumer: 

executor.execute(() -> { 
    try (KafkaConsumer<String, String> consumer = new KafkaConsumer<>(consumerProperties)) { 
        String kafkaTopic = externalTask.getVariable(KAFKA_TOPIC); 
        consumer.subscribe(Collections.singletonList(kafkaTopic)); 

 В результате всех перечисленных изысканий укрупнённая схема взаимодействия компонентов при необходимости масштабирования выглядела следующим образом: 

Собственно, данных артефактов и манипуляций было достаточно для реализации и исполнения приближенного к реальному бизнес-процесса открытия вклада и проведения итерации нагрузочного тестирования (для того чтобы убедиться в жизнеспособности схемы под нагрузкой на проде). 

Первая итерация нагрузочного тестирования включала в себя один инстанс воркера (Camunda Run), 1 инстанс сервиса активации процесса и один инстанс сервиса обработки внешних задач и стартовые показатели ресурсов (нужно, наверное, добавить что-нибудь про железо):  

Настройки деплоймента для микросервиса обработки внешних задач 

resources: 

  limits: 

    cpu: '1' 

    memory: 512Mi 

  requests: 

    cpu: '1' 

    memory: 512Mi 

   Настройки деплоймента для микросервиса обработки активации процессов 

resources: 

  limits: 

    cpu: '1' 

    memory: 512Mi 

  requests: 

    cpu: '1' 

    memory: 512Mi  

Настройки деплоймента воркера    

resources: 

  limits: 

    cpu: '4' 

    memory: 2G 

  requests: 

    cpu: '1' 

    memory: 512Mi 

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

Источник: https://habr.com/ru/companies/domrf/articles/754990/


Интересные статьи

Интересные статьи

Протокол WireGuard позволяет создать защищенный сетевой туннель на третьем уровне модели ВОС между двумя узлами с помощью протокола передачи сообщений UDP. Он использует криптографическое квитирование...
Если вы студент, работаете в академической сфере или на производстве, вы, вероятно, сталкивались с ситуациями, когда вам нужно было объединить работу с более чем одного языка программирования. Это обы...
Python 3 существует уже какое-то время и довольно много разработчиков, особенно те, кто только начинает свой путь в Python, уже используют эту версию языка. Несмотря на то, что мн...
На протяжении столетия самым популярным типом излучателей звука являются динамические громкоговорители. Традиционные аналоговые динамики применяются повсеместно. Именно они остаются п...
Ни для кого не секрет, что корпоративные IPS становятся все умнее и умнее. Сейчас уже никого не удивишь IPS с SSL-митмом на периметре сети или даже внутри корпоративной сети между сегментами. В т...