Обробка розподілених транзакцій в мікросервісній архітектурі

Alex Alex 02 листопада
Обробка розподілених транзакцій в мікросервісній архітектурі

Сьогодні ми пропонуємо вашій увазі невеличкий матеріал про мікросервіси та розподілену архітектуру. Він, зокрема, зачіпає ідею Мартіна Фаулера про те, що нова система повинна починатися з моноліту, а навіть у розвинутій мікросервісній архітектурі доцільно залишати велике монолітне ядро.

Що таке розподілена транзакція?

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

Ось монолітна система інтернет-магазину, в якій використовуються транзакції:

Обробка розподілених транзакцій в мікросервісній архітектурі

Рис. 1: Транзакція в моноліті

Якщо у вищенаведеній системі користувач відправляє до платформи запит на замовлення (Checkout), то платформа створює локальну транзакцію в базі даних, і ця транзакція охоплює безліч таблиць бази даних, щоб обробити (Process) замовлення і зарезервувати (Reserve) товари зі складу. Якщо будь-який з цих кроків зробити не вдасться, то транзакція може відкотитися, що означає відмову як від самого замовлення, так і від зарезервованих товарів. Цей набір принципів називається ACID (атомарність, узгодженість, ізоляція, довговічність) і гарантується на рівні системи бази даних.

Ось декомпозиція системи інтернет-магазину, побудованої на мікросервісах:

Обробка розподілених транзакцій в мікросервісній архітектурі

Малюнок 2: Транзакції в мікросервісі

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

У чому проблема при здійсненні розподілених транзакцій в мікросервісах?

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

Як підтримувати атомарність транзакції?

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

Як обробляти конкурентні запити?

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

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

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

Можливі рішення

Дві вищезгадані проблеми вельми критичні в контексті проєктування і створення застосунків на основі мікросервісів. Для їх вирішення застосовується два наступних підходи:

  • Двофазна фіксація
  • Узгодженість у загальному підсумку і компенсація / SAGA

1. Двофазна фіксація

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

Як це працює

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

Знову розглянемо для прикладу систему інтернет-магазину:

Обробка розподілених транзакцій в мікросервісній архітектурі

Малюнок 3: Успішна двофазна фіксація в мікросервісній системі

В наведеному вище прикладі (малюнок 3), коли користувач надсилає запит на замовлення, координатор TransactionCoordinator спершу починає глобальну транзакцію, володіючи повною інформацією про контекст. Спочатку він відправляє команду prepare мікросервісу,OrderMicroservice щоб створити замовлення. Потім відправляє команду prepare доInventoryMicroservice, Щоб зарезервувати товари. Коли обидва сервісу готові внести зміни, вони блокують об'єкти від подальших змін і повідомляють про це.TransactionCoordinator Як тільки TransactionCoordinator підтвердить, що все мікросервіси готові застосувати свої зміни, він накаже цим мікросервісам зберегти їх, надіславши запит фіксації транзакції. У цей момент всі об'єкти будуть розблоковані.

Обробка розподілених транзакцій в мікросервісній архітектуріМалюнок 4: Невдала двофазна фіксація при роботі з мікросервісами

У сценарії відмови (рисунок 4) - якщо в будь-який момент окремо взятий мікросервіс не встигне приготуватися, TransactionCoordinator скасує транзакцію і почне процес відкату. На схемі OrderMicroservice з якоїсь причини не зміг створити замовлення, але InventoryMicroservice відгукнувся, що готовий створити замовлення. Координатор TransactionCoordinator запросить скасування на,InventoryMicroservice Після чого сервіс відкотить всі зроблені зміни та розблокує об'єкти бази даних.

Переваги

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

Недоліки

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

2. Узгодженість у загальному підсумку і компенсація / SAGA

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

При такому підході розподілена транзакція виконується як сукупність асинхронних локальних транзакцій на відповідних мікросервісах. Мікросервіси обмінюються інформацією через шину подій.

Як це працює

Знову ж таки, розгляньмо як приклад систему, що працює в інтернет-магазині:

Обробка розподілених транзакцій в мікросервісній архітектурі

Малюнок 5: Узгодженість у загальному підсумку / SAGA, успішний результат

В наведеному вище прикладі (малюнок 5), клієнт вимагає, щоб система обробила замовлення. При цьому запиті Choreographer породжує подію Create Order (створити замовлення), і починає транзакцію. Мікросервіс OrderMicroservice слухає цю подію і створює замовлення - якщо ця операція пройшла успішно, то він породжує подію Order Created (Замовлення створений). Координатор Choreographer слухає цю подію і переходить до замовлення товарів, породжуючи подію. Reserve Items (зарезервувати товари). Мікросервіс InventoryMicroservice слухає цю подію і замовляє товари; якщо ця подія пройшла успішно, то він породжує подію Items Reserved (товари зарезервовані). В даному прикладі це означає, що транзакція закінчена.

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

Обробка розподілених транзакцій в мікросервісній архітектурі

Малюнок 6: Узгодженість у загальному підсумку / SAGA, невдалий результат

Якщо з якоїсь причини InventoryMicroservice не вдалося зарезервувати товари (малюнок 6), він породжує подію Failed to Reserve Items (Не вдалося зарезервувати товари). Координатор Choreographer слухає цю подію і запускає компенсаційну транзакцію, породжуючи подію Delete Order (видалити замовлення). Мікросервіс OrderMicroservice слухає цю подію і видаляє раніше створене замовлення.

Переваги

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

Недоліки

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

Висновок

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

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

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

Джерело: Handling Distributed Transactions in the Microservice world

Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Війти / Зареєструватися