API-шлюз (REST) ​​+ управляемые событиями микросервисы

16

У меня есть несколько микросервисов, функциональность которых я предоставляю через API REST в соответствии с шаблоном API Gateway. Поскольку эти микросервисы являются приложениями Spring Boot, я использую Spring AMQP для обеспечения синхронного обмена данными между этими микросервисами в стиле RPC. Пока все шло гладко. Однако чем больше я читаю об управляемых событиями микросервисных архитектурах и смотрю на такие проекты, как Spring Cloud Stream, тем больше убеждаюсь в том, что, возможно, я неправильно поступаю с RPC, синхронным подходом (особенно потому, что мне это нужно для масштабирования для того, чтобы отвечать на сотни или тысячи запросов в секунду от клиентских приложений).

Я понимаю смысл архитектуры, управляемой событиями. Что я не совсем понимаю, так это как на самом деле использовать такой шаблон, когда он сидит за моделью (REST), которая ожидает ответа на каждый запрос. Например, если у меня есть свой API-шлюз в качестве микросервиса и другой микросервис, который хранит пользователей и управляет ими, как я могу смоделировать такую ​​вещь, как GET /users/1объект, чисто управляемый событиями?

Тони Э. Старк
источник

Ответы:

9

Повторяй за мной:

REST и асинхронные события не являются альтернативами. Они полностью ортогональны.

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


В качестве тривиального примера протокол AMQP отправляет сообщения по TCP-соединению. В TCP каждый пакет должен быть подтвержден получателем . Если отправитель пакета не получает ACK для этого пакета, он продолжает повторную отправку этого пакета до тех пор, пока не получит подтверждение ACK или пока уровень приложения не «сдастся» и не прекратит соединение. Это явно не отказоустойчивая модель запрос-ответ, потому что каждый «запрос на отправку пакета» должен иметь сопровождающий «ответ на подтверждение пакета», а отказ ответить приводит к сбою всего соединения. Тем не менее, AMQP, стандартизированный и широко распространенный протокол асинхронной отказоустойчивой передачи сообщений, передается по протоколу TCP! Что дает?

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

Давайте рассмотрим две стороны, взаимодействующие либо напрямую с RESTful HTTP, либо косвенно с брокером сообщений AMQP. Предположим, что Сторона A желает загрузить изображение JPEG на Сторону B, которая будет повышать резкость, сжимать или иным образом улучшать изображение. Участник А не нуждается в обработанном изображении немедленно, но требует ссылки на него для будущего использования и поиска. Вот один путь, который может пойти в REST:

  • Сторона A отправляет сообщение HTTP- POSTзапроса стороне B сContent-Type: image/jpeg
  • Сторона B обрабатывает изображение (долгое время, если оно большое), пока сторона A ждет, возможно, делая другие вещи
  • Сторона B отправляет 201 Createdответное сообщение HTTP Стороне A с Content-Location: <url>заголовком, который ссылается на обработанное изображение.
  • Сторона А считает свою работу выполненной, поскольку теперь она имеет ссылку на обработанное изображение.
  • Когда-нибудь в будущем, когда Сторона А нуждается в обработанном изображении, она ПОЛУЧАЕТ его, используя ссылку из предыдущего Content-Locationзаголовка

Код 201 Createdответа сообщает клиенту, что он не только успешно выполнил запрос, но и создал новый ресурс. В ответе 201 Content-Locationзаголовок является ссылкой на созданный ресурс. Это указано в RFC 7231, разделы 6.3.2 и 3.1.4.2.

Теперь давайте посмотрим, как это взаимодействие работает над гипотетическим протоколом RPC поверх AMQP:

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

Вы видите проблему здесь? В обоих случаях, Сторона А не может получить адрес изображения , пока после того, как Сторона B обрабатывает изображение . Тем не менее, Сторона А не нуждается в изображении сразу, и, по праву, все равно, если обработка еще не завершена!

Мы можем легко исправить это в случае AMQP, если Сторона B сообщит A, что B приняла изображение для обработки, и предоставит A адрес, по которому изображение будет находиться после завершения обработки. Затем Сторона B может отправить A сообщение в будущем, указывающее, что обработка изображения завершена. Обмен сообщениями AMQP на помощь!

Кроме угадайте, что: вы можете достичь того же с помощью REST . В примере AMQP мы изменили сообщение «вот обработанное изображение» на сообщение «изображение обрабатывается, вы можете получить его позже». Чтобы сделать это в RESTful HTTP, мы будем использовать 202 Acceptedкод и Content-Locationснова:

  • Сторона A отправляет HTTP- POSTсообщение стороне B сContent-Type: image/jpeg
  • Сторона B немедленно отправляет обратно 202 Acceptedответ, который содержит некоторый контент «асинхронной операции», который описывает, завершена ли обработка и где изображение будет доступно после завершения обработки. Также включен Content-Location: <link>заголовок, который в 202 Acceptedответе представляет собой ссылку на ресурс, представленный всем телом ответа. В данном случае это означает, что это ссылка на нашу асинхронную операцию!
  • Сторона А считает свою работу выполненной, поскольку теперь она имеет ссылку на обработанное изображение.
  • Когда-нибудь в будущем, когда Стороне А понадобится обработанное изображение, она сначала ПОЛУЧИТ ресурс асинхронной операции, связанный с Content-Locationзаголовком, чтобы определить, завершена ли обработка. Если это так, то Сторона А затем использует ссылку в самой асинхронной операции, чтобы ПОЛУЧИТЬ обработанное изображение.

Единственное отличие состоит в том, что в модели AMQP Сторона B сообщает Стороне A, когда обработка изображения завершена. Но в модели REST Сторона A проверяет, выполняется ли обработка непосредственно перед тем, как ей действительно потребуется изображение. Эти подходы эквивалентно масштабируемы . По мере увеличения системы количество сообщений, отправляемых как в асинхронных AMQP, так и в асинхронных REST-стратегиях, увеличивается с эквивалентной асимптотической сложностью. Разница лишь в том, что клиент отправляет дополнительное сообщение вместо сервера.

Но у подхода REST есть еще несколько хитростей: динамическое обнаружение и согласование протокола . Рассмотрим, как началось синхронное и асинхронное взаимодействие REST. Сторона A направила точно такой же запрос Стороне B, с той лишь разницей, что это был особый вид сообщения об успехе, на которое Сторона B ответила. Что, если Сторона А хотела выбрать, будет ли обработка изображения синхронной или асинхронной? Что, если Сторона A не знает, способна ли Сторона B даже к асинхронной обработке?

Ну, на самом деле HTTP уже имеет стандартизированный протокол для этого! Он называется HTTP Preferences, а именно respond-asyncRFC 7240, раздел 4.1. Если Сторона A желает получить асинхронный ответ, она включает Prefer: respond-asyncзаголовок с начальным запросом POST. Если Сторона B решает удовлетворить этот запрос, она отправляет 202 Acceptedответ, который включает в себя Preference-Applied: respond-async. В противном случае Сторона B просто игнорирует Preferзаголовок и отправляет обратно, 201 Createdкак обычно.

Это позволяет Стороне А вести переговоры с сервером, динамически адаптируясь к любой реализации обработки изображений, с которой она общается. Кроме того, использование явных ссылок означает, что Сторона A не должна знать о каких-либо сторонах, кроме B: нет брокера сообщений AMQP, нет загадочной Стороны C, которая знает, как на самом деле преобразовать адрес изображения в данные изображения, нет второй B-Async участник, если необходимо сделать синхронные и асинхронные запросы и т. д. Он просто описывает, что ему нужно, что ему может потребоваться, а затем реагирует на коды состояния, содержимое ответа и ссылки. Добавить вCache-Controlзаголовки для явных инструкций о том, когда хранить локальные копии данных, и теперь серверы могут согласовывать с клиентами, ресурсы каких клиентов могут хранить локальные (или даже автономные!) копии. Именно так вы строите слабосвязанные отказоустойчивые микросервисы в REST.

разъем
источник
1

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

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

Для моделирования GET /users/1при таком подходе можно было бы слушать для UserCreatedи UserUpdatedсобытий, и сохранить полезную часть данных пользователей в службе. Когда вам необходимо получить информацию о пользователях, вы можете просто запросить данные в вашем локальном хранилище данных.

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

Энди Хант
источник
Я понимаю. Но как насчет обработки ошибок (и отчетности) для клиентов в этом сценарии?
Тони Э. Старк
Я имею в виду, как мне сообщать клиентам REST об ошибках, возникающих при обработке UserCreatedсобытия (например, дублирование имени пользователя или электронной почты или сбой базы данных).
Тони Э. Старк
Это зависит от того, где вы выполняете действие. Если вы находитесь внутри пользовательской системы, вы можете выполнить всю свою проверку, записав в хранилище данных там, а затем опубликовать событие. В противном случае я считаю вполне приемлемым выполнить стандартный HTTP-запрос к /users/конечной точке и разрешить этой системе публиковать свое событие, если оно выполнено успешно, и ответить на запрос новой сущностью
Энди Хант
0

В системе с источником событий асинхронные аспекты обычно вступают в игру, когда что-то, что представляет состояние, может быть база данных или агрегированное представление некоторых данных, изменяется. Используя ваш пример, вызов GET / api / users может просто вернуть ответ от службы, которая имеет актуальное представление списка пользователей в системе. В другом сценарии запрос к GET / api / users может заставить службу использовать поток событий с момента последнего снимка пользователей для создания другого снимка и простого возврата результатов. Система, управляемая событиями, не обязательно является чисто асинхронной по сравнению с запросом на ответ, но, как правило, находится на уровне, на котором сервисы должны взаимодействовать с другими сервисами. Часто бессмысленно возвращать запрос GET асинхронно, поэтому вы можете просто вернуть ответ службы,

Ллойд Мур
источник