Микросервисы #
1. Монолит vs Микросервис #
- Монолит - это подход, где всё приложение разрабатывается как единое целое. Все функции и компоненты связаны друг с другом, работают в одном проекте и на одном сервере
- Микросервис - это подход, где приложение разбивается на независимые сервисы, каждый из которых отвечает за отдельную задачу
Плюсы и минусы монолитов?
Плюсы:
- Не надо настраивать взаимодействие(Kafka например)
- Нет проблем с транзакциями в БД в отличии от микросервисов
- Выше производительность
- Простое развертывание для девопса
- Проще тестировать, т.к. монолит проще поднять нежели 10-ки микросервисов
Минусы:
- Если упадет монолит, то упадут все его части
- Долгая сборка тестов
- При увеличении нагрузки, приходится масштабировать всё приложение целиком, даже если проблема только в одной его части
- Сложность добавления нового функционала, т.к. надо понимать как компоненты работают, чтобы где то не упало что то
- Сложно добавлять новые технологии, потому что все взаимосвязано
Плюсы и минусы микросервисов?
Плюсы:
- Проще влиться в проект - не надо понимать весь проект
- Проще протестировать конкретный микросервис
- Отказоустойчивость – если упал какой-нибудь сервис, остальные продолжат работать
- Легче пробовать новые технологии, переписав один сервис, а не весь монолит
- Можно масштабировать только те сервисы, которые под нагрузкой, не трогая остальные
Минусы:
- Медленнее монолита
- Интеграционные тесты сложнее, т.к нужно проверить, как взаимодействуют все сервисы вместе.
- Порог вхождения разработчиков выше, чем в монолитной архитектуре(надо разбираться в брокерах сообщений)
- Сложнее работать с транзакциями если они в разных микросервисах
2. Паттерн Saga в микросервисной архитектуре #
Saga — это паттерн для управления распределёнными транзакциями в микросервисах. Он разбивает глобальную транзакцию на последовательность локальных транзакций, каждая из которых выполняется в рамках одного сервиса.
Решает проблему распределённых транзакций (которые отрабатывают в разных системах), чтобы при ошибке в одной из транзакций был rollback во всех
Плюсы SAGA
| Плюс | Описание |
|---|---|
| Гибкость | Позволяет работать с распределёнными транзакциями без блокировок баз данных. |
| Отказоустойчивость | При сбоях можно выполнить компенсационное действие вместо отката всей транзакции. |
| Масштабируемость | Сервисы работают независимо друг от друга, повышая масштабируемость системы. |
| Независимость сервисов | Нет необходимости в общей базе данных, каждый сервис может использовать собственное хранилище. |
| Подходит для долгих операций | Локальные транзакции могут быть выполнены асинхронно и растянуты во времени |
Минусы SAGA
| Минус | Описание |
|---|---|
| Сложность реализации | Необходимо тщательно продумать порядок шагов и компенсационные действия. |
| Не атомарность | Результаты промежуточных шагов видны другим сервисам, даже если последующие шаги завершатся сбоем. |
| Неявная согласованность | Данные могут быть несогласованными в течение выполнения всей цепочки. |
| Задержки | Асинхронные процессы могут увеличить время завершения транзакции. |
| Необходимость оркестрации | Требует использования системы оркестрации (например, Orchestrator Saga) или управления событиями (Choreography Saga). |
Какие способы координации транзакций в Sag-e?
Для координации транзакций существует два основных способа:
- Хореография. Микросервисы сами обмениваются событиями между собой. Каждый сервис реагирует на события другого и выполняет свою локальную транзакцию, создавая новое событие для следующего сервиса
- Оркестрация. Есть один “управляющий” сервис, который следит за процессом. Он говорит каждому микросервису, что делать, и следит за успешностью каждого шага
Какие типы транзакций существуют в Sag-е (Компенсирующая, компенсируемая, поворотная, повторяемая)
- Компенсирующая - запускается, в случае возникновения ошибки в компенсируемой транзакции, которая откатывает все предыдущие транзакции
- Компенсируемая - это обычная транзакция, которую можно откатить в случае ошибки на следующих шагах
- Поворотная - это транзакция, после которой нельзя ничего откатить
- Повторяемая - это транзакция, которую можно выполнить снова, если возникли временные проблемы
3. CAP теорема #
CAP теорема гласит, что система может обладать лишь двумя из следующих трех свойств:
- C - Согласованность (Consistency). Данные во всех сервисах не противоречат друг другу
- A - Доступность (Availability). Система продолжает работу даже при частичном отказе сервисов
- P - Устойчивость к разделению (Partition Tolerance). Система продолжает работу при частичной потере коммуникации между сервисами
4. Паттерн Circuit Breaker #
Он мониторит вызовы к внешнему сервису и при обнаружении большого количества неудачных попыток временно “отключает” вызов, предотвращая падение всей системы. Circuit Breaker основывается на трех состояниях: закрытое, открытое и полуоткрытое
- Закрытое. Всё нормально, запросы проходят к сервису
- Открытое. Если ошибки становятся слишком частыми, Circuit Breaker перестаёт направлять запросы к проблемному сервису
- Полуоткрытое. После некоторого времени Circuit Breaker отправляет несколько тестовых запросов. Если всё нормально - он переходит в закрытое состояние. Если нет — остаётся в открытом состоянии
5. Паттерн API Gateway #
API Gateway это шлюз для API. Он анализирует запросы и решает, какой микросервис должен их обработать. Может объединять информацию от разных микросервисов в один ответ, а так же: управлять нагрузкой, кэшировать ответы (уменьшение времени отклика)
6. HTTP vs Kafka. Какой способ общения между микросервисами лучше и почему? #
| Критерий | HTTP | Kafka |
|---|---|---|
| Тип взаимодействия | Синхронное (запрос-ответ). | Асинхронное (публикация и подписка на события). |
| Надёжность передачи данных | Нет встроенного механизма повторной доставки запросов. | Сообщения сохраняются и доставляются даже при сбоях потребителей. |
| Производительность | Ограничено количеством одновременных запросов и доступностью сервисов. | Высокая производительность при обработке больших объёмов сообщений. |
| Простота реализации | Легко реализовать с использованием стандартных библиотек и протоколов. | Требуется дополнительная настройка брокеров, топиков и клиентов. |
| Задержки | Минимальные, так как ответ приходит сразу после запроса. | Возможны задержки из-за асинхронной обработки сообщений. |
| Независимость сервисов | Сервисы зависят от доступности друг друга (сервис-отправитель и сервис-получатель). | Полная независимость сервисов: отправитель не знает о состоянии получателя. |
| Масштабируемость | Ограничена синхронным взаимодействием и увеличением числа клиентов. | Хорошо масштабируется для работы с высокими нагрузками. |
| Поддержка событийности | Не подходит для событийной архитектуры. | Предназначен для работы с событиями (обработка, хранение, ретрансляция). |
| Совместимость | Поддерживается практически всеми языками программирования и библиотеками. | Требуется поддержка Kafka-клиентов для различных языков. |
| Устойчивость к сбоям | Если один сервис недоступен, запрос не может быть обработан. | Сообщения сохраняются в очереди, пока не будут обработаны. |
Использовать HTTP, если:
- Нужно немедленное подтверждение результата (например, авторизация, выполнение команды).
- Система проста и не требует сложной обработки сообщений.
Использовать Kafka, если:
- Требуется высокая производительность и масштабируемость.
- Сервисы должны быть независимыми и работать асинхронно.
- Важна надёжность передачи данных и минимизация влияния сбоев.
Идеальный подход — гибридное использование:
- Для взаимодействия в реальном времени применять HTTP.
- Для событийной модели и обработки больших объёмов данных — Kafka.
7. Шаблоны проектирования для микросервисов (паттерны) #
| Паттерн | Категория | Краткое описание | Когда применять |
|---|---|---|---|
| API Gateway | Интеграция | Единая точка входа, маршрутизация запросов к микросервисам | Когда нужно скрыть внутренние URL и объединить контракты |
| Service Discovery | Инфраструктура | Автоматическая регистрация и поиск сервисов в кластере | При динамическом масштабировании и частых перезапусках |
| Circuit Breaker | Надёжность | Останавливает вызовы к недоступному сервису, даёт ему восстановиться | Для хрупких удалённых зависимостей с переменной доступностью |
| Retry | Надёжность | Повторная попытка выполнения неудачных запросов | При кратковременных сетевых или инфраструктурных сбоях |
| Bulkhead | Надёжность | Изоляция ресурсов (пулы потоков, соединений) для каждого сервиса | Чтобы сбой одного функционала не «затопил» весь сервис |
| Saga | Транзакции | Управление распределёнными транзакциями через последовательность локальных операций и компенсации | Когда нужны данные в нескольких сервисах с откатом изменений |
| CQRS | Моделирование данных | Разделение команд (write) и запросов (read) на разные модели и сервисы | При высоких требованиях к производительности чтения |
| Strangler Fig | Развёртывание | Постепенный перенос функциональности старой системы в микросервисы | Миграция монолита на микросервисы шаг за шагом |
| Sidecar | Инфраструктура | Соседний процесс рядом с сервисом, предоставляющий кросс-функциональные возможности (логирование, прокси) | Для единообразной маршрутизации, безопасности, телеметрии |
| Ambassador | Инфраструктура | Прокси/агент, выступающий «послом» сервиса, осуществляющий внешнюю интеграцию | Когда нужно вынести внешние зависимости за границы сервиса |
8. Что такое Transactional Outbox #
Transactional Outbox — это паттерн, который обеспечивает надёжную доставку событий между сервисами. Он заключается в том, что бизнес-данные и событие записываются в одной транзакции в БД, а потом отдельный процесс публикует событие в брокер. Таким образом, мы избегаем рассинхронизации между базой и очередью.
🚨 Проблема, которую он решает
Представь ситуацию 👇
Ты делаешь заказ:
@Transactional
public void placeOrder(Order order) {
orderRepository.save(order); // 1️⃣ Сохранили заказ в БД
eventBus.publish(new OrderCreatedEvent(order)); // 2️⃣ Отправили событие в Kafka
}
Если между 1️⃣ и 2️⃣ произойдёт сбой (например, сеть упала, брокер недоступен, приложение умерло) —
заказ сохранится, но событие не уйдёт, и другие сервисы (например, payment-service) о нём никогда не узнают.
Если наоборот — сообщение ушло, а коммит не успел,
то получатель узнает о заказе, которого в базе нет 😱
💡 Решение: Transactional Outbox Pattern
Суть:
Сначала записываем событие в свою же БД в отдельную таблицу (
outbox),
в рамках той же транзакции, где сохраняем бизнес-данные.Потом отдельный процесс (или поток) читает
outboxи публикует события в брокер.
Так мы достигаем атомарности (atomicity) —
либо обе операции фиксируются, либо обе откатываются.
⚖️ Преимущества паттерна
✅ Атомарность: данные и событие фиксируются одной транзакцией.
✅ Надёжность: не потеряешь события даже при сбое.
✅ Простота: всё хранится в одной БД.
✅ Совместимо с любым брокером: Kafka, RabbitMQ, SQS, etc.
✅ Легко отлаживать: можно посмотреть outbox-таблицу.
⚠️ Недостатки
❌ Таблица outbox растёт — нужно чистить.
❌ События публикуются не мгновенно (нужно планировать задержку).
❌ Нужно следить за идемпотентностью на стороне потребителей.