Какова наиболее приемлемая стратегия транзакций для микросервисов?

80

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

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

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

Kristof
источник
Просто чтобы быть уверенным, эти микросервисы используются многими клиентами одновременно или только по одному за раз?
Марсель
2
Я пытался задать этот вопрос, независимый от этого, Марсель. Но давайте предположим, что мы используем систему, достаточно большую, чтобы сделать то и другое, и мы хотим иметь архитектуру, которая поддерживает оба.
Кристоф
4
Это плохо сформулированный, но очень важный вопрос. Когда большинство людей думают о «транзакциях», они думают почти исключительно о согласованности, а не об атомарности, изоляции или долговечности транзакций. На самом деле должен быть задан вопрос: «Как создать ACID-совместимую систему с учетом архитектуры микросервисов. Только реализация согласованности, а не остальной части ACID не очень полезна. Если вам не нравятся плохие данные.
Джефф Фишер,
10
Ты всегда можешь попытаться изменить мой вопрос, чтобы сделать его менее "плохо сформулированным", Джефф Фишер.
Кристоф
Этот вопрос и обсуждение тесно связаны с другим вопросом о SO .
Пауло Мерсон

Ответы:

42

Обычный подход состоит в том, чтобы максимально изолировать эти микросервисы - рассматривать их как отдельные элементы. Затем транзакции могут быть разработаны в контексте службы в целом (то есть не являются частью обычных транзакций БД, хотя вы все равно можете иметь транзакции БД внутри службы).

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

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

gbjbaanb
источник
5
Вы предполагаете, что возврат никогда не может потерпеть неудачу.
Санджив Кумар Данги
9
@SanjeevKumarDangi это менее вероятно, и даже если это не удастся, с ним можно легко справиться, поскольку текущий счет и текущий счет находятся под контролем банка.
Gldraphael
30

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

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

Сору
источник
37
Такой подход вполне может привести к объединению всех микросервисов в одно монолитное приложение.
Слава Фомин II
4
Это правильный подход. На самом деле все просто: если вам нужна транзакция между сервисами, ваши сервисы ошибочны: перепроектируйте их! @SlavaFominII то, что вы говорите, верно только в том случае, если вы не знаете, как спроектировать микросервисную систему. Если вы столкнулись с борьбой с микросервисами, не делайте этого, ваш монолит будет лучше, чем плохой микросервисный дизайн. Только тогда, когда вы найдете правильные границы услуг, вы должны разделить монолит на услуги. В противном случае использование микросервисов не является правильным архитектурным выбором, оно просто следует обману.
Франческ Кастельс
@FrancescCastells Что если нашим сервисам действительно требуются транзакции среди других сервисов, вы имеете в виду, что мы должны игнорировать ограниченный контекст и моделировать наши сервисы таким образом, чтобы они заканчивались как единая транзакция? Я новичок в микросервисах, все еще читаю, так что извините за мой вопрос, если это звучит наивно ....: D: D
Бильбо Бэггинс
@BilboBaggins Я имею в виду противоположность игнорирования ограниченного контекста. По определению транзакции происходят в ограниченном контексте, а не в нескольких из них. Поэтому, если вы правильно проектируете свои микросервисы вместе с вашими ограниченными контекстами, тогда ваши транзакции не должны охватывать несколько сервисов. Обратите внимание, что со временем вам понадобятся не транзакции, а лучшая обработка возможной согласованности и надлежащих компенсирующих действий, когда дела идут неправильно.
Франческ Кастельс
Я не понимаю вашу точку зрения, есть ли шанс, что мы можем обсудить это в чате?
Бильбо Бэггинс
17

Я думаю, что если согласованность является сильным требованием в вашем приложении, вы должны спросить себя, являются ли микросервисы лучшим подходом. Как говорит Мартин Фаулер :

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

Но, возможно, в вашем случае, вы можете пожертвовать Последовательность в pos доступности

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

Однако я также спрашиваю себя, существует ли стратегия для распределенных транзакций в микросервисах, но, возможно, цена слишком высока. Я хотел дать вам свои два цента с всегда превосходной статьей Мартина Фаулера и теоремой CAP .

gabrielgiussi
источник
1
В распределенных бизнес-транзакциях согласованность иногда решается путем добавления уровня оркестровки, такого как механизм рабочего процесса или тщательно разработанная очередь. Этот уровень обрабатывает все два этапа фиксации и отката и позволяет микросервисам сосредоточиться на конкретной бизнес-логике. Но возвращаясь к CAP, инвестиции в доступность и согласованность делают производительность жертвой. Как микросервисы сравниваются с множеством разрозненных классов, составляющих ваш бизнес в ООП?
Грег
Вы можете использовать RAFT через микросервисы для обеспечения согласованности FWIW
f0ster
1
+1. Я часто задаюсь вопросом, почему в контексте микросервисов транзакции вообще желательны, если все «вытягивающие» представления данных могут быть реализованы как материализованные представления. Например, если один микросервис реализует дебетование с одного аккаунта, а другой - на другой аккаунт, представления балансов будут рассматривать только пары кредитов и дебетов, где несопоставленные кредиты и дебиты все еще будут в буферах для материализованного представления.
Страж
16

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

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

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

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

Есть продукты, которые могут быть прикреплены к вашей системе, которые заботятся о согласованности. Хорошим примером является механизм бизнес-процессов, который, как правило, в конечном итоге обрабатывает последовательность и использует компенсацию. Другие продукты работают аналогичным образом. Как правило, у вас есть уровень программного обеспечения рядом с клиентом (-ами), который имеет дело с согласованностью и транзакциями и вызывает (микро) сервисы для выполнения фактической бизнес-обработки . Одним из таких продуктов является универсальный JCA-коннектор, который можно использовать с решениями Java EE (для прозрачности: я автор). См. Http://blog.maxant.co.uk/pebble/2015/08/04/1438716480000.html для более подробной информации и более глубокого обсуждения вопросов, поднятых здесь.

Другой способ обработки транзакций и согласованности состоит в том, чтобы превратить вызов микросервиса в вызов чего-либо транзакционного, например очереди сообщений. Возьмите приведенный выше пример записи о продажах / записи заказов - вы можете просто позволить микросервису продаж отправить сообщение в систему заказов, которая фиксируется в той же транзакции, которая записывает продажу в базу данных. Результатом является асинхронное решение, которое очень хорошо масштабируется . Используя такие технологии, как веб-сокеты, вы даже можете обойти проблему блокировки, которая часто связана с расширением асинхронных решений. Дополнительные идеи о таких шаблонах см. В другой моей статье: http://blog.maxant.co.uk/pebble/2015/08/11/1439322480000.html .

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

Муравей Кучера
источник
В то же время я бы сказал, что нужно серьезно подумать о том, чтобы превратить процесс в асинхронный, где каждый шаг процесса полностью транзакционный. Подробности смотрите здесь: blog.maxant.co.uk/pebble/2018/02/18/1518974314273.html
Муравей Кучера
«Сделайте приведенный выше пример записи о продажах / записи о заказах - вы можете просто позволить микросервису по продажам отправить сообщение в систему заказов, которая фиксируется в той же транзакции, которая записывает продажу в базу данных». Не уверен, что то, что вы предлагаете, - это в основном распределенная транзакция, в которой сказано, как бы вы справились со сценарием отката в этом случае? Например, сообщение передается в очередь сообщений, но откатывается на стороне БД.
Саквояй
@sactiw не уверен, мог ли я иметь в виду двухфазную фиксацию, но я бы избегал этого сейчас и вместо этого записывал свои бизнес-данные, а также тот факт, что сообщение необходимо добавить в очередь за одну транзакцию в моей микросервисной БД , «Факт», то есть «команда», затем обрабатывается асинхронно после фиксации транзакции с использованием механизма автоматической повторной попытки. См. Статью блога за 2018-03-10 для примера. Избегайте отката или компенсации в пользу форвардной стратегии, если это возможно, так как ее легче реализовать.
Муравей Кучера
1

В микросервисах есть три способа достижения согласованности между diff. Сервисы:

  1. Оркестровка - один процесс, который управляет транзакцией и откатом между службами.

  2. Хореография - Сервис передает сообщения между собой и, наконец, достигает согласованного состояния.

  3. Гибрид - смешивание двух выше.

Для полного чтения перейдите по ссылке: https://medium.com/capital-one-developers/microservices-when-to-react-vs-orchestrate-c6b18308a14c

techagrammer
источник
1

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

Вариант 1. Избегайте необходимости транзакций, если это возможно

Очевидный и упомянутый ранее, но идеальный, если мы можем справиться с этим. Действительно ли компоненты принадлежат одному и тому же микросервису? Или мы можем изменить систему (ы) так, чтобы транзакция стала ненужной? Возможно, принятие не транзакционности - самая доступная жертва.

Вариант 2: использовать очередь

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

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

Begin transaction
Insert entity
Insert e-mail
Commit transaction

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

Вариант 3: выполняйте внешнюю работу последним, непосредственно перед завершением транзакции

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

Begin transaction
Insert entity
Insert another entity
Make external call
Commit transaction

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

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

Вариант 4: создать вещи в состоянии ожидания

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

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

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

Вариант 5: разрешить микросервису поделиться своим запросом

Ни один из других вариантов не сделает это за вас? Тогда давайте неортодоксально .

В зависимости от компании это может быть неприемлемо. Я знаю. Это неортодоксально. Если это не приемлемо, идите другим путем. Но если это соответствует вашей ситуации, это решает проблему просто и мощно. Это может быть просто самый приемлемый компромисс.

Существует способ превратить запросы из нескольких микросервисов в простую транзакцию с одной базой данных.

Верните запрос, а не выполняйте его.

Begin transaction
Execute our own query
Make external call, receiving a query
Execute received query
Commit transaction

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

Если базы данных, участвующие в транзакции, находятся на одном сервере, это будет обычная транзакция. Если они находятся на разных серверах, это будет распределенная транзакция. Код один и тот же независимо.

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

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

Если компромисс приемлем, такой подход дает нам прямую транзакционность монолитного приложения в микросервисной архитектуре.

Timo
источник
0

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

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

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

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

Вот несколько практических советов о том, как построить распределенную систему.

Западло
источник