Микросервисы — архитектура и паттерны

Микросервисная архитектура делит приложение на небольшие независимые сервисы, у каждого — своя база, свой цикл деплоя и своя команда. В этом уроке ты разберёшь, когда это оправдано, какие паттерны применять и как не превратить проект в распределённый хаос.

Уровень: средний 16 разделов 3–4 часа

1. Монолит vs микросервисы

Монолит — одно приложение: весь код, база данных и интерфейс находятся в одном развёртываемом блоке. Микросервисы — набор небольших независимых сервисов, каждый из которых отвечает за одну бизнес-возможность и общается с остальными по сети.

В монолите app.py объединяет каталог, корзину и оплату; в микросервисах это три отдельных сервиса со своими базами и деплоями:

# Монолит: один процесс
app.py  (catalog + cart + payment)
db.sql

# Микросервисы: N процессов
catalog/   -> PORT 8081, база catalog_db
cart/      -> PORT 8082, база cart_db
payment/   -> PORT 8083, база payment_db
orders/    -> PORT 8084, база orders_db
КритерийМонолитМикросервисы
Развёртываниеодна единицакаждый сервис отдельно
База данныхобщая на всёу каждого своя
Связьвызовы функцийHTTP / очереди
Отказпадает всё приложениепадает один сервис
Командаодна на всёпо сервису на команду
Микросервисы — не «модно», а инструмент под задачи. Большинство компаний годами живут на монолите, и это нормально.
«Распилил монолит» не означает «стало микросервисами»: если сервисы связаны общей базой и деплоятся вместе, получился просто распределённый монолит.

2. Плюсы микросервисов

Зачем вообще делить приложение? У микросервисной архитектуры есть измеримые преимущества.

service catalog:
  build: ./catalog
  ports: ["8081:8081"]
  scale: 2          # только каталог ловит пик трафика

service payment:
  build: ./payment
  ports: ["8083:8083"]
Главный практический плюс проявляется на командах больше 6–8 человек: свой сервис = своя зона ответственности, нет «почини чужое, оно у меня упало».
Независимое развёртывание достигается только при версионировании API: старый сервис может ещё говорить с новым, пока идёт миграция.

3. Минусы микросервисов

Плата за гибкость честная и немалая. Основные минусы — это не «сложно», а стабильные дополнительные затраты.

Проблема монолитаЦена решения микросервисами
громоздкий деплойN деплоев и пайплайнов
единая база-бутылочное горлышкоN баз + синхронизация данных
одна команда всё трогаетмежсервисные контракты и версионирование
падает целикомотказоустойчивость требует circuit breaker, retry, k8s
Если у вас нет CI/CD и мониторинга даже для монолита, микросервисы приведут к хаосу, а не к скорости. Сначала автоматизируй релиз и наблюдение за одним приложением.
Классический компромисс — «модульный монолит»: модульная структура и общая база, которую можно дробить постепенно, когда действительно понадобится.

4. Архитектурные паттерны

Совокупность практик превращает набор сервисов в архитектуру. Ключевые паттерны декомпозиции и согласованности:

ПаттернРешаетСуть
Бизнес-возможностичто делитьграницы по бизнес-процессам (заказ, склад)
Database per serviceнесвязанность данныхбаза принадлежит одному сервису
Sagaраспределённые транзакциишаги + компенсирующие откаты
Strangler Figпостепенная миграцияновые функции выносятся, старые «удушаются»
API Gatewayединая точка входароутинг, аутентификация, агрегация
Sidecarвспомогательное рядомпрокси/тулинг рядом с сервисом

Пример Saga на оркестрации заказа: сервис резервирует товар, затем списывает оплату, а при сбое оплаты вызывает компенсирующий шаг возврата товара:

orders:  "Создать заказ"
  -> inventory:   "Зарезервировать товар"
  -> payment:     "Списать оплату"
  -> [сбой оплаты]
     -> inventory: "Отменить резерв"   # компенсация
  -> orders: "Заказ отменён"
Паттерн Strangler Fig — самый безопасный путь к микросервисам: не переписывай всё разом, а постепенно выноси функции в новые сервисы под общим фасадом.
Saga бывает двух сортов: хореография (сервисы слушают события друг друга) и оркестрация (один сервис дирижирует шагами). Для начала проще оркестрация.

5. API Gateway

Клиенты (браузер, мобильное приложение) не должны знать адреса всех сервисов. API Gateway — единственная точка входа: принимает запрос, роутит его по сервисам, выполняет аутентификацию и лимиты, отдаёт клиенту единый ответ.

Простой gateway на базе Nginx разводит запросы по путям на сервисы:

server {
    listen 80;

    location /users/   { proxy_pass http://users-service:8081; }
    location /orders/  { proxy_pass http://orders-service:8082; }
    location /payments/{ proxy_pass http://payment-service:8083; }
}

Готовые gateway-продукты (Kong, Traefik, Ambassador) добавляют сверху aутентификацию, rate limiting, кэширование и метаданные по маршрутам.

ФункцияЗачем нужна
Роутингодин домен вместо N адресов сервисов
Аутентификацияпроверка токена до попадания в сервис
Rate limitingзащита от всплесков и перебора
Агрегацияобъединение нескольких вызовов в один ответ
SSL terminationшифрование наружу, простой трафик внутри
Gateway — сингл-поинт-оф-фейл, если это один процесс. Делай его горизонтально масштабируемым и отказоустойчивым, иначе он станет единственной точкой сбоя всего магазина.
Тонкий gateway роутит запросы без бизнес-логики. Не превращай его в «корзину для всего»: сквозная логика — это скрытый монолит.

5. Сервис-дискавери

Сервисы меняют адреса при масштабировании, а друг друга узнают через service registry — реестр доступных инстансов. Без него каждый сервис хранит жёсткие IP, которые устаревают с каждым рестартом.

# Регистрация сервиса в реестре (пример, Consul HTTP API)
curl -X PUT \
  http://consul:8500/v1/agent/service/register \
  -d '{
    "ID": "cart-1",
    "Name": "cart",
    "Address": "10.0.1.5",
    "Port": 8082,
    "Check": {"HTTP": "http://10.0.1.5:8082/health", "Interval": "10s"}
  }'

При старте сервис регистрируется в реестре, при остановке — снимается. Потребитель спрашивает реестр: «какие инстансы cart есть?» — и получает актуальный список.

ПодходКак работаетИнструменты
Реестр (registry)сервисы сами регистрируются, клиенты спрашиваютConsul, Eureka, Zookeeper
Client-sideклиент сам выбирает инстанс из реестраRibbon, feign + consul
Server-sideбалансировщик смотрит в реестрNginx + consul-template, Traefik
DNS (в k8s)имя сервиса резолвится в балансировщикCoreDNS + Service
Health-чек в реестр — обязателен: регистрация без проверки здоровья оставляет «мёртвые» адреса в списках и ломает вызовы.
В Kubernetes сервис-дискавери встроен: имя Service резолвится автоматически, и контейнеры не пишут IP руками. Поэтому в микросервисах на k8s реестры часто не нужны.

6. Очереди сообщений

Синхронные HTTP-вызовы делают сервисы зависимыми: если оплата лежит, весь запрос падает. Асинхронная связь через очереди развязывает сервисы и сглаживает пики нагрузки.

order_service  ->  queue "orders"  ->  warehouse_service
order_service  ->  queue "emails"  ->  email_service

# Пример сообщения (JSON) в очереди "orders":
{ "order_id": 1001, "items": ["sku-42"], "customer": "user@example.com" }

Распространённые брокеры: Kafka (потоки событий, миллионы сообщений в секунду) и RabbitMQ (гибкие очереди и задачи).

Тип связиБлокировкаОтказ отправителяПропускная способность
HTTP-вызов (sync)да, ждёт ответзапрос падаетниже
Очередь (async)нет, fire-and-forgetсообщение остаётся в очередивыше, сглаживает пики
Событийная шинанетсобытие в топике для подписчиковмаксимальная
Асинхронность добавляет требования идемпотентности: потребитель может получить одно и то же сообщение дважды. Делай обработчики идемпотентными, чтобы это не ломало данные.
Сокращение «событийная шина» не означает «заменяет API-запросы». Синхронные операции (fast-path, проверки) остаются HTTP-вызовом, а фоновая работа уходит в очередь.

7. Observability — наблюдаемость

Запрос, проходящий через 5 сервисов, нельзя отлаживать по логам одного. Observability — три кита: логи, метрики и трассировка, собранные в одну систему.

# Структурированный лог сервиса — его удобно искать по полям
{"level":"error","service":"payment","order_id":1001,
 "trace_id":"7f3a...","error":"insufficient funds","ts":"2026-09-19T12:00:01Z"}
СтолпВопросТипичный инструмент
Logsчто случилось в этот момент?ELK, Loki
Metricsгде узкое место, насколько загружен сервис?Prometheus + Grafana
Tracesсколько времени занял каждый шаг запроса?Jaeger, OpenTelemetry
Ключевой принцип: сквозной id запроса (trace_id). Один и тот же идентификатор в логах всех сервисов позволяет собрать путь запроса целиком.
Начинай наблюдение с каркаса: health-эндпоинт, метрики на Prometheus, логи через stdout, один trace-инструмент. Дальше докручивай по мере необходимости.

8. Когда НЕ стоит переходить

Микросервисы — не серебряная пуля, и есть явные сигналы, когда переход принесёт больше боли, чем пользы.

СитуацияЧто разумнее
Маленькая команда (до 5–7 человек)модульный монолит с чистыми границами
Дедлайн и нет автоматизациисначала наладить CI/CD на монолите
Единая база и сильная связанность данныхразобрать модули, потом думать о сети
Нет чётких границ бизнес-функцийвыделить домены, дождаться ясности
Монолит работает и быстро развиваетсяоставить как есть, чинить только реальные пробки

Правильный триггер для перехода — конкретная боль: бутылочное горлышко одного модуля, недельные деплои из-за связанности, команда топчется друг у друга по ногам.

# Проверка перед "распиливанием":
# 1. Деплой монолита занимает больше 1 дня?     только тогда ...
# 2. Один модуль создаёт основную долю пиковой нагрузки? ... выделяем его в сервис
# 3. Команды конфликтуют в одной ветке?           ... границы функций готовы
# Если 3 раза "нет" — пилить рано.
Микросервисы не решают проблемы людей и процессов: если команда не умеет релизить монолит, N сервисов только увеличат беспорядок.
Существует золотая середина: выделяй в сервис только то, что реально масштабируется и деплоится независимо. Остальное честно остаётся в монолите.

9. Деплой и оркестрация

Сервисы живут в контейнерах: один контейнер — один процесс, конфигурация через переменные окружения, а не через код. Стартовый вариант — Docker Compose:

services:
  gateway:
    image: gateway:1.2.0
    ports: ["80:80"]
  auth:
    image: auth:1.0.3
    environment:
      AUTH_DB_URL: postgres://auth:pass@db/auth

Для продакшена поверх контейнеров строят оркестрацию — Kubernetes берёт на себя масштабирование, обновления и восстановление после сбоев.

10. Консистентность и saga

Классическая транзакция не живёт поверх нескольких баз. В распределённой системе используют компенсирующие паттерны:

Для заказа с оплатой и складом шаги Saga: «зарезервировать товар → списать деньги → отгрузить», а при падении — компенсация: «вернуть деньги → снять резерв».

Начинай с оркестрации saga — центральный координатор проще отлаживать, а к хореографии через события переходят, когда количество сервисов перерастает координатора.

11. Тестирование микросервисов

Интеграция сервисов проверяется на уровне контрактов. Consumer-driven контракты (Pact) фиксируют, что отправитель ожидает от получателя:

{
  "provider": "auth",
  "consumer": "api-gateway",
  "interactions": [
    { "description": "get user by token",
      "request":  { "method": "GET", "path": "/user" },
      "response": { "status": 200, "body": { "id": 1 } } }
  ]
}

Тестовую пирамиду логично строить так:

Контракты версионируются вместе с сервисами: изменение формата обязано проходить через брокера контракта до выката, иначе прод сломается в первый же день.

12. Изоляция сбоев

Сеть и сервисы ненадёжны, поэтому каждый вызов обязан быть ограничен. Четыре базовых паттерна устойчивости:

ПаттернЧто даёт
Timeoutзапрос не висит вечно, ошибка приходит быстро
Retryповтор с backoff спасает от транзиентных сбоев
Circuit breakerотключает поток к падающему сервису и даёт ему восстановиться
Bulkheadизолирует пулы потоков и памяти одного вызова

Каскадные гонки из-за одного медленного сервиса — самая частная авария: таймауты и breaker сокращают время реагирования до секунд.

Ретраи опасны, когда обработка не идемпотентна: двойной платёж или двойной заказ — результат повторного запроса. Идемпотентные ключи и компенсации обязательны.

13. Версионирование API

Микросервисы живут дольше, чем клиенты, которые их используют. Если изменять контракт без плана, один из потребителей сломается. Поэтому API версионируется осмысленно:

GET /v1/users        # первая версия контракта
GET /v2/users?active  # вторая — расширение
Версия роли между собой и сервисом должна совпадать: пары «новая схема БД + старый endpoint» приводят к «смазанному» ответу. Версионируй одновременно код и контракт.

14. Безопасность между сервисами

Внутренний трафик тоже атакуем: попасть в сеть проще, чем кажется. Три уровня защиты — транспорт, идентичность, авторизация:

mTLS:        каждый сервер проверяет сертификат клиента
OAuth 2.0:   сервис-клиенты получают токены
RBAC/Vault:  доступ к секретам по ролям
Защита «всё изнутри доверено» перестала работать. Минимальные права, ротация ключей и аудит доступа — обязательные пункты для платёжных и персональных данных.

15. Продакшен-чеклист запуска

Запуск сервиса в бой — событие, а не случайность. До релиза пройдись по контрольному списку, чтобы прод не стал «первым тестом»:

health: GET /health           # жив ли процесс
ready:  GET /ready            # готов принимать трафик
metrics: /metrics             # метрики для Prometheus
Не выпускай сервис без runbook: опиши, куда смотреть при 500, как перезапустить, как откатить. Через неделю это спасёт ночь сна при первом же инциденте.

16. Миграция монолита: стратегия вырезания

Переход с монолита на микросервисы «разом» почти всегда проваливается. Набирает очки другой путь — Strangler шаблон: функциональность вырезается кусками, пока монолит не «умирает» постепенно:

1. Выделить домен (DDD) для первого сервиса
2. За фасадом временный роутинг: часть запросов — новый сервис
3. Накопить уверенность, выключить старый путь
Параллельно внедряй Docker и Kubernetes для оркестрации вырезанных сервисов, а брокер событий (Kafka) возьмёт на себя разрывы транзакций.

Что дальше

Ты разобрался в основах микросервисной архитектуры: паттерны, API Gateway, дискавери, очереди, observability и критерии «за» и «против». Практический следующий шаг — собрать два сервиса в Docker с общим gateway на Nginx, а затем попробовать оркестрацию в Kubernetes. Темы очередей и метрик раскрыты в уроках Kafka и Prometheus.

← Вернуться к курсам