Как стать автором
Обновить

Микросервисы и данные: Как Saga-паттерн спасает от хаоса транзакций

Уровень сложностиСредний
Время на прочтение7 мин
Количество просмотров10K
Всего голосов 25: ↑22 и ↓3+21
Комментарии41

Комментарии 41

Может есть смысл начать с отказа от веры в святой Грааль микросервисы?

Рекомендация: Для простых саг, где 2-3-4 шага и логика прямая как рельс, хореография может быть элегантным решением

Может в таком случае элегантным решением будет сервис с ACID?!

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

Вы консистентность путаете с персистентностью. Обеспечению консистентности база только мешает.

Обеспечению консистентности база только мешает.

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

В отсутствии такого средства со стороны СУБД приложению приходится заботиться о сохранении согласованности самому приложению. И если вы успели поработать с десктопными/файл-серверными БД на основе файлов (формата dbf, Paradox и иже с ними), то должны были запомнить, какой геморрой это вызывало. Лично для меня Inetrbase с его транзакциями в Delphi стал буквально глотком свежего воздуха после этого геморроя.

Как насчет event sourcing? Люди стараются, работают, а оказывается, всё впустую.

Eventual consistency, упомянутая в тексте, под которым мы тут клавиатуры стираем — тоже пустой звук?

Меня больше интересует, чем база мешает?

Ну и исторический вопрос (стаж у вас вполне подходящий) - не приходилось ли с Клиппером или Парадоксом (ну, или с Фоксом, или даже с C с CodeBase) изворачиваться, чтобы свою, типа, транзакцию замутить: чтобы если с одного счета списалось, то на другой бы обязательно записалось, и наоборот? Если нет - завидую.

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

Что до согласованности в конечном итоге, то согласованность в моменте все равно лучше, Только вот не всегда ее сделать можно: времена Клиппера возвращаются, так сказать, на новом витке спирали.

Но все-таки, чем база мешает-то?

① Меня больше интересует, чем база мешает?

GOTO ⑤

② не приходилось ли с Клиппером или Парадоксом (ну, или с Фоксом, или даже с C с CodeBase) изворачиваться, чтобы свою, типа, транзакцию замутить

Приходилось, конечно, но я даже тогда изобретал не транзакции, а event log.

③ Но вот в бухучете типичную продажу товара с одновременной уплатой НДС (а в 90-е НДС уже был) так не проведешь […]

Почему? Я в основном складские учёты тогда и писал, и не вижу никакой проблемы.

④ согласованность в моменте все равно лучше

Вообще ничем не лучше. Мне лично никогда не требовалась, более того: я вообще всё в последние несколько лет делаю асинхронно (с тех пор, как научился).

⑤ Но все-таки, чем база мешает-то?

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

Тем, что она рано, или поздно, превратится в узкое место.

Вы в предыущем неправильно выбрали форму глагола: правильная - "может когда-нибудь помешать".
Превратится-то оно когда-то, а база помогает сейчас. Или - вообще не превратится. К примеру, если ваш бизнес в типологии Белоусова из 90-х попадает в категорию "крысить" (большинство бизнесов из 90-х в нее попадали), этого не случится никогда. А современные СУБД - они весьма масштабируемые, по жизни на них работают очень крупные предприятия.

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

У меня очень разный опыт, на основании которого я могу сделать только два вывода (не без помощи Декарта и Мёрфи, конечно).

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

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

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

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

Короче, вы тут вбрасываете. Учту в дальнейшей дискуссии, чтобы лишний раз не кормить.

PS И чисто для себя поинтересуюсь - корона не жмёт(вопрос, на самом деле, риторический)?

Есть много вещей которые в любом случае будут большей частью вне единой бд. Те же платежи - мастер-система будет вообще не в контуре инфраструктуры компании. Какая-нибудь система лояльности или tms - time management system.

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

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

Тут становится ещё важнее договориться с бизнесом о альтернативных вариантах обработки и максимально вынести их в конец обработки. Но тут нет отличий от монолитной разработки.

Будет кому-то развлечение на разруливание ситуации руками.

Часто именно так и происходит :)

  • OrderService ловит InventoryUpdateFailed (или PaymentCancelled), ставит статус CANCELLED.

Если два сервиса ловят один и тот же ивент — это не сага. Можно ловить только PaymentCancelled в данном случае.

Если два сервиса ловят один и тот же ивент — это не сага.

Обоснуйте.

Можно ловить только PaymentCancelled в данном случае.

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

Вы не сможете правильно откатить всё назад, если два разных приёмника получили и применили (один успешно, второй нет) один и тот же ивент.

К примеру, если в заказе стои́т «оплата курьеру», то PaymentService вообще не обязан принимать участие в обработке.

А если в заказе стои́т «взорвать бомбу» — то нужны еще сапёры. Я отвечал строго на приведенный в тексте пример.

Вы не сможете правильно откатить всё назад, если два разных приёмника получили и применили (один успешно, второй нет) один и тот же ивент.

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

Я отвечал строго на приведенный в тексте пример.

Да. А я написал про то, что будет, если его чуть-чуть расширить.

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

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

Я использую саги с хореографией для крайне запутанных систем уже примерно восемь лет. И всё отлично (возможно пока, и теперь, когда я невнимательно прочитал текст в интернете — всё поломается).

Откат назад в саге с хореографией — личное дело каждого приёмника […]

Угу. Именно поэтому и нельзя реагировать на один и тот же ивент из разных мест.

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

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

Я не против микросервисов - это красиво и по своему элегантно. Но надо понимать, чего эта красота стоит. Для мега проектов вроде amazon/netflix по другому, наверное, и нельзя: будет больно и сложно. Тут эффект масштаба бизнеса (плюс эффект мега бабла) работает на микросервисы. Но в куче проектов микросервисы это понты. Поначалу - прикольно. Даже MVP выглядят ничего себе. Но когда дело доходит до прода, то начинаются танцы с бубном.

Именно поэтому и нельзя реагировать на один и тот же ивент из разных мест.

Ну почему же? Если компенсирующее действие по факту идемпотентно - например, в нем фиксируется, что оно уже было применено, и повторное применение ничего не делает - то почему бы его не приенить несколько раз?

Но, конечно, компенсирующее действие по жизни может внезапно оказаться фактически не идемпотентным (ну, к примеру, из-за гонки при его параллельном применении) - и тогда всё будет интереснее, да. Так что смысл накладывать ваше ограничение есть. Но смысл его не накладывать есть тоже - я как про это и писал. Диалектика, однако.

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

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

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

PaymentService по-прежнему принимает участие в обработке, тут даже напрямую: у него состояние изменяется на «оплата курьеру»,

Я предполагал другое разделение ответственности: PaymentService занимается чисто приемом платежей. Он сервис простой: попросили оплатить - организовал оплату. И даже состояние счета клиента - не его дело.

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

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

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

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

Это я так криво рассказываю формализм за Lvars — https://www.janestreet.com/tech-talks/abstractions-for-expressive-efficient-parallel-and-distributed-computing/ (как у них это заведено, на Hackage протухло и авторы забили).

Снимки там, или коды — это детали реализации. Что действительно важно, это то, как принимается решение «куда теперь» после такой последовательности:

A → B → [UNDO] A → C → [UNDO] A → REDO???

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

Торадиционно откатывается только верхнее действие. А после действия стирается буфер Redo.

А если действие было тождественно Redo?

Как написать плохой механизм, я знаю :)

А если действие было тождественно Redo?

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

А вы для какой ситуации писали? «Доктор, мне бы очень хотелось поговорить об этом» — тема меня реально интересует.

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

Допишу вот немного кода за еду, и возьмусь, ждите.

Допишу вот немного кода за еду, и возьмусь, ждите.

Спасибо. А вкусно хоть кормят? И питательно ли?

Потолки, конечно, надо белить, но это более-менее бессмысленный трёп. Тут все всё понимают - инженеры, всё-таки. Но цели в жизни разные. У меня большая часть минусов в карме "политика/пропаганда", подозреваю, что и большая часть плюсов ровно за то же самое.

А вкусно хоть кормят? И питательно ли?

Я неприхотлив :)

У меня большая часть минусов в карме […]

У меня вот так.
У меня вот так.

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

Как по мне то CDC паттерн по интересней будет

Судя по Хабру каждый первый в компаниях типа Гугла работает и у них без кучи микросервисов ничего работать не может. Скорее всего у 90 процентов здешних посетителей все данные спокойно в ОЗУ нормального сервера влезут…. Соответственно проблема есть но нашего уровня. Если поднапрячься то все можно спокойно впихнуть в классическую БД и наслаждаться всей прелестью встроенного транзакционного движка.

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

Зачем вам это надо- другой вопрос. .. как вы это на сагах реализовать собираетесь?

Ещё очень плохо что от изобилия постов про микросервисы(зачастую об их проблемах и как их решать) новичку можно показаться что монолиты уже никому давно не нужны. Сам несколько лет назад попался в этоу ловушку. Тперь я 100 раз подумаю прежде чем заикнуться о микросервисах

А компенсирующая транзакция же тоже может сбойнуть, значит на нее надо компенсирующую транзакцию следующего уровня:-)

У меня все чаще складывается впечатление, что программисты уже совсем запутались в поисках серебрянной пули и погоне за хайпом: "Подход Х мертв, да здравствует Y", "Вы не поняли X", "Почему X недооценен", "Почему Y переоценен", "X против Y, что лучше в 2025?". Бессмыслица.

На самом деле, процесс моделирования достаточно прозрачен. Сначала необходимо определить, какие данные нужно обработать и какие состояния они могут принимать. В результате мы получаем машину состояний. Затем выделяем агрегаты, которые определяют границы консистентности данных. Консистетный переход между состояниями может происходить только в рамках одного агрегата. Например, в машине состояний, отвечающей за перевод средств между счетами, агрегатами могут быть "счет" и "перевод". Консистентные переходы для агрегата "перевод" могут включать: перевод запрошен, деньги списаны с отправителя, деньги отправлены получателю, перевод завершен. Переходы между агрегатами являются неконсистентными. Например, если деньги уже списаны со счета отправителя, но статус перевода все еще "перевод запрошен", это является неконсистентным переходом. Границы консистентности определяются в зависимости от конкретного юзкейса: чем менее консистентна система, тем сложнее становится машина состояний (больше промежуточных состояний), но повышается доступность; наоборот, чем более консистентна система, тем проще машина состояний, но снижается доступность.

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

Далее, рассмотрим оркестрацию и хореографию. Как правило, одному процессу соответствует одна машина состояний (например, перевод денег). В процессе работы одной машины состояний может быть активирована другая машина состояний с собственным жизненным циклом (например, проверка подозрительной активности). Когда мы говорим об оркестрации и хореографии, мы имеем в виду не выбор одного из этих направлений, а целый спектр возможностей. Можно реализовать централизованную оркестрацию, выделить службу оркестрации для определенной категории процессов, службу для конкретного процесса или распределить логику оркестрации между различными службами по принципу "поближе к тем данным, которые обслуживаются сервисом". Чем ближе к централизованной оркестрации, тем проще система, но она может оказаться несбалансированной и менее устойчивой при масштабировании. С другой стороны, чем больше децентрализация, тем сложнее становится управление и координация, но повышается устойчивость отдельных частей системы.

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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации