Руководство по транзакциям через микросервисы

Руководство по транзакциям через микросервисы

1. Вступление

В этой статье мы обсудим варианты реализации транзакции между микросервисами.

Мы также рассмотрим некоторые альтернативы транзакциям в сценарии распределенного микросервиса.

2. Избежание транзакций через микросервисы

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

Это может серьезно повлиять на пользовательский опыт и общую пропускную способность системы. Итак,one of the best ways to solve the problem of distributed transactions is to avoid them completely.

2.1. Пример архитектуры, требующей транзакций

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

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

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

Микросервисuser будет связан с профилем пользователя (создание нового пользователя, редактирование данных профиля и т. Д.) Со следующим базовым классом домена:

@Entity
public class User implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Basic
    private String name;

    @Basic
    private String surname;

    @Basic
    private Instant lastMessageTime;
}

Микросервисmessage будет заниматься широковещательной передачей. Он инкапсулирует объектMessage и все вокруг него:

@Entity
public class Message implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Basic
    private long userId;

    @Basic
    private String contents;

    @Basic
    private Instant messageTimestamp;

}

Каждый микросервис имеет свою базу данных. Обратите внимание, что мы не ссылаемся на объектUser из объектаMessage, поскольку классы пользователей недоступны из микросервисаmessage. Мы обращаемся к пользователю только по id.

Теперь сущностьUser содержит полеlastMessageTime, потому что мы хотим показать информацию о времени последней активности пользователя в ее профиле.

Однако, чтобы добавить новое сообщение пользователю и обновить егоlastMessageTime, теперь нам нужно будет реализовать транзакцию между микросервисами.

2.2. Альтернативный подход без транзакций

Мы можем изменить нашу микросервисную архитектуру и удалить полеlastMessageTime из сущностиUser.

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

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

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

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

3. Протокол двухфазной фиксации

Two-phase commit protocol (или 2PC) - это механизм для реализации транзакции между различными программными компонентами (несколько баз данных, очереди сообщений и т. д.)

3.1. Архитектура 2PC

Одним из важных участников распределенной транзакции является координатор транзакции. Распределенная транзакция состоит из двух этапов:

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

  • Фаза фиксации или отката - на этом этапе координатор транзакции выдает всем участникам команду фиксации или отката.

Проблема с 2PC в том, что он довольно медленный по сравнению с временем работы одного микросервиса.

Coordinating the transaction between microservices, even if they are on the same network, can really slow the system down, поэтому этот подход обычно не используется в сценариях с высокой нагрузкой.

3.2. XA Стандартный

XA standard - это спецификация для проведения распределенных транзакций 2PC через вспомогательные ресурсы. Любой JTA-совместимый сервер приложений (JBoss, GlassFish и т. Д.) Поддерживает его "из коробки".

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

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

3.3. REST-AT Стандартный проект

Еще один предлагаемый стандарт -REST-AT, который был доработан RedHat, но все еще не вышел из стадии разработки. Однако он изначально поддерживается сервером приложений WildFly.

Этот стандарт позволяет использовать сервер приложений в качестве координатора транзакций с определенным API REST для создания и объединения распределенных транзакций.

Веб-службы RESTful, которые хотят участвовать в двухфазной транзакции, также должны поддерживать определенный API REST.

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

4. Конечная последовательность и компенсация

Безусловно, одной из наиболее возможных моделей обеспечения согласованности между микросервисами являетсяeventual consistency.

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

4.1. Аргументы в пользу конечной последовательности

Например, предположим, что нам нужно решить следующую задачу:

  • зарегистрировать профиль пользователя

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

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

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

One way to solve it would be with a message-driven approach including compensation. Рассмотрим следующую архитектуру:

  • микросервисuser, которому поручена регистрация профиля пользователя

  • микросервисvalidation, которому поручена проверка данных

  • платформа обмена сообщениями, которая поддерживает постоянные очереди

Платформа обмена сообщениями может обеспечить сохранение сообщений, отправляемых микросервисами. Затем они будут доставлены позже, если получатель в данный момент недоступен.

4.2. Счастливый сценарий

В этой архитектуре счастливый сценарий будет:

  • микросервисuser регистрирует пользователя, сохраняя информацию о нем в своей локальной базе данных

  • микросервисuser помечает этого пользователя флажком. Это может означать, что этот пользователь еще не прошел проверку и не имеет доступа ко всем функциям системы.

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

  • микросервисuser отправляет сообщение микросервисуvalidation, чтобы выполнить фоновую проверку пользователя

  • микросервисvalidation запускает фоновую проверку и отправляет сообщение микросервисуuser с результатами проверки

    • если результаты положительные, микросервисuser разблокирует пользователя

    • если результаты отрицательные, микросервисuser удаляет учетную запись пользователя

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

The last step, when the user microservice removes the invalid account, is a compensation phase.

4.3. Сценарии отказа

Теперь рассмотрим несколько сценариев сбоя:

  • если микрослужбаvalidation недоступна, то платформа обмена сообщениями с ее функцией постоянной очереди гарантирует, что микросервисvalidation получит это сообщение в более позднее время.

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

  • если микросервисvalidation получает сообщение, проверяет пользователя, но не может отправить ответ обратно из-за сбоя платформы обмена сообщениями, микросервисvalidation также пытается отправить сообщение через некоторое время.

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

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

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

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

5. Заключение

В этой статье мы обсудили некоторые механизмы реализации транзакций через микросервисы.

И, в первую очередь, мы изучили некоторые альтернативы выполнению транзакций такого типа.