Как построить атомарный рабочий процесс в системе, которая физически распределена и разделена во времени?

Атомарность. Грубое определение может быть «Все или ничего».

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

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

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

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

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

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

В принципе, он работает, получая блокировку для каждого участника в качестве фазы 1. После того, как блокировки получены для всех участников, фаза 2 является фактической фазой фиксации.

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

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

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

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

Как построить атомарный рабочий процесс в системе, которая физически распределена и разделена во времени?

Избегайте блокировок, сохраняйте состояния и внедряйте восстановление — шаблон Saga

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

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

Оригинальная статья Saga находится здесь. Хотя он был написан для операций с базами данных, в нем говорится о прямом, обратном и смешанном восстановлении. Таким образом, актуально для современных микросервисных архитектур.

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

  1. Компенсируемые транзакции. Их можно отменить или отменить, начав компенсирующую транзакцию. Например, неверный счет-фактура, оплаченный клиентом, может быть компенсирован путем выставления кредит-ноты. Ради обсуждения предположим, что сама компенсационная транзакция терпит неудачу, в таких случаях обычно ручное вмешательство может упростить процесс, например, выдача кредит-ноты вручную.
  2. Идемпотентные транзакции. Они не изменяют состояние системы. Следовательно, может выполняться более одного раза. Например, чтение данных, создание отчетов, преобразование заданных данных.
  3. Сводная транзакция. Это суть саги. В случае успеха все последующие транзакции в саге должны быть успешными. Полезно иметь точку сохранения после такой транзакции в саге.

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

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

  1. Восстановление назад/откат — когда возможны компенсационные транзакции, и нам нужно предоставить возможность отмены (логический вид, представленный выше).
  2. Восстановление вперед — при откате Saga нельзя, и рабочий процесс должен быть завершен. Saga может сохранить состояние, чтобы возобновить работу с шага, на котором произошло прерывание, либо повторив попытку, либо выбрав альтернативный блок восстановления, либо ожидая ручного вмешательства.
  3. Смешанный — в Saga некоторые сбои могут потребовать обратного восстановления, в то время как другие могут потребовать прямого восстановления.

Implementing Saga: Поскольку мы говорим о взаимодействии между микросервисами. Одна из возможностей — реализовать взаимодействие с сервисом Orchestrator, таким как AWS Step Function, где каждый смоделированный шаг явно реализован (в исходном коде) с путями отказа.

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

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

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

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

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

Для получения дополнительной информации о хореографии и оркестровке я могу порекомендовать эту прекрасную статью Ян Цуя.

Я надеюсь, что вы нашли запись полезной, и если у вас есть какие-либо отзывы, дайте мне знать.

Что я держу для будущих статей, так это аномалии, которые могут возникнуть в саге, и то, как можно смоделировать сами сервисы, чтобы гарантировать контракт, который они обещают саге.