Camunda и распределённые транзакции на примере

Распределенные транзакции в микросервисах могут принести много боли разработчикам. Как за 30 минут сделать распределенные транзакции на Camunda BPM и перестать страдать — читайте в этой статье. Ссылки на github и видео-версия внутри. Статья для программистов, системных аналитиков и архитекторов. ¯ \ _ (ツ) _ / ¯.

Видеоверсия

Что за распределённые транзакции и способы решения

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

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

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

1 Способ решения: 2 phase-commit

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

Распределенные транзакции: 2 phase commit

Распределенные транзакции: 2 phase commit

 

2 Способ решения: Saga

В этом варианте разработчик работает с заданиями, которые можно откатить. Т.е. сначала выполняются все действия, после проверяется что всё ок, и транзакция закрывается. Если произошла ошибка, то выполненные действия откатываются.

Распределенные транзакции: Saga

Распределенные транзакции: Saga

Как правило, 2PC используется для мгновенных транзакций: например в одной сети, а Saga — для длительных: например с участием внешних систем.

Я предпочитаю Saga, особенно в реализации с Camunda, потому что их удобнее модифицировать и они более универсальные.

Пример: перевод средств с одного сервиса в другой в Camunda

Для реализации распределённой транзакции мы воспользуемся BPM движком Camunda. Вот такую схему мы реализуем:

  • Jmeter исполуем для запуска 9000 инстансов
  • Camunda для реализации Saga, вот репозиторий проекта.  Проект собран на базе вот этого поста
  • Microservice 1, Microservice 2 — простое приложение на Kotlin и Javalin.io , которое хранит в оперативной памяти состояние аккаунта. Вот репозиторий.
  • Excamad — дополнительная админка к Camunda, которая позволяет смотреть историю процессов. Вот репозиторий. 

Распределенные транзакции (SAGA) в BPMN

В BPMN SAGA реализуется с помощью символа компенации  и встроенного транзакционного подпроцесса:

Saga паттерн для распределенных транзакций в Camunda

Транзакционный процесс гарантирует, что либо процесс будет выполнен успешно, т.е. дойдет до завершающего события, либо (при срабатывании Cancel Event), будут выполнены все компенсации.

На диаграмме в голубом квадратике 40% транзакций определяются в ошибку (через случайное число), что приводит процесс к Cancel event и запуску компенсаций «Вернуть на 1», и «Списать с 2».

Таким образом мы получаем согласованность в конечном итоге сумме в двух микросервисах.

Запуск и результаты

В итоге, запустив 9000 процессов, я получил такие результаты:

Результат работы распределённой транзакции на первом сервисе

Результат работы распределённой транзакции на втором сервисе

Как вы видите, сумма не равна 9000*50(это размер одного платежа) =450 000, т.е. были ошибочные транзакции. Которые отработали вот так:

Скриншот прохождения процесса распределённой транзакции в Camunda

Скриншот прохождения процесса в Camunda

В итоге

Сделать Saga на Camunda довольно легко. У вас есть задачи распределённых транзакций? Как вы их решаете?

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Комментарии

Вам так же понравится

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: