Comments 19
Грамотно разложил
Это хорошая статья на мой взгляд. Но у меня возникает вопрос. А что если исключение происходит в компенсирующем событии? Здесь нужно компенсирующее событие для компенсирующего события? Или как это работает?
Во первых, обработчики compensation logic должны быть покрыты тестами и точно отрабатывать.
Во вторых должно быть настроенно retry policy, дабы цепочка не нарушилась из-за кривого подключения к базе и т.п.
В третьих, недошедшие сообщения должны храниться в DLQ и в первую очередь обработаны.
Если есть вероятность что обработчики compensation logic будут переодически отрабатывать с исключениями, то можно сделать один ивент компенсации на всю транзакцию и настроить на него retry policy.
Спасибо за ответ. Идея понятна в принципе. Нр справедливости ради хотелось бы отметить, что retry policy можно настроить и на целевые события, и обойтись без компенсирующих.
Ибо если затык на уровне БД происходит, к примеру из-за таймаутов, допустим в том же методе бронирования, то отмена бронирования будет в 99% случаев обращаться, к примеру, к той же заблокированной таблице (по неизвестным нам причинам).
Только в случае компенсации, у нас позже сработает повторная отмена брони, и пользователю придется вновь проходить весь этап. А в случае без компенсационного метода, мы просто позже проведем попытку бронирования повторно.
Но здесь все сугубо ситуационно. Поэтому конечно для каждой ситуации, требуется предварительный анализ, с решением какой вариант подойдет больше
Во первых, ...
Во вторых, ...
...
В десятых, ...
Понимаете, в чем дело. Вы, как пример, приводите простой код, который корректен только для идеального случая, когда всё идёт как задумано, а когда, вот, начинаются вопросы, "а что, если вот это, или вот то пошло не так", то даёте только общие ответы, которые выглядят просто. Но, если все эти ответы: "во первых, во вторых", и т.п., начать воплощать в вашем простом коде, то в результате на один только код потребуется штуки три таких статьи, и любой, кто на этот код посмотрит, вполне резонно скажет: "- Да ну их на... эти ваши микросервисы".
Я сам с микросервисами работаю уже больше семи лет, и мне вся идея сначала очень понравилась, но навозившись с ними за всё это время я полностью поменял к ним отношение. Право на существование они имеют, да. Если у вас проект, команда, и/или нагрузки масштабов какого-нибудь Netflix. В остальных случаях ну его в пень - выгода от них даже если есть, то и на доли процента не покрывает всего дрочева, связанного с их реализацией (при условии если реализация не полное го*но - но это уже отдельная тема).
И еще вы заблуждаетесь насчет "eventual consistency" и "saga". Потому что "saga" это как раз только "eventual" consistency и есть. Если вам нужна "полная" consistency, то вам нужна не saga, а "2PC" (two-phase commit) с распределенными блокировками, а это настолько сложно в реализации (опять-таки если делать всё правильно), что я даже никогда и не слышал, чтобы кто-то в реальности с этим связывался.
Меня давно интересовал случай, когда в саге упадёт откат одной из транзакций - тогда и "eventual consistency" не удастся достигнуть. Поэтому при работе например с деньгами никак нельзя уйти от two-phase commit или работать только с одной бд (но такое часто бывает невозможно).
Можно (и, как правило, нужно) в дополнение к "saga" и "compensating" использовать еще "transactional outbox". Но, опять-таки, это тоже дополнительное усложнение - вам уже придется не просто брать и отправлять сообщение второму сервису, а встраивать в первый еще какой-то планировщик, который будет по таймеру проверять этот "outbox" на наличие записей и отправлять во второй сервис сообщение до тех пор пока тот явно ответным сообщением не подтвердит, что он его обработал. А тут уже возникают еще новые вопросы, например, как подбирать интервал этого таймера чтобы и задержка в синхронизации данных сервисов была приемлемой, но при этом не перегружать брокер потоком одинаковых сообщений, или, например, следует ли установить какой-то лимит на количество повторных попыток, или нет. И т.д. и т.п.
Как вариант (что, по личному опыту, на самом деле, обычно и делают :) это вообще по сути забить на всё это, списав на то, что подобные ситуации (какой-то сервис, или брокер, или какая-нибудь из БД, или всё вместе, просто взяло и тупо внезапно упало в самый неподходящий момент) они всё-таки нетипичны и, в случае чего, админы вместе с поддержкой руками будут разруливать проблемы типа "продали клиенту несуществующий товар" или "деньги за заказ взяли, а заказ на сборку так и не поступил" и прочие, в случае возникновения таковых.
Вот интересно, а если по какой-то причине оно зависло в каком-то конкретном состоянии по какой-то причине, то можно ли как-то протолкнуть дальше вручную?
Да, такие механизмы есть.
При оркестрации контекст FSM можно хранить, например в MongoDb (masstransit умеет так из коробки). Даже если сам оркестратор умрет, то можно будет продолжить с того места, откуда начали.
При хореографии можно надеятся только на стандартные иструменты с очередьми: DLQ, retry policy и т.п.
А сценарии сложнее линейной последовательности из нескольких шагов MassTransit в Saga научился обрабатывать? Условные переходы, параллельные задачи?
Сложность сценария зависит от задачи и разработчика, а не от библиотеки (особенно это касается хореографии).
Если, вы имеете ввиду, умеет ли такие возможности реализация FSM в Masstransit при оркестрации, то да.
Условия можно прописывать внутри блока When с помощью конструкции If.
Возможно я не понимаю, что вы имете ввиду о "параллейных задачах" в распределенной транзакции, но Вы можите в рамках одного State публиковать несколько разных собыйтий и они буду выполняться паралельно, плюсом masstransit поддерживает из коробки Schedule
и теперь необходимо уже отметить бронь и вернуть деньги пользователю.
отменить
Почему описывая SAGA, большинство авторов стараются взять кейсы, допускающие откат в 100% случаев ?
Опишите обратный кейс. Там, где вы можете словить не техническую, а процессную блокировку в момент отката.
Ну давайте в статье был Заказ, а мы возьмем Возврат.
Клиент оформляет возвратный документ.
Вы
1. Создаёте "приходный одер"
2. Ставите товар на остаток
3. Пытаетесь вернуть деньги.
На этапе "3" возникает ошибка - счёт клиента заблокирован. Нужно всё вернуть.
При попытке откатить транзакцию "2" Вы натыкаетесь на то, что только что возвращенный товар уже оплачен другим клиентом. Вам уже нужно или откатить новую оплату, или пытаться снова пойти вперёд и вернуть деньги за возвращенный товар.
Что в оркестрации, что в хореографии - какие действия системы ?
Это я сейчас на ходу из головы придумал. Просто в тему статьи. Понятно, что именно этот кейс решается сменой последовательности шагов 2 и 3. Но можно назвать и другие кейсы с нарушением консистентности в распределённой транзакции.
Опишите обратный кейс.
Автор уже меня обосрал за "непонимание истинных принципов outbox", хотя практических, (с примерами кода), ответов на ваш вопрос, как и у всех свидетелей микросервисов у него не найдется. Статья, по сути, от очередного Почетного Участника Всех Конференций Архитекторов всех Архитектур :))
Если вдруг очередной Архитектор вдруг решит хотя бы задуматься о том, что ACID не просто так придуман (кстати в те же времена, что и собствено SAGA), и строить учёт на распределённых транзакциях может быть очень больно, уже хорошо.
Так считаю.
Автор уже меня обосрал за "непонимание истинных принципов outbox",
У меня только 1 аккаунт. На ваши коментарии я не отвечал. В личные сообщения вы мне не писали
Почему описывая SAGA, большинство авторов стараются взять кейсы, допускающие откат в 100% случаев ?
В начале статьи написанно:
"В этой статье мы разберём Saga с нуля, простыми словами и на понятных примерах. Материал подойдёт как для первого знакомства с темой, так и в качестве пошагового гайда по её реализации в C#."
Не было цели рассмотреть все подводные камни. Была цель описать работу SAGA так, чтобы она была понятна многим.
Но можно назвать и другие кейсы с нарушением консистентности в распределённой транзакции.
Никогда не спорил, что такие примеры есть. Каждый кейс решается отдельно.
Вы привели пример из ритейла.
В общем я только поэтому и решил немножко подискутировать, так бы пропустил эту статью мимо, она не первая и не последняя.
То, что Вы называете "подводными камнями" - это не камни. Это жесткие, неустранимые ограничения технологии. SAGA - не серебряная пуля. По сути она и не "паттерн" даже. Изначально эта техника разрабатывалась в условиях распределённых баз, соединенных по модему. Когда длинные транзакции прерывались вместе с обрывом канала. Тогда и была предложена технология разрыва транзакций на "пакеты" со сборкой их на получателе по старт-стоп флагам (хореография) или внешним сервисом (оркестрация).
Сейчас эта технология просто всплыла вместе с развитием микросервисов, когда базы начали дробить уже по другой причине.
Ритейл - жёстно зарегулированная область с довольно серьёзными санкциями. Нарушения консистентности баз могут привести к реально серьёзным штрафам, а в случае с "Честным знаком" - и до 171.1 УК довести.
Поэтому нужно иметь очень серьёзные аргументы для построения учета (именно учета, а не фронтовых сервисов) для ритейла на распределенных базах. Делать это с полным пониманием ограничений технологий и связанных с этим рисков.
Об этом в Вашей статье ни слова. И может очень больно прилететь новичкам, поверившим в "чудо".
Всех благ.
Паттерн Saga через MassTransit. Оркестрация vs Хореография