Транзакции через микросервисы REST?

Скажем, у нас есть микросервисы пользователя, кошелька REST и шлюз API, который склеивает все вместе. Когда Боб регистрируется на нашем веб-сайте, нашему шлюзу API необходимо создать пользователя через микросервис пользователя и кошелек через микросервис Кошелька.

Вот несколько сценариев, в которых все может пойти не так:

  • Создание пользователя Боба: это нормально, мы просто возвращаем сообщение об ошибке Бобу. Мы используем транзакции SQL, поэтому никто никогда не видел Боба в системе. Все хорошо:)

  • Пользователь Bob создан, но до того, как наш Кошелек может быть создан, наш шлюз API сильно сработает. Теперь у нас есть Пользователь без кошелька (непоследовательные данные).

  • Создан пользователь Bob Bob, и когда мы создаем Кошелек, HTTP-соединение падает. Возможно, создание кошелька преуспело, или, возможно, не было.

Какие существуют решения для предотвращения такого несоответствия данных? Существуют ли шаблоны, позволяющие транзакциям охватывать несколько запросов REST? Я прочитал страницу Wikipedia на двухфазном коммите, который, похоже, затрагивает эту проблему, но я не уверен, как применить ее на практике. Эта статья Atomic Distributed Transactions: документ RESTful кажется интересной, хотя я еще не прочитал ее.

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

90
задан Olivier Lalonde 13 мая '15 в 14:27
источник поделиться
8 ответов

Что не имеет смысла:

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

Что даст вам головные боли:

  • EJB с распределенными транзакциями. Это одна из тех вещей, которые работают теоретически, но не на практике. Сейчас я пытаюсь сделать работу с распределенными транзакциями для удаленных EJB в экземплярах JBoss EAP 6.3. Мы говорили о поддержке RedHat в течение нескольких недель, и это еще не сработало.
  • SOAP-сервисы с WS-AT. Это решение с несколькими движущимися частями, мало используемое в промышленности и не поддерживаемое на вашей платформе.
  • Решения для точной фиксации в целом. Я думаю, что протокол 2PC - отличный алгоритм (много лет назад я реализовал его в C с RPC). Для этого требуются комплексные механизмы восстановления отказов с повторами, государственным хранилищем и т.д. Вся сложность скрыта в рамках транзакции (например, JBoss Arjuna). Однако 2PC не подлежит доказательству. Есть ситуации, когда транзакция просто не может быть завершена. Затем вам необходимо определить и исправить несоответствия базы данных вручную. Это может произойти раз в миллион транзакций, если вам повезет, но это может произойти раз в каждые 100 транзакций в зависимости от вашей платформы и сценария.
  • Компенсация транзакций. Для SOAP-сервисов, если у вас есть BPEL-движок, вы можете воспользоваться этой альтернативой, что является лучшей альтернативой для длительных транзакций. Существуют другие платформы/платформы, которые поддерживают компенсацию, но BPEL, вероятно, является наиболее распространенным. Но компенсация также не подлежит подтверждению. Вы все еще можете столкнуться с несоответствиями (= головная боль).

Какая, пожалуй, лучшая альтернатива:

  • Конечная согласованность. Ни ACID-подобные распределенные транзакции, ни компенсирующие транзакции не поддаются проверке, и оба могут привести к несогласованности. Возможная последовательность часто бывает лучше, чем случайная несогласованность, поэтому вы можете создать более надежное решение, используя асинхронную связь. В вашем сценарии, когда Боб регистрируется, шлюз API может отправить сообщение в очередь NewUser, и сразу же ответьте пользователю: "Вы получите электронное письмо, чтобы подтвердить создание учетной записи". Служба обслуживания очереди может обрабатывать сообщение, выполнять изменения базы данных в одной транзакции и отправлять письмо Бобу для уведомления о создании учетной записи.

Но что, если вам нужны синхронные ответы?

  • Переделать микросервисы. Если решение с очередью не работает, потому что пользователь службы сразу нуждается в ответе, я бы предпочел перестроить функциональность пользователя и кошелька для размещения в одной службе (или, по крайней мере, в той же виртуальной машине, чтобы избежать распределенных транзакций). Да, это шаг дальше от микросервисов и ближе к монолиту, но избавит вас от некоторой головной боли.
75
ответ дан Paulo Merson 12 нояб. '15 в 20:59
источник поделиться

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

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

Это потребует, чтобы последний вызов знал об обеих таблицах (так что это можно сделать в одной транзакции JDBC).

В качестве альтернативы вам может понадобиться подумать, почему вы так беспокоитесь о пользователе без кошелька. Вы считаете, что это вызовет проблему? Если это так, возможно, наличие этих отдельных вызовов для отдыха - плохая идея. Если пользователь не должен существовать без кошелька, то вам, вероятно, следует добавить кошелек пользователю (в исходном POST-вызове для создания пользователя).

22
ответ дан Rob Conklin 13 мая '15 в 14:47
источник поделиться

Это классический вопрос, который меня задавали во время интервью в последнее время. Как вызвать несколько веб-сервисов и по-прежнему сохранять некоторую обработку ошибок в середине задачи. Сегодня при высокопроизводительных вычислениях мы избегаем двухфазных коммитов. Я много лет читал статью о так называемой модели "Starbuck" для транзакций: подумайте о процессе заказа, оплаты, подготовки и получения кофе, заказанного в Starbuck... Я упрощаю вещи, но модель с двумя фазами предположим, что весь процесс будет представлять собой единую транзакцию для всех шагов, пока вы не получите кофе. Однако с этой моделью все сотрудники будут ждать и перестать работать, пока вы не получите кофе. Вы видите картину?

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

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

  • Не будьте слишком хороши при определении своих веб-сервисов (я не убеждены в шумихе микро-сервиса, которая происходит в наши дни: тоже многие риски заходят слишком далеко);

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

  • Создайте более интеллектуальные сервисы, чтобы сделать их "доступными" для любого номера раз, обработка с помощью uid или taskid, которая будет следовать за нижним верхом заказа до конца, проверяя бизнес-правила на каждом шаге;

  • Использовать очереди сообщений (JMS или другие) и переадресовывать на обработку ошибок процессоры, которые будут применять операции для "откат", применяя напротив, для работы с асинхронным порядком потребуется какая-то очередь для проверки текущего состояния процесса, поэтому учтите, что

  • В крайнем случае (так как это может не часто случаться), поставьте его в очередь для ручной обработки ошибок.

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

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

Псевдокод веб-службы будет выглядеть следующим образом:
1-Call Account, создайте микросервис, передайте ему некоторую информацию и уникальный идентификатор задачи 1.1. Микросервис для создания учетной записи сначала проверит, была ли эта учетная запись уже создана. Идентификатор задачи связан с записью учетной записи. Микросервис обнаруживает, что учетная запись не существует, поэтому она создает ее и сохраняет идентификатор задачи. ПРИМЕЧАНИЕ: эту услугу можно назвать 2000 раз, она всегда будет выполнять тот же результат. Служба отвечает "квитанцией, которая содержит минимальную информацию для выполнения операции отмены, если это необходимо".
Создание кошелька 2-Call, присвоение ему идентификатора учетной записи и идентификатора задачи. Скажем, условие недействительно и создание кошелька невозможно. Вызов возвращается с ошибкой, но ничего не было создано.
3-Оформитель сообщается об ошибке. Он знает, что ему нужно прервать создание учетной записи, но она не сделает этого сама. Он попросит услугу кошелька сделать это, передав "минимальную квитанцию ​​об отказе", полученную в конце шага 1.
4 - Служба учетной записи считывает расписку отмены и знает, как отменить операцию; квитанция отмены может даже включать информацию о другом микросервисе, который он мог бы назвать собой, чтобы выполнить часть задания. В этой ситуации квитанция отмены может содержать идентификатор учетной записи и, возможно, дополнительную информацию, необходимую для выполнения противоположной операции. В нашем случае, чтобы упростить вещи, скажем, просто удалите учетную запись, используя свой идентификатор учетной записи. 5. Теперь, скажем, веб-служба никогда не получала успех или неудачу (в данном случае), что было выполнено отмену создания учетной записи. Он просто снова вызовет службу отмены аккаунта. И эта услуга должна быть нормальной, потому что ее цель состоит в том, чтобы учетная запись больше не существовала. Поэтому он проверяет, существует ли он и не видит ничего, что могло бы быть сделано для его отмены. Поэтому он возвращает, что операция успешна.
6- Веб-служба возвращает пользователю, что учетная запись не может быть создана.

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

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

Компенсация шаблона транзакции

15
ответ дан user8098437 01 июня '17 в 17:50
источник поделиться

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

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

8
ответ дан mithrandir 04 июня '15 в 10:15
источник поделиться

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

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

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

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

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

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

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

7
ответ дан Robert Moskal 13 мая '15 в 18:08
источник поделиться

Какие существуют решения для предотвращения такого несоответствия данных?

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

Существуют ли шаблоны, позволяющие транзакциям охватывать несколько запросов REST?

Для SOAP (ok, not REST) ​​существует спецификация WS-AT, но никакая служба, которую мне когда-либо приходилось интегрировать, имеет поддержку что. Для REST JBoss что-то в пути. В противном случае "шаблон" должен либо найти продукт, который вы можете подключить к своей архитектуре, либо создать собственное решение (не рекомендуется).

Я опубликовал такой продукт для Java EE: https://github.com/maxant/genericconnector

Согласно документу, на который вы ссылаетесь, есть также шаблон Try-Cancel/Confirm и связанный с ним продукт от Atomikos.

BPEL Двигатели обрабатывают согласованность между удаленными службами с использованием компенсации.

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

Существует много способов "привязки" не транзакционных ресурсов к транзакции:

  • Как вы полагаете, вы можете использовать очередь транзакций, но она будет асинхронной, поэтому, если вы зависите от ответа, она становится беспорядочной.
  • Вы могли бы написать, что вам нужно вызвать службы back end в вашу базу данных, а затем вызвать службы back end с помощью пакета. Опять же, async, так что может стать беспорядочным.
  • Вы можете использовать механизм бизнес-процессов в качестве своего шлюза API для организации микросервисов на задней панели.
  • Вы можете использовать удаленный EJB, как упоминалось в начале, поскольку это поддерживает распределенные транзакции из коробки.

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

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

3
ответ дан Ant Kutschera 08 авг. '15 в 22:17
источник поделиться

Лично мне нравится идея Micro Services, модулей, определенных в прецедентах, но, как ваш вопрос упоминает, у них есть проблемы адаптации для классических предприятий, таких как банки, страхование, телекоммуникации и т.д.

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

Я написал блог о моем предлагаемом решении, может быть, это может помочь вам....

https://mehmetsalgar.wordpress.com/2016/11/05/micro-services-fan-out-transaction-problems-and-solutions-with-spring-bootjboss-and-netflix-eureka/s

1
ответ дан posthumecaver 12 апр. '17 в 9:31
источник поделиться

Почему бы не использовать платформу API Management (APIM), которая поддерживает сценарии/программирование? Таким образом, вы сможете создавать составные услуги в APIM, не нарушая микросервисы. Для этой цели я разработал APIGEE.

-1
ответ дан sra 12 окт. '16 в 22:10
источник поделиться

Другие вопросы по меткам или Задайте вопрос