РозробкаSenior

Мікросервіси

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

Визначальна риса
Незалежний деплой
Правило даних
Своє сховище на сервіс
Режим відмови
Розподілений моноліт

Що робить сервіс мікросервісом

Забудьте на мить про «мікро» — цей склад завдав більше шкоди, ніж будь-який інший у цій темі, бо він запрошує міряти сервіси в рядках коду. Важлива ось яка властивість: чи можна змінити, протестувати й задеплоїти цей сервіс у продакшен, не деплоячи нічого іншого? Якщо так — це мікросервіс. Якщо реліз вимагає, щоб два сервіси вийшли разом, у вас один сервіс у двох репозиторіях.

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

Кожен сервіс деплоїться незалежно й володіє власним сховищем, тож жоден не читає чужих таблиць. Червоний шлях — це ланцюг синхронних викликів: три кроки по 99,9% доступності множаться в 99,7%, що гірше за будь-який окремий сервіс у ньому. Пунктирне бурштинове ребро — це альтернатива: пошук реагує на подію, тож може лежати, не зупиняючи оформлення замовлення.

На правилі вирівнювання варто затриматися, бо помилка тут тиха. Сервіси з назвами «сервіс бази даних», «сервіс валідації» та «сервіс API» — це шари з мережевими викликами між ними: кожна фіча зачіпає всі три, тож ніщо не релізиться незалежно, а повну ціну розподіленої системи ви вже сплатили за нульову незалежність. Сервіси, названі за тим, що робить бізнес, зачіпає одна фіча за раз — і саме звідти береться вся користь.

Що потрібно мати до початку

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

  • Автоматичний деплой

    Деплой — це пайплайн, а не людина. З двадцятьма сервісами деплой із ручним кроком трапляється у двадцять разів частіше, — і саме ручний крок зупинить незалежні релізи команд.

  • Спостережуваність

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

  • Швидке створення середовищ

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

  • Команди, що володіють сервісом

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

Дані — найскладніше

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

Ви втрачаєте джойн. Звіт, якому потрібні дані замовлень і клієнтів, більше не може попросити обидва в бази; він питає два сервіси або читає з подання, зробленого для цього. Ви також втрачаєте транзакцію. Операція, що охоплює два сервіси, не може бути атомарною, тож «списати оплату й зарезервувати товар» стає послідовністю кроків із визначеною поведінкою на випадок, коли другий крок падає після успішного першого, — це сага з компенсаційними діями, які пишете ви.

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

Синхронно, асинхронно і рахунок за доступність

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

Синхронний виклик зв’язує доступність. Якщо оформлення замовлення викликає білінг, а той — реєстр, і кожен доступний 99,9% часу, то оформлення тепер доступне 99,7% — гірше за будь-який компонент у ланцюзі й гірше за моноліт, який робив ту саму роботу в одному процесі. Додайте ретраї — і ви додасте навантаження саме тоді, коли сервіс нижче за потоком уже задихається; так повільна залежність стає аварією.

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

МеханізмВід чого захищаєЧим коштує
ТаймаутТого, що викликач тримає потік, поки мертва залежність відповість, і вичерпує пул.Повільний, але справний виклик може обірватися
Ретрай із відступомТимчасових збоїв — перезапущеного пода, одного втраченого пакета.Додане навантаження на сервіс, якому вже зле
ЗапобіжникРетраїв у справді мертву залежність і каскаду, що з цього піде.Погіршена відповідь, поки він розімкнений
Ключ ідемпотентностіРетраю, що списує з клієнта двічі, бо перший виклик усе-таки пройшов.Стан, який має тримати отримувач

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

Як це виявляється в реальній поставці

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

Де вони вироджуються

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

Коли це застосовувати

Застосовуйте, коли

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

Уникайте, коли

  • Невелика команда — проблеми координації, яку це розв’язує, ще немає, а рахунок усе одно прийде.
  • Межі предметної області ще не зрозумілі, тож межі сервісів були б здогадами, які дорого пересувати.
  • Справжня проблема — заплутаний код: поділ по наявній зв’язності дає його розподілену версію.
  • Деплой ще ручний — це множиться на кількість сервісів, а не лишається сталим.
  • Сувора консистентність усієї системи є бізнес-вимогою, а не звичкою.

Було корисно?

Поділіться з тим, хто працює над тією ж задачею.