Операції через мікросервіси REST?


195

Скажімо, у нас є мікросервіси користувача, Wallet REST та шлюз API, який склеює речі. Коли Боб реєструється на нашому веб-сайті, наш шлюз API повинен створити користувача через мікросервіс користувача та гаманець через мікросервіс Wallet.

Тепер ось декілька сценаріїв, коли все може піти не так:

  • Створення користувача Bob не вдається: це нормально, ми просто повертаємо повідомлення про помилку. Ми використовуємо транзакції SQL, тому ніхто ніколи не бачив Боба в системі. Все добре :)

  • Користувач Bob створений, але перед тим, як створити наш Wallet, наш шлюз API важко вийде з ладу. Зараз у нас є Користувач без гаманця (суперечливі дані).

  • Користувач Bob створений, і коли ми створюємо Wallet, HTTP-з'єднання припиняється. Створення гаманця, можливо, вдалося, а може і не було.

Які рішення доступні для запобігання невідповідності такого роду даних? Чи існують шаблони, які дозволяють транзакціям охоплювати кілька REST-запитів? Я прочитав сторінку Вікіпедії про двофазний комітет, який, здається, торкається цього питання, але не знаю, як це застосувати на практиці. Цей атомний розподілений транзакцій: RESTful дизайнерський папір також здається цікавим, хоча я ще цього не читав.

Крім того, я знаю, що REST просто не підходить для цього випадку використання. Можливо, правильний спосіб вирішити цю ситуацію, щоб повністю скинути REST та використати інший протокол зв'язку, наприклад систему черги повідомлень? Або я повинен застосовувати послідовність у своєму коді програми (наприклад, маючи фонове завдання, яке виявляє невідповідності та виправляє їх, або атрибут "state" на моїй користувацькій моделі зі значеннями "create", "created" тощо)?


3
Цікаве посилання: news.ycombinator.com/item?id=7995130
Олів'є

3
Якщо користувач не має сенсу без гаманця, навіщо створити для нього окрему мікросервіс? Можливо, щось не так з архітектурою в першу чергу? Для чого потрібен загальний шлюз API, btw? Чи є якась конкретна причина для цього?
Владислав Раструсний

4
@VladislavRastrusny це був вигаданий приклад, але ви можете подумати про послугу гаманця як про обробку Stripe, наприклад.
Олів’є Лалонде

Ви можете використовувати диспетчер процесів для відстеження транзакції (модель менеджера процесів), або кожен мікросервіс знає, як викликати відкат (модель менеджера саги) або зробити якийсь двофазний фіксатор ( blog.aspiresys.com/software-product-engineering / producteering /… )
панд andrew

@VladislavRastrusny "Якщо користувач не має сенсу без гаманця, навіщо створити для нього окрему мікросервіс" - наприклад, крім того, що користувач не може існувати без гаманця, вони не мають спільного коду. Тож дві команди будуть самостійно розробляти та впроваджувати мікросервіси користувачів та Wallet. Чи не вся справа в тому, щоб робити мікросервіси?
Нік

Відповіді:


148

Що не має сенсу:

  • розподілені операції з послугами REST . Послуги REST за визначенням є без громадянства, тому вони не повинні бути учасниками транзакційного кордону, який охоплює більше однієї послуги. Ваш сценарій використання реєстрації користувачів має сенс, але дизайн з мікросервісами REST для створення даних користувача та гаманця є недобрим.

Що доставить вам головний біль:

  • EJB з розподіленими транзакціями . Це одна з тих речей, яка працює в теорії, але не на практиці. Зараз я намагаюся зробити розподілену транзакцію для віддалених EJB через інстанції JBoss EAP 6.3. Ми тижнями спілкувались із службою підтримки RedHat, але вона ще не працювала.
  • Двофазні рішення комісії взагалі . Я думаю, що протокол 2PC - це чудовий алгоритм (багато років тому я його реалізував у C із RPC). Він вимагає комплексних механізмів відновлення несправностей, з повторними спробами, сховищем стану тощо. Вся складність прихована в рамках транзакцій (напр .: JBoss Arjuna). Однак 2PC не є невдалим доказом. Є ситуації, які транзакцію просто неможливо виконати. Потім потрібно виявити та виправити невідповідності бази даних вручну. Це може статися раз на мільйон транзакцій, якщо вам пощастить, але це може відбуватися раз на кожні 100 транзакцій, залежно від вашої платформи та сценарію.
  • Сагас (компенсація угод) . Існує накладні витрати на створення компенсуючих операцій та механізм координації для активації компенсації наприкінці. Але компенсація теж не є невдалим доказом. Ви все ще можете закінчитися невідповідностями (= деякий головний біль).

Яка, мабуть, найкраща альтернатива:

  • Послідовна послідовність . Ні розподілені транзакції, як ACID, ні компенсаційні транзакції не є відмовою, і обидві можуть призвести до невідповідностей. Побічна послідовність часто краща, ніж "випадкова непослідовність". Існують різні дизайнерські рішення, такі як:
    • Ви можете створити більш надійне рішення за допомогою асинхронного зв'язку. У вашому сценарії, коли Боб реєструється, шлюз API може надіслати повідомлення до черги NewUser, а відповідь відразу ж відповісти користувачеві, що "Ви отримаєте електронний лист для підтвердження створення облікового запису". Служба споживачів у черзі може обробити повідомлення, здійснити зміни бази даних в одній транзакції та надіслати електронною поштою Бобу повідомлення про створення облікового запису.
    • Мікросервіс Користувача створює запис користувача та запис гаманця в одній базі даних . У цьому випадку магазин гаманців у користувальницькій мікросервісі - це репліка магазину головного гаманця, видимого лише для мікросервісу Wallet. Існує механізм синхронізації даних, який на основі тригера або періодично починає надсилати зміни даних (наприклад, нові гаманці) з репліки до головного, і навпаки.

Але що робити, якщо вам потрібні синхронні відповіді?

  • Переробити мікросервіси . Якщо рішення з чергою не працює, тому що споживачеві послуги потрібна відповідь відразу, я б краще переробити функцію користувача та Wallet, щоб їх розмістили в одній службі (або принаймні в одній віртушній машині, щоб уникнути розподілених транзакцій ). Так, це на крок далі від мікропослуг і ближче до моноліту, але позбавить вас від головного болю.

4
Подія послідовності спрацювала для мене. У цьому випадку черга "NewUser" повинна бути високодоступною та стійкою.
Рам Бавіредді

@RamBavireddi чи Kafka чи RabbitMQ підтримують стійкі черги?
v.oddou

@ v.oddou Так, вони.
Рам Бавіредді

2
@PauloMerson Я не впевнений у тому, як ти відрізняєшся Компенсація транзакцій до можливої ​​послідовності. Що робити, якщо за вашою можливою послідовністю створення гаманця не вдасться?
бальсик

2
@balsick Одне з викликів можливих налаштувань узгодженості - це підвищена складність дизайну. Часто потрібні перевірки послідовності та виправлення. Конструкція рішення варіюється. У відповідь я пропоную ситуацію, коли запис Wallet створюється в базі даних при обробці повідомлення, надісланого через брокера повідомлень. У цьому випадку ми могли б встановити канал Dead Letter Channel, тобто, якщо обробка цього повідомлення призведе до помилки, ми можемо надіслати повідомлення до черги мертвих листів та повідомити команду, відповідальну за "Гаманець".
Пауло Мерсон

66

Це класичне запитання, яке мені задали нещодавно під час інтерв'ю Як зателефонувати на декілька веб-сервісів і як і раніше зберігати якусь обробку помилок посеред завдання. Сьогодні, використовуючи високоефективні обчислення, ми уникаємо двох фазних комітетів. Я багато років тому читав статтю про те, що називалося "модель Starbuck" для транзакцій. Подумайте про процес замовлення, оплати, приготування та отримання кави, яку ви замовляєте в Starbuck ... Я спрощую речі, але двофазова модель фіксації буде припустимо, що весь процес буде однією транзакцією для обгортання всіх кроків, поки ви не отримаєте каву. Однак із цією моделлю всі співробітники зачекали б і перестали працювати, поки не отримаєте свою каву. Ви бачите картину?

Натомість "модель Starbuck" є більш продуктивною, дотримуючись моделі "найкращих зусиль" та компенсуючи помилки в процесі. По-перше, вони впевнені, що ви платите! Потім з'являються черги з повідомленнями з вашим замовленням, доданим до чашки. Якщо в процесі щось піде не так, як, наприклад, ви не отримали кави, це не те, що ви замовили тощо. Ми вступаємо в процес компенсації і ми гарантуємо, що ви отримаєте те, що хочете, або повернемо вам гроші. Це найефективніша модель для підвищення продуктивності праці.

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

  • Не будьте надто чіткими, визначаючи свої веб-сервіси (я не переконаний у тому, що в цей час відбувається мікросхема мікроелементів: занадто багато ризиків зайти занадто далеко);

  • Async підвищує продуктивність, тому віддайте перевагу асинхронізації, надсилайте сповіщення електронною поштою, коли це можливо.

  • Створіть більш інтелектуальні сервіси, щоб зробити їх "повторними" в будь-якій кількості разів, обробляючи uid або taskid, які дотримуватимуться порядку замовлення до кінця, перевіряючи ділові правила на кожному кроці;

  • Використовуйте черги повідомлень (JMS або інші) та перейдіть до процесорів обробки помилок, які застосовуватимуть операції до "відкату", застосовуючи протилежні операції, до речі, робота з порядком асинхронізації потребує певної черги для перевірки поточного стану процесу, тому вважайте це;

  • В крайньому випадку (оскільки це може траплятися не часто), поставте його в чергу для ручної обробки помилок.

Повернемося до початкової проблеми, яка була опублікована. Створіть обліковий запис і створіть гаманець і переконайтесь, що все зроблено.

Скажімо, веб-сервіс покликаний організувати всю операцію.

Псевдо-код веб-служби виглядатиме так:

  1. Зателефонуйте до мікросервісу створення облікового запису, передайте йому певну інформацію та якийсь унікальний ідентифікатор завдання 1.1. Мікросервіс створення облікового запису спочатку перевірить, чи цей обліковий запис вже створений. Ідентифікатор завдання пов’язаний із записом облікового запису. Мікросервіс виявляє, що обліковий запис не існує, тому він створює його і зберігає ідентифікатор завдання. ПРИМІТКА: цю послугу можна викликати 2000 разів, вона завжди матиме однаковий результат. Сервіс відповідає "квитанцією, яка містить мінімальну інформацію для виконання скасування операції, якщо потрібно".

  2. Виклик створення Wallet, надавши йому ідентифікатор облікового запису та ідентифікатор завдання. Скажімо, умова недійсна, і створення гаманця неможливо виконати. Виклик повертається з помилкою, але нічого не створено.

  3. Оркестрові повідомляється про помилку. Він знає, що йому потрібно припинити створення облікового запису, але він не зробить це сам. Він попросить службу гаманця зробити це, передавши її "мінімальну розписку квитанції", отриману в кінці кроку 1.

  4. Служба рахунків читає квитанцію про скасування і знає, як скасувати операцію; квитанція про скасування може навіть включати інформацію про інший мікросервіс, який він міг би закликати виконувати частину роботи. У цій ситуації квитанція про скасування може містити ідентифікатор облікового запису та, можливо, додаткову інформацію, необхідну для здійснення протилежної операції. У нашому випадку для спрощення речей скажімо, просто видаліть обліковий запис, використовуючи його ідентифікатор облікового запису.

  5. Скажімо, веб-сервіс ніколи не отримував успіху чи невдачі (в даному випадку) відміни створення облікового запису. Він просто знову зателефонує до послуги скасування послуги. І ця послуга, як правило, ніколи не виходить з ладу, оскільки її мета полягає в тому, щоб обліковий запис більше не існував. Тож він перевіряє, чи існує, і не бачить нічого, щоб його скасувати. Тож повертається, що операція є успішною.

  6. Веб-сервіс повертається користувачеві, що обліковий запис неможливо було створити.

Це синхронний приклад. Ми могли б управляти цим по-іншому і помістити справу в чергу повідомлень, спрямовану на службу довідки, якщо ми не хочемо, щоб система повністю відновила помилку ". Я бачив, як це виконується в компанії, де недостатньо для виправлення ситуацій можуть бути надані гачки до системи зворотного зв'язку. Довідкова служба отримала повідомлення, що містять те, що було успішно виконано та мали достатньо інформації, щоб виправити такі речі, як наша квитанція про скасування може бути використана повністю автоматизованим способом.

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

Компенсаційна схема транзакцій


2
Як ви думаєте, ви могли б розширити цю відповідь, щоб надати більш конкретні поради з ОП. На сьогодні ця відповідь є дещо невиразною і важко зрозумілою. Хоча я розумію, як каву подають у Starbucks, мені незрозуміло, які аспекти цієї системи слід наслідувати в службах REST.
jwg

Я додав приклад, пов’язаний зі справою, спочатку наданою в оригінальній публікації.
користувач8098437

2
Просто додано посилання на компенсаційну схему транзакцій, як описано в Microsoft.
користувач8098437

3
Для мене це найкраща відповідь. Так просто
Оскар Неварес

1
Зауважте, що компенсація транзакцій може бути відвертою неможливою у певних складних сценаріях (як це блискуче підкреслено в документах microsoft). У цьому прикладі, уявіть собі, перш ніж створення гаманця не зможе, хтось міг прочитати деталі пов’язаного облікового запису, здійснивши дзвінок GET на службу Обліковий запис, який в ідеалі не повинен існувати в першу чергу з моменту створення облікового запису. Це може призвести до невідповідності даних. Ця проблема ізоляції добре відома в моделі SAGAS.
Anmol Singh Jaggi

32

Усі розподілені системи мають проблеми з транзакційною консистенцією. Найкращий спосіб зробити це, як ви сказали, зробити двофазну фіксацію. Потрібно створити гаманець та користувача у стані очікування. Після його створення здійсніть окремий дзвінок, щоб активувати користувача.

Цей останній дзвінок повинен бути безпечно повторюваним (у випадку, якщо ваше з'єднання перестане).

Це потребує того, щоб останній дзвінок знав про обидві таблиці (щоб це можна було зробити за одну транзакцію JDBC).

Крім того, ви можете подумати про те, чому ви так переживаєте за користувача без гаманця. Чи вірите ви, що це спричинить проблеми? Якщо так, можливо, таке, як окремі дзвінки для відпочинку - погана ідея. Якщо користувач не повинен існувати без гаманця, ви, ймовірно, мусите додати його для користувача (у вихідному POST-дзвінку, щоб створити користувача).


Дякую за пропозицію. Послуги «Користувач / Гаманець» були вигаданими, просто для ілюстрації. Але я погоджуюся, що я повинен розробити систему так, щоб якомога більше уникати потреби в операціях.
Олів'є Лалонде

7
Я згоден з другою точкою зору. Здається, те, що ваша мікрослужба, яка створює користувача, також повинна створити гаманець, оскільки ця операція являє собою атомну одиницю роботи. Крім того, ви можете прочитати цей eaipatterns.com/docs/IEEE_Software_Design_2PC.pdf
Sattar Imamov

2
Це насправді чудова ідея. Ундос - це головний біль. Але створити щось у стані очікування набагато менш інвазивно. Були проведені будь-які перевірки, але поки що нічого остаточного не створено. Тепер нам залишається лише активувати створені компоненти. Напевно, ми можемо це зробити навіть без транзакцій.
Тімо

10

Одним з ключових аспектів архітектури мікросервісів IMHO є те, що транзакція обмежується індивідуальним мікросервісом (принцип єдиної відповідальності).

У поточному прикладі створення користувача буде власною транзакцією. Створення користувача підштовхне подія USER_CREATED до черги подій. Служба Wallet підписується на події USER_CREATED і виконує створення Wallet.


1
Якщо припустити, що ми хочемо уникати будь-якого 2PC, і припускаючи, що служба користувача записується в базу даних, то ми не можемо зробити натискання повідомлення в чергу подій Користувачем транзакційною, а це означає, що він ніколи не може зробити це сервіс Wallet.
Роман Харковський

@RomanKharkovski Справді важливий момент. Одним із способів вирішити цю проблему може бути розпочати транзакцію, зберегти Користувача, опублікувати подію (не частину транзакції) та здійснити транзакцію. (Найгірший випадок, вкрай малоймовірно, що зобов’язання не вдається, і ті, хто реагує на подію, не зможуть знайти користувача.)
Тимо,

1
Потім збережіть подію в базі даних, а також об'єкт. Майте заплановане завдання для обробки збережених подій та надсилання їх брокеру повідомлень. stackoverflow.com/a/52216427/4587961
Ян Khonski

7

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

Мені здається, ви запитуєте про те, що відбувається, коли код створення гаманця вимагає доторкнутися до іншої системи чи систем? Я кажу, що все залежить від того, наскільки складний і ризикований процес створення.

Якщо це лише питання доторкнутися до іншого надійного сховища даних (скажімо, такого, який не може брати участь у ваших транзакціях sql), то, залежно від загальних системних параметрів, я, можливо, буду ризикувати зникаючим малим шансом, що повторне записування не відбудеться. Я можу нічого не робити, але створити виняток і попрацювати з суперечливими даними через компенсаційну транзакцію або навіть якийсь спеціальний метод. Як я завжди кажу своїм розробникам: "якщо подібне відбувається в додатку, воно не залишиться непоміченим".

У міру збільшення складності та ризику створення гаманця ви повинні вжити заходів для покращення пов'язаних із цим ризиків. Скажімо, для деяких кроків потрібно викликати кілька партнерів apis.

У цей момент ви можете ввести чергу повідомлень разом із поняттям частково побудованих користувачів та / або гаманців.

Проста і ефективна стратегія, щоб переконатися, що ваші особи в кінцевому підсумку будуть належним чином побудовані, - це повторити роботу, поки вони не стануть успішними, але багато залежить від випадків використання для вашої програми.

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


4

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


3

Які рішення доступні для запобігання невідповідності такого роду даних?

Традиційно використовуються розповсюджені менеджери транзакцій. Кілька років тому у світі Java EE ви могли б створити ці служби як EJB , які були розгорнуті в різні вузли, і ваш шлюз API здійснив би віддалені дзвінки до цих EJB. Сервер додатків (якщо правильно налаштовано) автоматично забезпечує, використовуючи двофазну фіксацію, що транзакція здійснюється або відкочується на кожному вузлі, так що гарантується узгодженість. Але для цього потрібно, щоб усі сервіси були розгорнуті на одному сервері прикладних програм (щоб вони були сумісні), а насправді коли-небудь працювали з сервісами, розгорнутими однією компанією.

Чи існують зразки, які дозволяють транзакціям охоплювати кілька REST-запитів?

Для SOAP (добре, не REST) ​​є WS-AT специфікація, але жодна служба, яку мені коли-небудь доводилося інтегрувати, не підтримує. Щодо REST, JBoss має щось у стадії розробки . В іншому випадку "шаблон" - це знайти продукт, який можна підключити до своєї архітектури, або створити власне рішення (не рекомендується).

Я опублікував такий продукт для Java EE: https://github.com/maxant/genericconnector

Згідно з документом, на який ви посилаєтесь, існує також схема "Скасувати / Підтвердити" та пов'язаний продукт від Atomikos.

Двигуни BPEL обробляють узгодженість між віддалено розгорнутими службами, використовуючи компенсацію.

Крім того, я знаю, що REST просто не підходить для цього випадку використання. Можливо, правильний спосіб вирішити цю ситуацію, щоб повністю скинути REST та використати інший протокол зв'язку, наприклад систему черги повідомлень?

Існує багато способів "прив'язати" не транзакційні ресурси до транзакції:

  • Як ви пропонуєте, ви можете використовувати чергу транзакційних повідомлень, але вона буде асинхронною, тому, якщо ви залежите від відповіді, вона стане безладною.
  • Ви можете записати той факт, що вам потрібно зателефонувати задніми службами у свою базу даних, а потім зателефонувати задніми службами за допомогою пакету. Знову ж таки, асинхронізація, тому може стати безладною.
  • Ви можете використовувати двигун бізнес-процесів як шлюз API, щоб організувати мікросервіси заднього кінця.
  • Ви можете використовувати віддалений EJB, як згадувалося на початку, оскільки він підтримує розподілені транзакції поза коробкою.

Або я повинен застосовувати послідовність у своєму коді програми (наприклад, маючи фонове завдання, яке виявляє невідповідності та виправляє їх, або атрибут "state" на моїй користувацькій моделі зі значеннями "create", "created" тощо)?

Граючі чорти захисники: навіщо будувати щось подібне, коли є продукти, які роблять це для вас (див. Вище), і, ймовірно, роблять це краще, ніж ви можете, бо вони перевірені і перевірені?


2

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

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

Я написав блог про запропоноване нами рішення, можливо, це може допомогти вам…

https://mehmetsalgar.wordpress.com/2016/11/05/micro-services-fan-out-transaction-problems-and-solutions-with-spring-bootjboss-and-netflix-eureka/


0

Тут є ключовою послідовність подій.

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

Командир відповідає за розподілену транзакцію і бере контроль. Він знає, що інструкція повинна бути виконана, і координує їх виконання. У більшості сценаріїв буде просто дві інструкції, але вона може обробляти кілька інструкцій.

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

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

Послідова послідовність Це вирішує більшість проблем із розподілом транзакцій, однак нам тут потрібно враховувати кілька моментів. Після кожної невдалої транзакції буде проведено повторне спробу, кількість спроб повторних спроб залежить від контексту.

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


-2

Чому б не використовувати платформу API Management (APIM), яка підтримує сценарії / програмування? Отже, ви зможете побудувати композитний сервіс в APIM, не порушуючи мікропослуг. Я створив для цієї мети використання APIGEE.

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