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

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

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

Проблема

Предположим, у вас есть монолитное приложение, предоставляющее REST API. Допустим, есть два модуля A и B, которые вы хотите преобразовать в микросервисы. Сущности, которые поддерживает B, могут ссылаться на сущности, которые поддерживает A (например, A поддерживает студентов, а B поддерживает классы). В монолитной ситуации модули просто ссылаются на одну и ту же базу данных, но в ситуации с микросервисами каждый из них имеет свою собственную базу данных и взаимодействует с помощью асинхронных сообщений. Таким образом, их базы данных в конечном итоге согласуются друг с другом.

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

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

Простая диаграмма, описывающая ситуацию

Вопрос

Существует ли передовая практика или общепринятый набор вариантов для смягчения этой проблемы? (Я придумал пример ученика/класса, не зацикливайтесь на его специфике. :))

Что мы можем думать о

  • Просто скажите разработчикам этих клиентов: с этого момента вы должны реализовать механизм повторных попыток для каждой вызываемой вами конечной точки. Недостаток кажется очевидным.
  • Реализуйте шлюз API, который ожидает, пока B не будет готов. Недостаток: существует множество возможных рабочих процессов (с участием большего количества модулей от A до Z), которые потребуют этого, поэтому шлюз может стать довольно сложным.
  • Каким-то образом создайте сеанс для клиента, который отслеживает, какие запросы он сделал последовательно. Затем B может выяснить, должен ли он ждать сообщения от A, или он может даже обновить свое состояние, просто просмотрев точный запрос, который клиент сделал для A.

Есть ли лучшие методы? Какой будет наиболее подходящим?

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


person Merlin's Beard    schedule 09.12.2020    source источник


Ответы (2)


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

В этой замечательной статье о миграции с монолита на микросервисы Мартин Фаулер обращается к несоответствие данных тоже:

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

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

Кроме того, в статье представлен действительно проницательный способ увидеть несогласованность данных в отношении бизнес-процессов:

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

Вот как я вижу эту проблему:

  • Это правда, что хранилища между микросервисами A и B обновляются асинхронно, но какова точная задержка этого рабочего процесса обновления? Если мы говорим о 1-2 секундах, то нестыковка вообще может быть замечена пользователями. В противном случае систему следует масштабировать для поддержки этого (или даже более низкого) порога задержки.
  • Вы можете отслеживать события несогласованности — когда пользователь пытается получить данные, которых нет в хранилище, потому что они находятся в процессе обновления, и масштабировать вашу систему на основе этого.
  • Суть в том, что это может помочь оценить потребность в такой гарантии согласованности, а затем применить подходящий обходной путь.
person Cosmin Ioniță    schedule 29.03.2021
comment
Спасибо за объяснение и ссылку на статью! Читая ваши пункты списка, я понимаю, что не учел один важный аспект моего вопроса: я не думал о человеческих клиентах нашего приложения. Вы совершенно правы, они, вероятно, не заметят отставание в 1-2 секунды. Мой вопрос касается программных клиентов, которые запускают запрос к модулю A и B в быстрой последовательности (настолько быстро, насколько позволяет их программирование и инфраструктура). Я отредактирую вопрос соответственно. - person Merlin's Beard; 30.03.2021
comment
Спасибо за разъяснения. В этом случае я считаю, что проще решить эту проблему, создав потоки повторных попыток (компенсирующие операции) или, в крайнем случае, используя одно и то же хранилище между сервисами A и B. - person Cosmin Ioniță; 30.03.2021

Существует ли передовая практика или общепринятый набор вариантов для смягчения этой проблемы?

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

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

person David Browne - Microsoft    schedule 30.03.2021
comment
Возможно, тогда я смогу сформулировать суть моего вопроса в следующих терминах: что, если у меня нет варианта использования для вызова B сразу после A, но я обнаруживаю (после разделения A и B) что какой-то другой потребитель моего API, по-видимому, действительно имеет такой вариант использования? В более общем смысле, если я разделю свою монолитную систему на модули в соответствии с моими собственными вариантами использования, как мне быть с другими потребителями, которые «случайно» (как мне кажется) вызывают разные части моей системы и ожидают, что это продолжит работать? Это ответ, который вы не можете, или есть «лучший обходной путь» для этого? - person Merlin's Beard; 03.04.2021
comment
Обычно посторонние люди не появляются и не изменяют ваши данные, но все, что вы можете сделать в этом сценарии, — это попросить пользователя подождать, пока ваша система не достигнет согласованности, с задержкой или повторной попыткой. И вам может показаться, что вы разделили свою систему слишком агрессивно. Обратите внимание, что несколько микросервисов могут совместно использовать репозиторий, а репозиторий может хранить более одной вещи. Группировка — это всегда уравновешивание. - person David Browne - Microsoft; 03.04.2021
comment
Я еще уточню немного о «незнакомцах», если это изменит ответ. :) Наше приложение в основном состоит из бэкенда с публичным API и нескольких фронтенд-приложений. Многие клиенты взаимодействуют с нашей системой исключительно через один из наших интерфейсов, но любой может напрямую вызвать наш API, чтобы интегрировать нашу систему в свою собственную. Это также большая часть клиентской базы, и именно это вызывает проблему. Эти интеграции не были построены с учетом возможной согласованности. Мы хотели бы разделить наши микросервисы, сведя к минимуму необходимость перезаписи на стороне клиента. - person Merlin's Beard; 12.04.2021
comment
Я думаю, что это ограничит степень, в которой вы можете разделить свои репозитории. Если у клиентов есть собственные данные, вы можете разделить репозиторий, чтобы каждый клиент работал с единым согласованным представлением своих данных, в то время как другие клиенты обращались к разным репозиториям. - person David Browne - Microsoft; 12.04.2021