Микросервисная архитектура
Микросервис — автономно развёртываемая часть системы вокруг бизнес-возможности. Главная граница проходит не по числу классов, а по владению моделью, данными, контрактом и жизненным циклом выпуска.
flowchart LR UI["Client"] --> A["Orders"] A --> B["Payments"] A --> K["Event log"] K --> C["Notifications"] A --- DA[("Orders DB")] B --- DB[("Payments DB")]
Отдельная БД на схеме означает ownership, а не запрет любых интеграционных проекций. Прямое чтение чужих таблиц связывает схемы и усложняет независимое изменение.
Цена распределения
Монолитный method call заменяется сетью, где возможны:
- timeout с неизвестным результатом;
- частичная недоступность;
- повтор и дубликат;
- изменение порядка сообщений;
- несовместимые версии;
- распределённая трассировка и сложный rollback.
Поэтому микросервисы оправданы, когда автономность команд/релизов и разные профили нагрузки окупают эту цену. Модульный монолит часто лучше как старт.
Надёжный синхронный вызов
- Общий latency budget и короткий timeout каждого downstream.
- Retry только для временной ошибки и повторяемой операции.
- Exponential backoff с jitter и лимитом попыток.
- Circuit breaker, чтобы не продолжать заведомо безуспешные calls.
- Bulkhead/лимит concurrency, чтобы один dependency не занял все threads.
- Наблюдаемость: correlation id, metrics, tracing.
Retry увеличивает нагрузку. Три уровня по три попытки могут превратить один запрос в десятки downstream calls.
Данные и события
Нельзя получить обычную ACID-транзакцию сразу для БД и Kafka простым порядком двух операций. Transactional outbox фиксирует бизнес-изменение и запись на публикацию в одной DB transaction. Consumer защищается inbox/idempotency key.
Для длительной операции используют saga: локальные transactions и compensating actions. Компенсация — новая бизнес-операция, а не магический rollback истории.
Реальный сетевой N+1
В рабочем потоке обход списка повторял получение одних и тех же связанных данных. Я вынес массовую загрузку доступных локально данных перед циклом и дедуплицировал оставшиеся удалённые обращения. Подробности: Кейс сетевой N plus 1.
Типичные сбои
- Разделить приложение по техническим слоям и получить “микросервис базы”.
- Создать синхронную цепочку из множества обязательных сервисов.
- Разрешить всем читать общую схему и потерять независимость.
- Retry любого POST без идемпотентности.
- Выпускать breaking contract одновременно с единственным новым клиентом.
- Централизовать каждое решение так, что сервисы нельзя развернуть независимо.
Вопросы для собеседования
В чём ключевой trade-off микросервисов? Организационная и deploy-автономность ценой распределённых отказов и эксплуатации.
Почему database per service? Чтобы сервис владел моделью и мог менять storage без скрытой связанности.
Как согласовать БД и событие без distributed transaction? Outbox плюс надёжная публикация, а на приёмной стороне — идемпотентность.
Почему retry опасен? Он усиливает нагрузку и повторяет side effects, если операция не идемпотентна.
Когда модульный монолит лучше? Когда границы ещё меняются, команда небольшая, а независимое масштабирование/релизы не окупают инфраструктурную цену.
См. REST, Kafka, Docker, Circuit Breaker, Предохранитель, автоматический выключатель, Обновление приложений в микросервисной и монолитной архитектурах.