Шлюз 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.