Допустим, у нас есть микросервисы User, Wallet REST и API-шлюз, который склеивает вещи. Когда Боб регистрируется на нашем веб-сайте, нашему API-шлюзу необходимо создать пользователя через микросервис User и кошелек через микросервис Wallet.
Теперь вот несколько сценариев, где что-то может пойти не так:
Не удалось создать пользователя Боба: все в порядке, мы просто возвращаем сообщение об ошибке Бобу. Мы используем транзакции SQL, поэтому никто никогда не видел Боба в системе. Все хорошо :)
Пользователь Bob создан, но до того, как наш кошелек может быть создан, наш шлюз API сильно падает. Теперь у нас есть пользователь без кошелька (противоречивые данные).
Пользователь Bob создан, и когда мы создаем кошелек, HTTP-соединение разрывается. Возможно, создание кошелька прошло успешно, а может и нет.
Какие решения доступны для предотвращения такого несоответствия данных? Существуют ли шаблоны, позволяющие транзакциям охватывать несколько запросов REST? Я читал страницу Википедии о двухфазной фиксации, которая, кажется, затрагивает эту проблему, но я не уверен, как применить ее на практике. Эта Атомная Распределенная Транзакция: документ о дизайне RESTful также кажется интересным, хотя я еще не читал его.
Кроме того, я знаю, что REST может просто не подходить для этого варианта использования. Возможно ли правильный способ справиться с этой ситуацией, чтобы полностью отбросить REST и использовать другой протокол связи, такой как система очереди сообщений? Или я должен обеспечить согласованность в коде моего приложения (например, с помощью фонового задания, которое обнаруживает несоответствия и исправляет их, или с помощью атрибута «состояние» в моей модели пользователя с «созданием», «созданием» значений и т. Д.)?
источник
Ответы:
Что не имеет смысла:
Что даст вам головную боль:
Что, вероятно, лучшая альтернатива:
Но что, если вам нужны синхронные ответы?
источник
Это классический вопрос, который мне недавно задали во время интервью. Как вызывать несколько веб-сервисов и при этом сохранять обработку ошибок в середине задачи. Сегодня в высокопроизводительных вычислениях мы избегаем двухфазных коммитов. Много лет назад я читал статью о том, что называлось «моделью Starbuck» для транзакций: подумайте о процессе заказа, оплаты, приготовления и получения кофе, который вы заказываете в Starbuck ... Я упрощаю вещи, но двухфазная модель фиксации могла бы Предположим, что весь процесс будет представлять собой одну транзакцию упаковки для всех этапов, пока вы не получите свой кофе. Однако с этой моделью все сотрудники будут ждать и прекращать работать, пока вы не получите свой кофе. Ты видишь картинку?
Вместо этого «модель Starbuck» более продуктивна, следуя модели «максимальных усилий» и компенсируя ошибки в процессе. Во-первых, они уверены, что вы платите! Затем есть очереди сообщений с вашим заказом, прикрепленным к чашке. Если что-то пойдет не так в процессе, например, вы не получили свой кофе, это не то, что вы заказали и т. Д., Мы вступаем в процесс компенсации и гарантируем, что вы получите то, что хотите, или вернете вам деньги. Это наиболее эффективная модель. для повышения производительности.
Иногда Starbuck тратит кофе, но в целом процесс эффективен. Есть и другие хитрости, о которых нужно подумать, когда вы создаете свои веб-сервисы, например, проектирование их таким образом, чтобы их можно было вызывать любое количество раз и при этом обеспечивать одинаковый конечный результат. Итак, моя рекомендация:
Не будьте слишком хороши при определении ваших веб-сервисов (я не уверен, что шумиха вокруг микро-сервисов происходит в наши дни: слишком много рисков зайти слишком далеко);
Async повышает производительность, поэтому предпочитайте быть асинхронными, отправляйте уведомления по электронной почте, когда это возможно.
Создавайте более интеллектуальные сервисы, чтобы их можно было «вызывать» любое количество раз, обрабатывая их с помощью идентификатора пользователя или задачи, которые будут следовать порядку снизу вверх до конца, проверяя бизнес-правила на каждом этапе;
Используйте очереди сообщений (JMS или другие) и переключайтесь на процессоры обработки ошибок, которые будут применять операции для «отката», применяя противоположные операции. Кстати, для работы с асинхронным порядком потребуется какая-то очередь для проверки текущего состояния процесса, так что подумай;
В крайнем случае (поскольку это может случаться не часто), поместите его в очередь для ручной обработки ошибок.
Давайте вернемся к первоначальной проблеме, которая была опубликована. Создайте аккаунт, создайте кошелек и убедитесь, что все сделано.
Скажем, веб-сервис призван организовать всю операцию.
Псевдокод веб-сервиса будет выглядеть так:
Позвоните в микросервис создания учетной записи, передайте ему некоторую информацию и какой-то уникальный идентификатор задачи. 1.1 Микросервис создания учетной записи сначала проверит, была ли эта учетная запись уже создана. Идентификатор задачи связан с записью учетной записи. Микросервис обнаруживает, что учетная запись не существует, поэтому создает ее и сохраняет идентификатор задачи. ПРИМЕЧАНИЕ: эту услугу можно вызывать 2000 раз, она всегда будет выполнять один и тот же результат. Служба отвечает «квитанцией, которая содержит минимальную информацию для выполнения операции отмены при необходимости».
Вызовите создание кошелька, присвоив ему идентификатор учетной записи и идентификатор задачи. Допустим, условие недействительно и создание кошелька не может быть выполнено. Вызов возвращается с ошибкой, но ничего не было создано.
Оркестр проинформирован об ошибке. Он знает, что должен прервать создание Учетной записи, но сам не сделает этого. Он попросит службу кошелька сделать это, передав свою «минимальную квитанцию об отмене», полученную в конце шага 1.
Служба Account считывает квитанцию об отмене и знает, как отменить операцию; квитанция об отмене может даже включать информацию о другом микросервисе, который он мог бы вызвать для выполнения части работы. В этой ситуации квитанция об отмене может содержать идентификатор учетной записи и, возможно, некоторую дополнительную информацию, необходимую для выполнения противоположной операции. В нашем случае, для упрощения, скажем, просто удалите учетную запись, используя ее идентификатор.
Теперь предположим, что веб-сервис никогда не получал успех (или неудачу) (в данном случае), что отмена создания учетной записи была выполнена. Он просто снова вызовет сервис отмены аккаунта. И этот сервис обычно не должен выходить из строя, поскольку его целью является отсутствие учетной записи. Таким образом, он проверяет, существует ли он, и видит, что ничего нельзя сделать, чтобы отменить его. Так что возвращается, что операция прошла успешно.
Веб-сервис возвращает пользователю, что учетная запись не может быть создана.
Это синхронный пример. Мы могли бы справиться с этим по-другому и поместить дело в очередь сообщений, предназначенную для службы поддержки, если мы не хотим, чтобы система полностью исправила ошибку ". Я видел, как это выполняется в компании, где недостаточно для серверной системы могут быть предоставлены хуки для исправления ситуаций. Служба поддержки получала сообщения, содержащие информацию о том, что было выполнено успешно, и имела достаточно информации для исправления таких вещей, которые можно было бы использовать для полной отмены получения квитанции об отмене.
Я выполнил поиск, и на веб-сайте Microsoft есть описание шаблона для этого подхода. Это называется моделью компенсирующей транзакции:
Компенсирующая схема транзакции
источник
Все распределенные системы имеют проблемы с транзакционной согласованностью. Лучший способ сделать это, как вы сказали, иметь двухэтапную фиксацию. Пусть кошелек и пользователь будут созданы в состоянии ожидания. После его создания сделайте отдельный звонок для активации пользователя.
Этот последний вызов должен быть безопасно повторяем (в случае, если ваше соединение обрывается).
Это потребует, чтобы последний вызов знал об обеих таблицах (чтобы это можно было сделать за одну транзакцию JDBC).
В качестве альтернативы вы можете подумать о том, почему вы так беспокоитесь о пользователе без кошелька. Верите ли вы, что это вызовет проблемы? Если это так, может быть, иметь их в качестве отдельных вызовов для отдыха - плохая идея. Если пользователь не должен существовать без кошелька, то вам, вероятно, следует добавить кошелек пользователю (в исходном вызове POST для создания пользователя).
источник
ИМХО Одним из ключевых аспектов архитектуры микросервисов является то, что транзакция ограничена отдельным микросервисом (принцип единой ответственности).
В текущем примере создание пользователя будет собственной транзакцией. Создание пользователя будет помещать событие USER_CREATED в очередь событий. Служба кошелька подпишется на событие USER_CREATED и сделает создание кошелька.
источник
Если бы мой кошелек был просто еще одной связкой записей в той же базе данных sql, что и пользователь, то я, вероятно, поместил бы код создания пользователя и кошелька в один и тот же сервис и обработал бы его, используя обычные средства транзакций базы данных.
Мне кажется, вы спрашиваете, что происходит, когда код создания кошелька требует, чтобы вы касались другой системы или систем? Я бы сказал, что все зависит от того, насколько сложным или рискованным является процесс создания.
Если это просто вопрос касания другого надежного хранилища данных (скажем, такого, которое не может участвовать в ваших транзакциях sql), то, в зависимости от общих параметров системы, я мог бы рискнуть исчезающе малым шансом, что вторая запись не произойдет. Я мог бы ничего не делать, но выдвинуть исключение и справиться с противоречивыми данными с помощью компенсирующей транзакции или даже с помощью специального метода. Как я всегда говорю своим разработчикам: «если подобное происходит в приложении, оно не останется незамеченным».
Поскольку сложность и риск создания кошелька возрастают, вы должны принять меры, чтобы уменьшить связанные с этим риски. Допустим, некоторые шаги требуют вызова нескольких партнеров apis.
На этом этапе вы можете ввести очередь сообщений вместе с понятием частично созданных пользователей и / или кошельков.
Простая и эффективная стратегия для обеспечения правильного построения ваших сущностей состоит в том, чтобы повторять задания до тех пор, пока они не будут выполнены успешно, но многое зависит от вариантов использования для вашего приложения.
Я также долго и усердно размышлял о том, почему у меня был неудачный шаг в процессе инициализации.
источник
Одно простое решение - вы создаете пользователя с помощью службы пользователя и используете шину обмена сообщениями, где служба пользователя генерирует свои события, а служба кошелька регистрируется на шине обмена сообщениями, прослушивает событие, созданное пользователем, и создает кошелек для пользователя. В то же время, если пользователь заходит в пользовательский интерфейс кошелька, чтобы увидеть свой кошелек, проверьте, был ли он только что создан, и покажите, что процесс создания вашего кошелька находится в процессе, проверьте это через некоторое время.
источник
Традиционно используются распределенные диспетчеры транзакций. Несколько лет назад в мире Java EE вы, возможно, создали эти сервисы как EJB, которые были развернуты на разных узлах, и ваш шлюз API сделал бы удаленные вызовы этих EJB. Сервер приложений (если он настроен правильно) автоматически, используя двухфазную фиксацию, гарантирует, что транзакция либо фиксируется, либо откатывается на каждом узле, что обеспечивает согласованность. Но для этого необходимо, чтобы все службы были развернуты на одном сервере приложений одного типа (чтобы они были совместимы) и в действительности работали только со службами, развернутыми одной компанией.
Для SOAP (хорошо, а не REST) существует спецификация WS-AT, но ни одна служба, которую мне когда-либо приходилось интегрировать, не поддерживает это. Для REST у JBoss есть кое-что в процессе . В противном случае «шаблон» - это либо найти продукт, который вы можете подключить к своей архитектуре, либо создать собственное решение (не рекомендуется).
Я опубликовал такой продукт для Java EE: https://github.com/maxant/genericconnector
Согласно документу, на который вы ссылаетесь, существует также шаблон «Попробуйте отменить / подтвердить» и связанный с ним продукт от Atomikos.
Механизмы BPEL обрабатывают согласованность между удаленно развернутыми службами, используя компенсацию.
Существует много способов «привязки» нетранзакционных ресурсов к транзакции:
Играющие в дьяволов защитники: зачем создавать что-то подобное, когда есть продукты, которые делают это для вас (см. Выше) и, вероятно, делают это лучше, чем вы, потому что они проверены и опробованы?
источник
Лично мне нравится идея Micro Services, модулей, определяемых сценариями использования, но, как упоминается в вашем вопросе, у них есть проблемы с адаптацией для классических предприятий, таких как банки, страхование, телекоммуникации и т. Д.
Распределенные транзакции, как многие упоминали, не очень хороший выбор, сейчас люди предпочитают использовать в конечном итоге согласованные системы, но я не уверен, что это сработает для банков, страхования и т. Д.
Я написал блог о моем предложенном решении, может быть, это может помочь вам ....
https://mehmetsalgar.wordpress.com/2016/11/05/micro-services-fan-out-transaction-problems-and-solutions-with-spring-bootjboss-and-netflix-eureka/
источник
Окончательная последовательность - вот ключ.
Командир отвечает за распределенную транзакцию и берет на себя управление. Он знает инструкцию, которая будет выполнена, и будет координировать их выполнение. В большинстве сценариев будет только две инструкции, но они могут обрабатывать несколько инструкций.
Командир берет на себя ответственность за обеспечение выполнения всех инструкций, а это значит, уходит в отставку. Когда командир пытается выполнить удаленное обновление и не получает ответ, он не повторяет попытку. Таким образом, система может быть настроена таким образом, чтобы она была менее подвержена сбоям и сама себя лечила.
Поскольку у нас есть повторные попытки, у нас есть идемпотентность. Идемпотентность - это свойство делать что-то дважды, таким образом, чтобы конечные результаты были такими же, как если бы это было сделано только один раз. Нам нужна идемпотентность в удаленном сервисе или источнике данных, чтобы в случае, когда он получает инструкцию более одного раза, он обрабатывал ее только один раз.
Окончательная согласованность Это решает большинство проблем распределенных транзакций, однако здесь необходимо рассмотреть несколько моментов. Каждая неудачная транзакция будет сопровождаться повторной попыткой, количество попыток зависит от контекста.
Согласованность возможна, т. Е. Когда система находится в состоянии отсутствия согласованности во время повторной попытки, например, если клиент заказал книгу и произвел оплату, а затем обновил количество запаса. Если операции обновления запаса завершатся неудачно и при условии, что это был последний доступный запас, книга будет по-прежнему доступна до тех пор, пока не будет выполнена повторная попытка обновления запаса. После успешного повтора ваша система будет работать согласованно.
источник
Почему бы не использовать платформу API Management (APIM), которая поддерживает сценарии / программирование? Таким образом, вы сможете построить составной сервис в APIM, не мешая микро сервисам. Я разработал, используя APIGEE для этой цели.
источник