РозробкаSenior

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

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

Визначальна риса
Деплоїться окремо, володіє своїми даними
У масштабі
Uber, 2020: ~2200 сервісів, перегрупованих у 70 доменів
Режим відмови
Розподілений моноліт; шторми повторів

Що додає мережа — шістнадцять проблем, патерн для кожної і його ціна

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

#ПроблемаПатернЧого коштуєПотрібно з
1Виклик зависає, і потоки викликача накопичуються в очікуванніТаймаут на кожному виклику, заданий від затримки викликаного; дедлайн, переданий далі внизПовільний, але справний виклик обривається, тож кожному таймауту потрібна запланована відповідьПершого сервісу
2Виклик падає один раз — перезапущений екземпляр, загублений пакетПовтор з експоненційною затримкою та джитером — лише на одному рівніДодаткове навантаження саме тоді, коли викликаному важко; три спроби на кожному з п’яти рівнів — це 243 викликиПершого сервісу
3Залежність лежить, а кожен викликач і далі її довбеБюджет повторів або запобіжник (circuit breaker), що на час припиняє викликиЗапобіжник — це другий режим поведінки, який треба тестувати, і він може сповільнити відновленняКоли багато викликачів ділять одну залежність
4Одна повільна залежність забирає всі потоки викликачаПереборка (bulkhead): окремий пул або ліміт паралельності на кожну залежністьПотужність простоює в пулах, які зараз не зайнятіКоли сервіс викликає кілька інших
5Повтор повторює дію, яка вже сталасяКлюч ідемпотентності: отримувач пам’ятає, що вже зробивСтан, який отримувач мусить зберігати й прибиратиПершого виклику, що змінює стан
6Запитів приходить більше, ніж сервіс може обслужитиСкидання навантаження й ліміти частоти: відмовляти рано, до виконання роботиЧастина користувачів отримує швидку помилку зараз, а не всі — повільну згодомПершого піку трафіку
7Двом сервісам потрібна та сама таблицяБаза на сервіс: один власник, а решта питає через його APIЖодних JOIN між сервісами; звітам потрібна власна копіяПершого сервісу
8Одна бізнес-дія охоплює кілька сервісів, а транзакції між ними немаєСага: ланцюг локальних транзакцій, кожна з компенсаційним крокомСкасування ви пишете самі, а паралельні саги бачать напівготовий стан одна одноїПершого потоку, що пише у два сервіси
9Зберегти рядок і опублікувати подію треба або обидва, або жоднеTransactional outbox: подію записують у тій самій транзакції, а ретранслятор її публікуєЩе один процес-ретранслятор; події приходять щонайменше раз, іноді двічіПершої події, на яку покладається інший сервіс
10Та сама подія приходить двічі або не в тому порядкуІдемпотентний споживач: записувати id оброблених повідомлень; перевіряти версіїТаблиця оброблених id для кожного споживачаПершого споживача
11Одному екрану потрібні дані п’яти сервісівКомпозиція API або модель для читання, зібрана з подій (CQRS)Композиція множить виклики; модель для читання завжди трохи відстаєПершої сторінки, що охоплює кілька сервісів
12Екземпляри з’являються й зникають, тож адреси весь час змінюютьсяВиявлення сервісів: реєстр або DNS від платформиРеєстр стає тим, від чого залежить кожен викликДругого сервісу
13Клієнтові довелося б викликати двадцять сервісівШлюз API або окремий бекенд для кожного фронтенду (BFF)Ще один перехід і шлюз, який має звичку обростати бізнес-логікоюПершого клієнта, якому потрібні два сервіси
14Кожному сервісу потрібні шифрування, повтори й метрики — у кожній мовіСпільна бібліотека або сервісна сітка (service mesh): проксі поруч із кожним сервісом чи вузломБібліотека прив’язує сервіси до своїх мов; сітка — ще одна розподілена система в експлуатаціїДесятків сервісів
15Запит упав десь на шляху через вісім сервісівРозподілене трасування: id трасування передається з кожним викликом, і логи його містятьІнструментування в кожному сервісі та сховище для трасДругого сервісу
16Зміна одного API ламає викликача, про якого ніхто не знавЗворотно сумісні зміни, версійовані API, контрактні тести від споживачівСтарі версії, які треба підтримувати, і набір контрактів на кожного споживачаДругої команди
Кожен сервіс деплоїться незалежно й володіє власним сховищем, тож жоден не читає чужих таблиць. Червоний шлях — це ланцюг синхронних викликів: чотири компоненти по 99,9% доступності множаться приблизно в 99,6% — майже три години простою на місяць замість 43 хвилин, — що гірше за будь-який окремий компонент у ньому. Пунктирне бурштинове ребро — це альтернатива: пошук реагує на подію, тож може лежати, не зупиняючи оформлення замовлення.

Що робить сервіс мікросервісом — дев’ять характеристик і та одна, що визначає

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

Термін визначили 25 березня 2014 року, коли Джеймс Льюїс і Мартін Фаулер описали «підхід до розробки одного застосунку як набору невеликих сервісів, кожен з яких працює у власному процесі й спілкується через легкі механізми». Формального визначення вони не дали. Натомість перелічили дев’ять характеристик, які зазвичай мали спільними системи, що вони бачили. Більшість пояснень цей список пропускає, і дарма, бо кожен пункт називає спосіб, яким стиль ламають на практиці. Ось усі дев’ять у їхньому порядку:

#ХарактеристикаЩо це означаєЯк виглядає, коли її немає
1Компоненти як сервісиКомпонент — це те, що деплоїться окремо й викликається через мережу, а не бібліотека, вбудована в процесСервіси, які мусять виходити разом
2Організація навколо бізнес-можливостейСервіси й команди йдуть за тим, що робить бізнес: замовлення, ціни, доставка«Сервіс бази даних», «сервіс валідації», «сервіс API» — шари з мережевими викликами між ними
3Продукти, а не проєктиОдна команда володіє сервісом увесь його вік, зокрема в продакшні, — амазонівське «you build it, you run it»Проєктна команда будує його, передає експлуатації й іде далі
4Розумні кінцеві точки й тупі трубиЛогіка живе в сервісах; транспорт — HTTP, простий брокер — лише переносить повідомленняКорпоративна шина сервісів або шлюз, що маршрутизує за бізнес-правилами
5Децентралізоване управлінняКожна команда обирає інструменти під свій сервіс у межах кількох спільних стандартівАбо один обов’язковий стек для всього, або сорок мов, під які нікого не знайти
6Децентралізоване керування данимиКожен сервіс володіє своїми даними й своєю моделлю; «клієнт» може означати різне для двох сервісівСпільна база даних — визначальна риса розподіленого моноліту
7Автоматизація інфраструктуриТестування, деплой і створення середовищ автоматизованіРучний крок деплою, помножений на кількість сервісів
8Проєктування з розрахунку на збійБудь-який виклик може впасти, тож кожен сервіс мусить це витримувати, — і збій перевіряють, а не припускаютьВиклики без таймаутів; ніхто ніколи навмисно не вимикав залежність
9Еволюційний дизайнСервіси можна переписати чи викинути; межі йдуть за тим, що змінюється разомСервіси, які ніхто не наважується видалити

Правило даних старше за саме слово. 2006 року технічний директор Amazon Вернер Фогельс описав сервісно-орієнтовану архітектуру, до якої компанія перейшла після 2001 року, коли її монолітний фронтенд перестав масштабуватися. Кожен сервіс інкапсулював свої дані разом із бізнес-логікою, що з ними працює. «Прямий доступ до бази ззовні сервісу заборонено, — сказав він, — і жодного спільного використання даних між сервісами немає». У тому ж інтерв’ю він дав правилу власності гасло: «You build it, you run it» — «збудував — експлуатуй». Льюїс і Фаулер через вісім років процитували гасло про власність, а їхня шоста характеристика переказує правило даних. Відтоді правила майже не змінилися; змінилося те, скільки існує інструментів, щоб їх дотримуватися.

Що потрібно мати до другого сервісу

28 серпня 2014 року Мартін Фаулер опублікував короткий список того, що потрібно команді, перш ніж будувати мікросервіси, і анонсував його фразою «You must be this tall to use microservices» — як табличка зі зростом біля атракціону. У списку чотири пункти. Жоден не екзотичний, і один застосунок може роками жити без будь-якого з них. Двадцять сервісів без них не протягнуть і тижня. Ось вони — з перевіркою, яку можна провести у своїй команді сьогодні:

ПередумоваЩо це означаєЧому двадцять сервісів без неї не можутьПеревірка на сьогодні
Швидке створення середовищНовий сервер чи середовище за години, автоматично, а не після черги тікетівЯкщо новий сервіс дорого підняти, команди чіпляють фічі до наявних, і межі пливутьСкільки часу від порожнього репозиторію до сервісу, що працює в продакшні?
Базовий моніторингТехнічні збої й бізнес-сигнали швидко видно в продакшніКоли сервісів багато, збій проявляється далеко від своєї причиниЧи видно просто зараз, скільки замовлень не пройшло за останні п’ять хвилин?
Швидкий деплой застосунківАвтоматизований конвеєр до тестового й продакшн-середовищаРучний крок трапляється вдвадцятеро частіше, і саме він не дає командам випускати окремоЧи може команда задеплоїти один сервіс, нікого не питаючи?
Культура DevOpsРозробка й експлуатація працюють як одне ціле, і команди експлуатують те, що будуютьСервіс, який експлуатує окрема команда, на практиці не деплоїться незалежно, хоч би що казала схемаКого будять, коли сервіс падає о третій ночі?

За дев’ять місяців Фаулер назвав ціну, що стоїть за цим списком: мікросервісна премія. Автоматизований деплой, моніторинг, обробка збоїв і узгодженість «з часом» — це робота, якої моноліт не потребує, і вона починається ще до появи будь-якої користі. Тож проста система як мікросервіси менш продуктивна, і лише після певного рівня складності стиль окуповує себе. Висновок у нього був різкий: «навіть не розглядайте мікросервіси, якщо у вас немає системи, надто складної, щоб керувати нею як монолітом». Сторінка про моноліт показує, що спробувати до цієї точки, а сторінка «Моноліт → мікросервіси» — як рухатися, коли ви вже за нею.

Дані між сервісами: без JOIN і без спільних транзакцій

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

Що давала одна базаЩо замінює це між сервісамиПідступ
JOINКомпозиція API або модель для читання, яку живлять події (рядок 11)Композиція балакуча; модель для читання відстає
Транзакція через дві таблиціСага з компенсаційними кроками (рядок 8)Скасування пишете ви; між сагами немає ізоляції
Зовнішній ключІдентифікатор плюс перевірка в коді та задача, що шукає «сиріт»Інший сервіс може видалити те, на що ви посилаєтеся
Обмеження унікальностіОдним правилом володіє один сервіс, і його питають до записуЦей сервіс тепер на критичному шляху
Прочитати щойно записанеНаступне читання користувача надсилати власникові або показувати стан «в обробці»Модель для читання якусь мить показує старе значення
Один запит для звітуКопія в аналітичному сховищі, яку живлять події чи журнал базиХвилини чи години відставання та ще один конвеєр

Саги: транзакція, яку ви пишете самі

Розподілені транзакції існують. У двофазній фіксації (two-phase commit) координатор просить кожного учасника підготуватися, а потім зафіксувати зміни, і бази даних підтримують це десятиліттями. Між сервісами її використовують рідко, з трьох причин. Кожен учасник, зокрема брокер повідомлень, мусить її підтримувати, а багато хто не підтримує. Учасники тримають блокування, поки чекають. А якщо координатор відмовить не в той момент, вони лишаються заблокованими, доки він не повернеться. Тож звична відповідь старша й простіша. Гектор Гарсія-Моліна й Кеннет Салем описали її 1987 року для довгих транзакцій усередині однієї бази й назвали сагою (saga).

Сага — це послідовність локальних транзакцій. Кожна фіксується у своєму сервісі й запускає наступну. Кожен крок, який можна скасувати, має компенсаційний крок, що його скасовує, — не відкат, а нову дію з протилежним бізнес-змістом. Сага замовлення створює замовлення в стані «очікує», резервує товар, списує гроші з картки, потім підтверджує. Якщо банк відхилив оплату, сага йде назад: знімає резерв і позначає замовлення відхиленим. Книжка Кріса Річардсона «Microservices Patterns» дає порядку кроків корисний словник. Спершу йдуть кроки, які можна компенсувати. Потім — опорний крок (pivot), після якого сага рухається лише вперед. Кожен крок після нього треба повторювати, доки він не вдасться.

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

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

ОркестраціяХореографія
Хто знає весь потікОркестраторНіхто; кожен сервіс знає лише свою реакцію
Де видно стан однієї сагиВ одному місціВідновлюється з подій у різних сервісах
Додати крокЗмінити оркестраторДодати підписника й перевірити, що ніхто не покладався на старий порядок
Як це псуєтьсяОркестратор вбирає бізнес-логіку, що належить учасникамЛанцюги подій, за якими ніхто не встежить, іноді по колу
Добре дляДовгих потоків із кількома компенсаціями, як-от виконання замовленняКоротких потоків із двома-трьома учасниками

Outbox: зберегти й опублікувати як один крок

Кожен крок саги закінчується двома записами: власними даними кроку й повідомленням, що каже наступному сервісу рушати. Саме в цій парі дані тихо зникають (рядок 9). Зберегти замовлення, потім опублікувати «замовлення створено» — і якщо процес помре між цими діями, замовлення існує, а більше ніхто про нього не дізнається. Спершу опублікувати, потім зберегти — і збій лишить подію про замовлення, якого не існує. Два записи у дві системи не є транзакцією, хоч як близько вони стоять у коді.

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

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

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

Виклики між сервісами: таймаути, повтори й рахунок за доступність

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

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

Почніть із таймауту (рядок 1), бо без нього не працює ніщо інше: викликач без таймауту тримає потік, доки залежність не відповість, а мертва залежність не відповідає ніколи. Практика Amazon, як її описує Марк Брукер, — обрати прийнятну частку хибних таймаутів, скажімо 0,1%, і поставити таймаут на відповідному перцентилі затримки викликаного — у цьому випадку на 99,9-му. Передавайте залишок часу далі вниз як дедлайн. Інакше сервіс за п’ять переходів униз і далі працює для викликача, який давно здався.

Повтори (рядок 2) — місце, де добрі наміри перетворюються на аварії. Повтор допомагає зі збоєм, що не повториться, як-от перезапущений екземпляр. Проти перевантаженого сервісу він додає навантаження саме тоді, коли навантаження і є проблемою. Повтори ще й множаться. Приклад Брукера — запит, що проходить стеком із п’яти сервісів, із трьома спробами на кожному рівні. Якщо база внизу почне падати під навантаженням, вона отримає 3 × 3 × 3 × 3 × 3 = 243 запити на кожен запит клієнта. Правило Amazon — повторювати лише в одній точці стеку, з експоненційною затримкою () та джитером: паузи ростуть і мають випадкову складову, тож клієнти не повторюють у такт.

У прикладі Amazon запит клієнта проходить стеком із п’яти сервісів, що закінчується базою даних, і кожен рівень пробує кожен виклик тричі. Коли база починає падати під навантаженням, кожен рівень повторює незалежно, тож навантаження множиться: 3, потім 9, 27, 81 — і 243 запити доходять до бази на один запит клієнта; перевантажена система тепер навряд чи взагалі відновиться. Повтор лише в одній точці стеку, з експоненційною затримкою та джитером, надсилає щонайбільше три. Обидва стовпці мають один масштаб, тому смуги праворуч майже зникають.

Обмеження на повтори не дає поганій годині стати поганим днем. Книжка Google про SRE описує два. Кожен запит отримує щонайбільше три спроби. А кожен клієнт стежить, яку частку його запитів становлять повтори, і припиняє повторювати, щойно ця частка сягає 10%. У найгіршому сценарії Google саме перше правило ще дозволяє трафіку вирости майже втричі; обидва разом тримають зростання на рівні приблизно 1,1 раза. Ту саму роботу виконує відро токенів (token bucket) на кожному клієнті, і AWS вбудував його у свої ще 2016 року.

Запобіжники (рядок 3) — найвідоміша відповідь, і про них сперечаються найбільше. Майкл Найгард назвав запобіжник (circuit breaker) і переборку (bulkhead) у книжці «Release It!» 2007 року. Запобіжник рахує збої і, щойно вони перетинають поріг, на певний час припиняє виклики залежності й натомість швидко повертає помилку. Бібліотека Hystrix від Netflix, відкрита 2012 року, зробила обидва патерни стандартом у світі Java. 19 листопада 2018 року Netflix перевів Hystrix у режим підтримки на версії 1.5.18. Фокус компанії змістився на адаптивні ліміти паралельності, що реагують на виміряну затримку, а не на пороги, задані вручну, і нові проєкти вона скерувала до resilience4j. Amazon у власних настановах іде далі. Запобіжники додають другий режим поведінки, який важко тестувати, пише Брукер, і можуть суттєво подовжити відновлення. Amazon натомість обмежує повтори локальним відром токенів.

МеханізмВід чого захищаєЧого коштуєРозумне значення на старті
ТаймаутНакопичення потоків за мертвою залежністюПовільні, але справні виклики обриваютьсяp99.9 викликаного — для 0,1% хибних таймаутів
Передача дедлайнуРоботи для викликача, який уже здавсяКожен перехід мусить його прочитати й передатиДедлайни gRPC; заголовок у HTTP
Повтор із затримкою та джитеромЗбоїв, що не повторятьсяДодаткове навантаження під час проблемТри спроби, лише на одному рівні
Бюджет повторівШтормів повторівЧастина запитів падає, хоча повтор їх урятував биПовтори — менше 10% запитів на клієнта
ЗапобіжникВикликів залежності, яка явно лежитьДругий режим для тестування; повільніше відновленняЛише там, де є справжній запасний варіант
ПереборкаТого, що одна повільна залежність забирає всі потокиПростій потужності в тихих пулахЛіміт паралельності на кожну залежність
Скидання навантаженняТого, що перевантаження переходить в обвалЧастина користувачів отримує швидку помилкуВідмовляти до будь-якої роботи
Ключ ідемпотентностіДубльованих побічних ефектів від повторівОтримувач зберігає ключіНа кожному виклику, що змінює стан

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

Експлуатація багатьох сервісів: трасування, виявлення, шлюзи й сітка

З одним сервісом запит, що впав, лишає один стектрейс. З багатьма — уривки у восьми наборах логів, і нічого їх не з’єднує, якщо ви цього не запланували (рядок 15). Рішення — id трасування, створений на вході й переданий з кожним викликом, щоб кожен рядок логу й кожен замір часу можна було згрупувати за запитом. Переносним це роблять два стандарти. W3C Trace Context, рекомендація W3C з лютого 2020 року, визначає заголовок traceparent, що переносить id між сервісами. дає бібліотеки й протокол, щоб збирати траси, метрики й логи без прив’язки до одного постачальника; 11 травня 2026 року проєкт отримав статус graduated у Cloud Native Computing Foundation. Uber описав, чого коштує відсутність трасування: одне розслідування означало пройти близько 50 сервісів дванадцяти команд, щоб знайти першопричину.

Виявлення сервісів (рядок 12) — адресна книга, що дозволяє сервісам знаходити одне одного, поки екземпляри з’являються й зникають. У Kubernetes його дає платформа через DNS; деінде — реєстр на кшталт Consul. Хай там як, це тепер те, від чого залежить кожен виклик, і воно заслуговує на таку саму турботу, як база даних. Roblox засвоїв це за 73 години в жовтні 2021 року — це один із п’яти кейсів нижче.

Шлюз (рядок 13) дає клієнтам одну точку входу замість двадцяти. Його ризик — гравітація: автентифікація, потім правила маршрутизації, потім трохи бізнес-логіки, доки шлюз знову не стане монолітом. Четверта характеристика Льюїса й Фаулера — розумні кінцеві точки, тупі труби — це перевірка, якої слід його тримати. Корисний варіант з’явився в SoundCloud, і 2015 року його описав Сем Ньюман: окремий бекенд для кожного фронтенду, або BFF. Мобільний застосунок і вебсайт отримують кожен тонкий бекенд, яким володіє команда цього клієнта, тож потреби жодного з клієнтів не гнуть спільний API.

Сервісна сітка (рядок 14) виносить із коду в проксі шифрування між сервісами, повтори, таймаути й метрики. Класичні сітки ставлять проксі поруч із кожним сервісом як sidecar; режим ambient в Istio, загальнодоступний із версії 1.24 у листопаді 2024 року, натомість запускає спільний проксі на кожному вузлі. Користь — однаковість для всіх мов. Ціна — друга розподілена система, яку треба експлуатувати й оновлювати, і пастка, яку варто назвати: якщо повторює сітка і повторює застосунок, ви збудували множильний стек із рядка 2, не написавши жодного рядка коду. Вирішіть, який рівень володіє повторами, і вимкніть їх на іншому.

Останній рядок — про зміни (рядок 16). Сервіс незалежний лише тоді, коли його викликачі переживають його релізи. Це означає зміни, що лишаються зворотно сумісними, нову версію, коли інакше не можна, і спосіб узагалі знати, хто вас викликає. Контрактні тести від споживачів роблять останнє перевірюваним: кожен викликач публікує запити, які робить, і поля, які читає, а збірка постачальника падає, коли зміна зламала б когось із них. Версіювання й контракти докладно розібрано на сторінці про проєктування API.

Сервіси й команди, що ними володіють

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

СигналЗдоровий станПроблема
Сервісів на командуКілька, які команда може обслуговувати на чергуванніСервісів більше, ніж людей
Скільки сервісів зачіпає типова фічаЗдебільшого одинТри, які випускають синхронно
Хто кого викликаєГраф, згенерований із коду чи трасЗміна ламає викликача, про якого ніхто не знав
ЧергуванняКожна команда — за свої сервісиЦентральна команда експлуатації — за все
Спільний кодНевеликі версійовані бібліотекиБібліотека «common», яку кожен сервіс мусить оновити одночасно

П’ять кейсів і те, чого в кожному бракувало

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

КейсЩо сталосяЩо це показує
Monzo, 2019Понад 1500 сервісів і 9300 з’єднань; кожен сервіс міг викликати будь-який інший, доки банк не згенерував список дозволів зі свого кодуГраф викликів існує лише в коді, доки ви його не витягнете (рядок 16)
Uber, 2020Близько 2200 критичних сервісів перегрупували в 70 доменів за шлюзамиПісля певного розміру одиниця, якою проєктують, мусить бути більшою за один сервіс
AWS Kinesis, 2020Невелике додавання потужності вивело кожен фронтенд-сервер за ліміт потоків; потім залежність «за можливості» заблокувала CognitoМ’яка залежність м’яка лише тоді, коли код, що її викликає, перевірено без неї
Roblox, 202173-годинна відмова кластера Consul, від якого залежали виявлення сервісів, планування й секретиСпільні частини платформи — єдині точки відмови, зокрема й для моніторингу (рядок 12)
AWS us-east-1, 2025Порожній DNS-запис DynamoDB виправили за три години; далі робота з відновлення EC2 відвалювалася за таймаутом і ставала в чергу знову, доки інженери її не притрималиПовтори можуть тримати систему лежачою вже після того, як причина зникла (рядки 2–3)

Історія Monzo починається як безпековий проєкт, а закінчується уроком про знання. У листопаді 2019 року безпекова команда банку хотіла, щоб сервіс, який керує зображеннями на «скарбничках», не міг звертатися до сервісу, що переказує гроші. З кількома сервісами список дозволів можна вести вручну. У Monzo їх було понад 1500, між ними — понад 9300 унікальних з’єднань, і список змінювався щодня. Ні в кого його не було. Тож команда написала rpcmap — інструмент, що читає Go-код кожного сервісу й знаходить виклики, які той робить до інших. Він генерує окремий файл правила на кожен викликаний сервіс і перевіряє ці файли на кожному push, а власники критичних сервісів можуть вимагати перегляду кожного нового викликача. Першим закрили реєстр. Урок ширший за безпеку: у великій системі сервісів справжній граф залежностей живе лише в коді, доки інструмент його не витягне, а без цього графа на рядок 16 не відповісти.

Uber — та сама проблема в більшому масштабі. До липня 2020 року компанія мала близько 2200 критичних мікросервісів, і її інженери прямо описали ціну. Сервіси стали чорними скриньками, чия поведінка могла змінитися будь-коли. Виклики йшли на багато рівнів углиб, тож сплеск затримки в одній залежності каскадом ішов угору. Пошук однієї першопричини вимагав пройти близько 50 сервісів, якими володіли 12 команд. Відповідь — Domain-Oriented Microservice Architecture — не злила сервіси. Вона їх згрупувала: пов’язані сервіси утворюють домен, домени утворюють шари, що обмежують, хто від кого може залежати, а до кожного домену звертаються через один шлюз. Uber розклав свої 2200 сервісів по 70 доменах і повідомив, що підключення нової фічі до платформи стало на 25–50% швидшим. Ще одне число з тексту варто запам’ятати: Uber виміряв період напіврозпаду мікросервісу — 1,5 року. Половину з них замінювали кожні півтора року, і шлюзи не давали кожній заміні стати міграцією для її викликачів.

Де це вироджується

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

  • Спільна база даних, що перетворює набір сервісів на розподілений моноліт (рядок 7).
  • Сервіси, названі за технічними шарами: кожна фіча зачіпає всі, і ніщо не виходить окремо.
  • Релізи, що вимагають випуску кількох сервісів у фіксованому порядку, — це і є визначення відсутньої незалежності.
  • Довгі ланцюги синхронних викликів, де доступність множиться вниз — нижче, ніж була в моноліті.
  • Повтори на кожному рівні — у клієнті, у бібліотеці й у сітці, — що множаться в шторм (рядок 2).
  • Повтори без ключів ідемпотентності, що дублюють побічні ефекти саме за тих умов, заради яких повтори існують (рядок 5).
  • Код «зберегти, потім опублікувати» без outbox, що губить подію щоразу, коли процес помирає не в той момент (рядок 9).
  • Саги, у яких ніхто не спитав, що бачить паралельний запит посеред процесу (рядок 8).
  • Шлюз, що оброс бізнес-логікою, тож він знову моноліт — лише з додатковим переходом.
  • Один сервіс на розробника, що дає топологію, яка відображає найм, а не предметну область.
  • Відсутність розподіленого трасування, що перетворює кожен міжсервісний інцидент на археологію (рядок 15).

Чесна межа в тому, що мікросервіси розв’язують організаційну проблему технічним механізмом. Якщо обмеження в тому, що дванадцять команд не можуть випускати зміни без узгодження, стиль його знімає й вартий свого рахунку. Якщо обмеження в тому, що код заплутаний, деплої страшні чи база повільна, стиль додає до проблеми мережу й виставляє за неї рахунок щомісяця. Мережа, яка робить межу дорогою для перетину, робить її й дорогою для переміщення. Хибна межа в моноліті коштує рефакторингу; між сервісами — міграції. І навіть компанії, які вміють це робити, зрештою знову мислять більшими одиницями, як Uber зі своїми 70 доменами.

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

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

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

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

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

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

Джерела й метод

На чому стоїть ця стаття

  • 19 джерел
  • 3 задокументовані збої
  • Перевірено 13 жовтня 2026 р.

Дослідження з Claude. Пошук і звірку першоджерел зроблено з Claude (Anthropic). Відбір, текст і перевірку фактів — редакція Anatomy of IT Product.

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

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