Шлюз API (REST) ​​+ мікросервіси, керовані подіями


16

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

Я розумію сенс архітектури, керованої подіями. Я не зовсім розумію, як насправді використовувати таку схему, сидячи за моделлю (REST), яка очікує відповіді на кожен запит. Наприклад, якщо у мене є шлюз API як мікросервіс та інша мікросервіс, що зберігає та керує користувачами, як я можу моделювати таку річ, як, наприклад, GET /users/1суто керована подіями?

Відповіді:


9

Повторюйте за мною:

REST та асинхронні події не є альтернативою. Вони повністю ортогональні.

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


Як тривіальний приклад, протокол AMQP передає повідомлення через TCP-з'єднання. У TCP кожен одержувач повинен підтверджувати кожен пакет . Якщо відправник пакету не отримує ACK для цього пакету, він продовжує повторно відправляти цей пакет, поки він не стане ACK'd або поки рівень програми "не здасться" і не припинить з'єднання. Це, очевидно, невідмовна модель запиту-відповіді, тому що кожен "запит на відправку пакета" повинен мати супровідну "відповідь підтвердження пакетів", а невідповідь призводить до відмови всього з’єднання. Але AMQP, стандартизований і широко прийнятий протокол для асинхронних помилок, що мають толерантність, передається через TCP! Що дає?

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

Давайте розглянемо дві сторони, що спілкуються безпосередньо з RESTful HTTP або опосередковано з брокером повідомлень AMQP. Припустимо, сторона A бажає завантажити зображення JPEG на Party B, який буде різко, стискати або іншим чином покращувати зображення. Партія A не потребує опрацьованого зображення відразу, але для подальшого використання та пошуку потрібна посилання на нього. Ось один із способів, який може пройти в REST:

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

Код 201 Createdвідповіді повідомляє клієнту, що їх запит не тільки був успішним, він також створив новий ресурс. У відповіді 201 Content-Locationзаголовок - це посилання на створений ресурс. Це визначено у розділах 6.3.2 та 3.1.4.2 RFC 7231.

Тепер давайте подивимося, як ця взаємодія працює над гіпотетичним протоколом RPC поверх AMQP:

  • Party A надсилає брокеру повідомлення AMQP (називайте його Messenger) повідомлення, що містить зображення та вказівки, щоб направити його на Party B для обробки, а потім відповісти на Party A з якоюсь адресою для зображення
  • Партія А чекає, можливо, роблячи інші речі
  • Месенджер надсилає оригінальне повідомлення учасниці А учасниці B
  • Партія B обробляє повідомлення
  • Сторона B надсилає Месенджеру повідомлення, що містить адресу обробленого зображення та вказівки для маршрутизації цього повідомлення до Party A
  • Месенджер надсилає стороні А повідомлення від учасника B, що містить оброблювану адресу зображення
  • Партія А вважає її виконаною роботою, оскільки тепер має посилання на оброблюваний образ
  • Коли-небудь у майбутньому, коли учаснику A потрібне зображення, він отримує зображення за адресою (можливо, надсилаючи повідомлення іншій стороні)

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

Ми можемо це легко виправити у випадку AMQP, якщо сторона B скаже A, що B прийняла зображення на обробку, давши A адресу, де буде зображення після завершення обробки. Тоді учасник B може надіслати повідомлення десь у майбутньому із зазначенням, що обробка зображення закінчена. AMQP-повідомлення на допомогу!

За винятком здогадайтесь, що: ви можете досягти того ж, що і з REST . У прикладі AMQP ми змінили повідомлення "ось оброблене зображення" на повідомлення "зображення обробляється, ви можете отримати його пізніше". Для цього в RESTful HTTP ми знову використаємо 202 Acceptedкод Content-Location:

  • Сторона A надсилає повідомлення HTTP POSTдо Party B зContent-Type: image/jpeg
  • Сторона B негайно надсилає 202 Acceptedвідповідь, що містить якийсь вміст "асинхронної операції", який описує, чи завершена обробка та де зображення буде доступне, коли буде виконано обробку. Також включений Content-Location: <link>заголовок, який у 202 Acceptedвідповіді є посиланням на ресурс, представлений будь-яким органом відповіді. У цьому випадку це означає, що це посилання на нашу асинхронну операцію!
  • Партія А вважає її виконаною роботою, оскільки тепер має посилання на оброблюваний образ
  • Коли-небудь у майбутньому, коли партії A потрібен оброблений образ, він спочатку отримує ресурс операції асинхронізації, пов'язаний у Content-Locationзаголовку, щоб визначити, чи завершена обробка. Якщо так, то сторона A використовує посилання в самій операції async для отримання обробленого зображення.

Єдина відмінність тут полягає в тому, що в моделі AMQP партія B повідомляє партії A, коли буде виконана обробка зображення. Але в моделі REST партія A перевіряє, чи обробка проводиться безпосередньо перед тим, як вона насправді потребує зображення. Ці підходи в рівній мірі масштабовані . Коли система зростає, кількість повідомлень, що надсилаються як в асинхронній AMQP, так і в асинхронній стратегії REST, збільшується з еквівалентною асимптотичною складністю. Єдина відмінність - клієнт надсилає додаткове повідомлення замість сервера.

Але підхід REST має ще кілька хитрощів в руці: динамічне виявлення та узгодження протоколу . Поміркуйте, як почалися взаємодії синхронізації та асинхронізації REST. Партія А надіслала такий самий запит до Партії Б, з тією лише різницею, що саме той вид повідомлення про успіх, на який відповіла Партія В. Що робити, якщо учасник A хотів вибрати, чи обробка зображення буде синхронною чи асинхронною? Що робити, якщо сторона А не знає, чи може партія B навіть здатна асинхронізувати обробку?

Ну, HTTP насправді вже має стандартизований протокол для цього! Вона називається HTTP Preferences, зокрема respond-asyncперевагою RFC 7240 Розділ 4.1. Якщо сторона A бажає асинхронної відповіді, вона включає Prefer: respond-asyncзаголовок із початковим запитом POST. Якщо Сторона Б вирішує виконати цей запит, він відправляє назад 202 Acceptedвідповідь , який включає в себе Preference-Applied: respond-async. В іншому випадку Party B просто ігнорує Preferзаголовок і відсилає назад, 201 Createdяк це було б зазвичай.

Це дозволяє учаснику A вести переговори з сервером, динамічно адаптуючись до того, з якою реалізацією обробки зображень ви не розмовляєте. Крім того, використання явних посилань означає, що сторона 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 / користувачів може просто повернути відповідь від служби, яка має сучасне представлення списку користувачів у системі. В іншому випадку запит на користувачів GET / api / користувачів може спричинити використання сервісом потоку подій з останнього знімка користувачів, щоб створити ще один знімок і просто повернути результати. Система, що керується подіями, не обов'язково є чисто асинхронною від запиту до відповіді, але, як правило, знаходиться на рівні, коли службам потрібно взаємодіяти з іншими службами. Часто не має сенсу асинхронно повертати GET-запит, і ви можете просто повернути відповідь послуги,

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.