1/34
Looks like no tags are added yet.
Name | Mastery | Learn | Test | Matching | Spaced | Call with Kai |
|---|
No analytics yet
Send a link to your students to track their progress
Что такое синхронная и асинхронная коммуникация между сервисами? В чем ключевое отличие?
Синхронная коммуникация (request-response) — как домашний телефон:
- Вызывающий знает о вызываемом
- Вызываемый не знает кто ему звонит
- Формат коммуникации определяет вызываемый (он первичен)
- Вызывающий ждет ответа и блокируется
Асинхронная коммуникация (producer-consumer) — как радио:
- Паблишер транслирует, не зная кто слушает
- Слушатель знает про источник вещания
- Формат определяет паблишер
- Паблишер не ждет реакции слушателей
Ключевое отличие: блокирующий vs неблокирующий характер взаимодействия.
Примеры:
- Синхронная: REST API вызов с ожиданием ответа
- Асинхронная: отправка сообщения в очередь RabbitMQ
Частая ошибка: Путать протокол (HTTP) со стилем взаимодействия (синхронный/асинхронный)
Какие преимущества и недостатки у синхронной коммуникации?
Преимущества:
- Простота реализации и понимания flow
- Немедленный результат операции
- Легче отлаживать и тестировать
- Привычная модель программирования
- Проще обработка ошибок
Недостатки:
- Сильная связанность сервисов (tight coupling)
- Блокировка потока на время ожидания
- Каскадные отказы при недоступности сервиса
- Сложность масштабирования
- Увеличение общей латентности при цепочке вызовов
Пример проблемы: Если сервис B недоступен, сервис A тоже перестает отвечать клиентам
Какие преимущества и недостатки у асинхронной коммуникации?
Преимущества:
- Слабая связанность сервисов (loose coupling)
- Отказоустойчивость — сообщения буферизуются в брокере
- Лучшая масштабируемость
- Возможность обработки пиковых нагрузок
- Параллельная обработка задач
Недостатки:
- Сложность реализации и отладки
- Нет немедленного результата
- Сложнее обработка ошибок
- Необходима дополнительная инфраструктура (брокер)
- Возможны проблемы с порядком сообщений
- Eventually consistency вместо strong consistency
Частая ошибка: Использовать асинхронность везде, даже где нужен немедленный ответ
Всегда ли HTTP — это синхронная коммуникация? Приведи примеры асинхронного использования HTTP.
Нет, HTTP может использоваться асинхронно.
Примеры асинхронного HTTP:
1. Webhooks — сервис A регистрирует callback URL, сервис B вызывает его когда готов результат
2. Long polling — клиент держит соединение открытым, сервер отвечает когда появятся данные
3. Server-Sent Events (SSE) — сервер пушит события клиенту
4. HTTP/2 Server Push
5. Паттерн "202 Accepted" — сервер принимает запрос и возвращает ID задачи, клиент потом проверяет статус
Пример webhook:
- GitHub отправляет HTTP POST на твой URL при push в репозиторий
- Твой сервис обрабатывает событие асинхронно
Частая ошибка: Думать, что протокол определяет синхронность (на самом деле это паттерн использования)
Всегда ли брокеры сообщений — это асинхронная коммуникация?
В подавляющем большинстве случаев да, но можно реализовать синхронный паттерн.
Синхронный паттерн через брокер (Request-Reply):
1. Клиент отправляет сообщение с correlation ID
2. Клиент блокируется и ждет ответ в отдельной очереди
3. Сервер обрабатывает и отправляет ответ с тем же correlation ID
4. Клиент получает ответ и продолжает работу
Почему так делают редко:
- Теряются преимущества асинхронности
- Усложнение без выигрыша
- Есть более подходящие инструменты (gRPC, REST)
Частая ошибка: Реализовывать RPC через брокер без веской причины
Практическое правило: Используй брокеры для fire-and-forget или когда ответ может прийти сильно позже
Как повысить отказоустойчивость при HTTP-взаимодействии между сервисами?
Основные подходы:
1. Retry с exponential backoff
- Повторять запросы с увеличивающейся задержкой
- Например: 1с, 2с, 4с, 8с
2. Circuit Breaker
- Прекращать попытки при множественных отказах
- Переход в состояния: Closed -> Open -> Half-Open
3. Timeout на запросы
- Не ждать ответа бесконечно
- Fail fast принцип
4. Bulkhead (изоляция ресурсов)
- Отдельные пулы потоков для разных сервисов
- Проблема одного сервиса не блокирует остальные
5. Fallback механизмы
- Кэш последних успешных ответов
- Дефолтные значения
- Деградация функциональности
6. Health checks и load balancing
- Не отправлять запросы на нездоровые инстансы
Инструменты в .NET: Polly, HttpClientFactory с Polly
Что проще писать и поддерживать: синхронное или асинхронное взаимодействие? Почему?
Синхронное взаимодействие проще писать и поддерживать.
Почему синхронное проще:
- Линейный flow выполнения
- Привычная модель try-catch для ошибок
- Легче отлаживать (stack trace сохраняется)
- Меньше moving parts
- Проще тестировать
- Нет проблем с eventual consistency
Сложности асинхронного:
- Распределенные транзакции
- Обработка дубликатов
- Компенсирующие действия при ошибках
- Мониторинг и трассировка
- Дебаг через несколько сервисов
- Race conditions
Когда асинхронное оправдано:
- Долгие операции (генерация отчетов)
- Интеграция с внешними системами
- Обработка пиковых нагрузок
- Fire-and-forget сценарии
Правило: Начинай с синхронного, переходи на асинхронное при необходимости
Зачем вообще использовать брокеры сообщений? Какие задачи они решают? Как брокер может использоваться для асинхронной обработки внутри одного сервиса?
Основные задачи брокеров:
1. Развязка (decoupling) сервисов
- Producer не знает о consumer
- Можно добавлять/удалять консьюмеров
2. Буферизация и сглаживание нагрузки
- Брокеры супер эффективно проглатывают большой поток сообщений
- Consumer обрабатывает в своем темпе
- Защита от пиковых нагрузок
3. Надежная доставка
- Сообщения не теряются при сбоях
- Retry механизмы
Паттерн "публикация для себя":
- API endpoint быстро принимает запрос
- Публикует задачу в очередь
- Возвращает клиенту request_id
- Фоновый воркер обрабатывает в своем темпе
- Клиент поллит статус по request_id
Пример:
1. POST /reports/generate -> возвращает {id: "abc-123", status: "pending"}
2. Задача в очереди на генерацию
3. GET /reports/abc-123/status -> {status: "processing"}
4. GET /reports/abc-123/status -> {status: "completed", url: "..."}
Выгоды:
- Быстрый отклик пользователю
- Защита от таймаутов на долгих операциях
- Масштабирование воркеров независимо от API
Что такое push и pull модель в контексте консьюмеров? Какая модель лучше и когда?
Push модель (RabbitMQ):
- Брокер активно отправляет сообщения консьюмеру
- Консьюмер регистрирует callback
- Брокер контролирует скорость доставки
Pull модель (Kafka):
- Консьюмер сам запрашивает сообщения
- Консьюмер контролирует скорость обработки
- Polling цикл
Когда Push лучше:
- Низкая латентность важна
- Простые сценарии обработки
- Мало консьюмеров
- Равномерная нагрузка
Когда Pull лучше:
- Консьюмеры с разной производительностью
- Batch обработка
- Нужен контроль над скоростью
- Replay сообщений
Гибридный подход:
- Push с prefetch limit (RabbitMQ)
- Long polling (компромисс)
Частая ошибка: Не учитывать производительность консьюмера при выборе модели
Что такое Exchange в RabbitMQ? Какие типы существуют (fanout, topic, direct, headers) и когда какой использовать?
Exchange — точка входа сообщений в RabbitMQ, роутер который направляет сообщения в очереди по правилам.
Типы Exchange:
1. Direct
- Точное совпадение routing key
- Использование: роутинг по типу сообщения
- Пример: "order.created" -> очередь обработки заказов
2. Fanout
- Broadcast всем привязанным очередям
- Игнорирует routing key
- Использование: уведомления всем подписчикам
- Пример: обновление кэша на всех серверах
3. Topic
- Паттерн matching с wildcards (* и #)
- * — одно слово, # — ноль или более слов
- Использование: гибкая подписка на события
- Пример: "order.*" подпишется на "order.created" и "order.updated"
4. Headers
- Маршрутизация по заголовкам сообщения
- Редко используется
- Использование: сложная логика роутинга
Частая ошибка: Использовать fanout когда нужен topic (теряется гибкость)
Что такое Queue в RabbitMQ и как она связана с Exchange?
Queue — буфер, который хранит сообщения до обработки консьюмером.
Связь с Exchange:
1. Exchange не хранит сообщения, только маршрутизирует
2. Queue привязывается к Exchange через binding
3. Binding содержит routing key или паттерн
4. Одна Queue может быть привязана к нескольким Exchange
5. Один Exchange может маршрутизировать в несколько Queue
Свойства Queue:
- Durable — переживет ли рестарт сервера
- Exclusive — только для одного соединения
- Auto-delete — удалится когда отключатся все консьюмеры
- TTL — время жизни сообщений
- Max length — максимальное количество сообщений
Пример flow:
Producer -> Exchange (routing) -> Binding (фильтр) -> Queue (хранение) -> Consumer
Частая ошибка: Путать persistent сообщения и durable очереди (нужно и то, и другое для надежности)
Что такое routing key и binding в RabbitMQ?
Routing key — метка на сообщении, которую producer устанавливает при отправке. Строка, разделенная точками (например, "order.created.eu").
Binding — связь между Exchange и Queue с правилами маршрутизации.
Как работает:
1. Producer отправляет сообщение с routing key
2. Exchange смотрит на bindings
3. Сравнивает routing key с правилами binding
4. Направляет в подходящие очереди
Примеры для разных Exchange:
- Direct: точное совпадение
- Routing key: "order.created"
- Binding key: "order.created" — совпадение
- Topic: паттерны
- Routing key: "order.created.eu"
- Binding key: "order.*.eu" — совпадение
- Binding key: "order.#" — совпадение
- Fanout: игнорирует routing key
Практический пример:
- Сервис логирования подписан на "*.error"
- Сервис аудита подписан на "order.#"
- Сервис нотификаций подписан на "order.created"
Опиши путь сообщения от паблишера до консьюмера в RabbitMQ. Где настраиваются правила маршрутизации — на Queue или на Exchange?
Путь сообщения:
1. Publisher создает сообщение с routing key
2. Publisher отправляет в конкретный Exchange
3. Exchange получает сообщение
4. Exchange проверяет все bindings
5. По правилам binding определяет целевые Queue
6. Сообщение копируется в каждую подходящую Queue
7. Consumer читает из своей Queue
8. Consumer отправляет acknowledgment
9. Queue удаляет сообщение
Правила маршрутизации:
- Тип маршрутизации настраивается на Exchange (direct/topic/fanout/headers)
- Конкретные правила настраиваются в Binding
- Queue сама по себе правил не содержит
Важные моменты:
- Если нет подходящих bindings — сообщение теряется
- Exchange не хранит сообщения
- Одно сообщение может попасть в несколько Queue
Частая ошибка: Думать что routing настраивается на Queue (на самом деле в binding между Exchange и Queue)
Что такое Dead Letter Queue в RabbitMQ? Когда и зачем используется?
Dead Letter Queue (DLQ) — специальная очередь для сообщений, которые не удалось обработать.
Когда сообщение попадает в DLQ:
1. Превышен TTL сообщения
2. Очередь переполнена (max-length)
3. Сообщение отклонено с requeue=false
4. Превышено количество попыток доставки
Настройка:
- На основной очереди указывается x-dead-letter-exchange
- Опционально x-dead-letter-routing-key
- Создается отдельная очередь для dead letters
Зачем нужна:
- Не терять проблемные сообщения
- Анализ ошибок
- Ручная обработка или retry
- Алертинг о проблемах
Пример использования:
1. Обработка платежа failed 3 раза
2. Сообщение попадает в DLQ
3. Алерт команде support
4. Ручной разбор и исправление
5. Переотправка в основную очередь
Частая ошибка: Не мониторить DLQ (сообщения накапливаются)
Как работает механизм acknowledgement в RabbitMQ? Что происходит с сообщением при ошибке обработки?
Acknowledgement (ack) — подтверждение от консьюмера что сообщение успешно обработано.
Режимы:
1. Automatic ack — сразу при доставке консьюмеру
2. Manual ack — консьюмер явно подтверждает
При manual ack есть варианты:
- basic.ack — успешно обработано, удалить из очереди
- basic.nack — ошибка обработки
- basic.reject — отклонить одно сообщение
При ошибке (nack/reject):
- requeue=true — вернуть в очередь, попробует другой консьюмер
- requeue=false — удалить или отправить в DLQ
Что происходит при сбое консьюмера:
1. Соединение разрывается
2. Все неподтвержденные сообщения возвращаются в очередь
3. Становятся доступны другим консьюмерам
Best practices:
- Ack только после полной обработки
- Использовать prefetch для контроля количества
- Настроить DLQ для проблемных сообщений
- Идемпотентность для повторных обработок
Частая ошибка: Ack до завершения обработки (потеря при сбое)
Что произойдет с сообщением, если в Exchange типа direct отправить сообщение с routing key, для которого нет binding?
Сообщение будет потеряно (dropped silently).
Почему:
- Exchange не хранит сообщения
- Нет подходящего binding = некуда направить
- RabbitMQ не возвращает ошибку по умолчанию
Как узнать о потере:
1. Mandatory flag при публикации
- Вернет basic.return если не нашел queue
- Publisher должен обработать return
2. Alternate Exchange
- Запасной exchange для unrouted сообщений
- Настраивается как параметр основного exchange
Пример защиты от потери:
```
// Настройка alternate exchange
declare exchange "main" with alternate-exchange="unrouted"
declare fanout exchange "unrouted"
declare queue "unrouted-messages"
bind "unrouted-messages" to "unrouted"
```
Best practice:
- Всегда настраивай alternate exchange для critical сообщений
- Мониторь unrouted очередь
- Используй mandatory=true в development
Частая ошибка: Не проверять наличие bindings перед отправкой важных сообщений
Что такое prefetch count в RabbitMQ и зачем его настраивать?
Prefetch count (QoS) — количество сообщений, которые RabbitMQ отправит консьюмеру до получения acknowledgment.
Как работает:
- prefetch=1: консьюмер получит новое сообщение только после ack предыдущего
- prefetch=10: консьюмер может иметь до 10 неподтвержденных сообщений
- prefetch=0: без ограничений (по умолчанию)
Зачем настраивать:
1. Равномерное распределение нагрузки
- Без prefetch быстрый консьюмер заберет все
- С prefetch=1 каждый берет по одному
2. Защита от перегрузки
- Консьюмер не захлебнется сообщениями
- Контроль памяти
3. Оптимизация производительности
- Баланс между throughput и latency
- Уменьшение сетевых round trips
Как выбрать значение:
- CPU-intensive задачи: prefetch=1-2
- I/O задачи: prefetch=10-50
- Быстрые задачи: prefetch=100+
Частая ошибка: Оставлять prefetch=0 и удивляться неравномерной нагрузке между консьюмерами
Что такое Topic в Kafka?
Topic — логическая категория или канал, в который публикуются сообщения. Аналог "темы" в pub/sub системах.
Ключевые характеристики:
- Именованный поток записей
- Разделен на партиции
- Хранит все сообщения в течение retention периода
- Многократное чтение (в отличие от очередей)
Структура:
- Topic состоит из одной или более партиций
- Каждая партиция — упорядоченный лог
- Сообщения имеют offset внутри партиции
Настройки Topic:
- Количество партиций
- Replication factor
- Retention (по времени или размеру)
- Compression
- Cleanup policy (delete/compact)
Примеры топиков:
- user-events
- order-transactions
- application-logs
- inventory-updates
Отличие от RabbitMQ Queue:
- Kafka сохраняет сообщения после чтения
- Несколько consumer groups могут читать независимо
- Можно перечитать с любого offset
Что такое Partition в Kafka? Как Partition Key влияет на распределение сообщений по партициям?
Partition — упорядоченная последовательность сообщений внутри топика. Единица параллелизма в Kafka.
Зачем нужны партиции:
- Масштабирование (больше партиций = больше параллелизма)
- Распределение нагрузки между брокерами
- Упорядоченность внутри партиции
Partition Key:
- Опциональный ключ при отправке сообщения
- Определяет в какую партицию попадет сообщение
- Хэш от ключа определяет номер партиции
Распределение сообщений:
1. С ключом: hash(key) % число_партиций
- Одинаковый ключ = та же партиция
- Гарантия порядка для одного ключа
2. Без ключа: round-robin или random
- Равномерное распределение
- Нет гарантии порядка
Примеры использования ключей:
- userId — все события пользователя в одной партиции
- orderId — порядок событий заказа
- customerId — история клиента
Важно: Изменение числа партиций ломает распределение существующих ключей!
Частая ошибка: Неравномерное распределение ключей (hot partition)
Что такое Offset в Kafka?
Offset — уникальный последовательный идентификатор сообщения внутри партиции. Начинается с 0.
Характеристики:
- Монотонно возрастает
- Уникален в пределах партиции
- Неизменяем
- Присваивается при записи
Использование offset:
1. Consumer tracking
- Каждый consumer group хранит свой offset
- Показывает где остановились в чтении
2. Replay сообщений
- Можно сбросить offset назад
- Перечитать с любой точки
3. Exactly-once семантика
- Commit offset только после обработки
Виды offset:
- Log end offset — последнее сообщение в партиции
- Current offset — где сейчас читает consumer
- Committed offset — последний сохраненный offset
Хранение offset:
- Старые версии: Zookeeper
- Новые версии: internal topic __consumer_offsets
Пример: Partition 0: [0,1,2,3,4,5...100]
- Consumer A на offset 50
- Consumer B на offset 90
Что такое Consumer и Consumer Group в Kafka?
Consumer — приложение, которое читает сообщения из топиков.
Consumer Group — логическая группа консьюмеров, работающих вместе для обработки топика.
Ключевые правила Consumer Group:
1. Каждая партиция читается только одним консьюмером в группе
2. Один консьюмер может читать несколько партиций
3. Разные группы читают независимо
Распределение партиций:
- 4 партиции, 2 консьюмера: каждый читает по 2
- 4 партиции, 4 консьюмера: каждый читает по 1
- 4 партиции, 6 консьюмеров: 2 консьюмера idle
Зачем нужны группы:
- Масштабирование обработки
- Отказоустойчивость (rebalancing при сбое)
- Независимое чтение разными приложениями
Примеры:
- analytics-service (группа 1) — для аналитики
- billing-service (группа 2) — для биллинга
- Обе читают один топик orders независимо
Rebalancing:
- При добавлении/удалении консьюмера
- Перераспределение партиций
- Короткая недоступность
Опиши путь сообщения от паблишера до консьюмера в Kafka.
Путь сообщения:
1. Producer:
- Создает ProducerRecord (topic, partition?, key?, value)
- Сериализует ключ и значение
- Если partition не указан — выбирает по ключу или round-robin
2. Batching:
- Producer накапливает сообщения в batch
- Отправляет batch при достижении размера или timeout
3. Broker (Leader):
- Получает batch
- Записывает в лог партиции
- Присваивает offset каждому сообщению
- Ждет подтверждения от replicas (если acks>1)
- Отправляет ack producer'у
4. Replication:
- Follower replicas копируют данные от leader
- In-Sync Replicas подтверждают получение
5. Consumer:
- Pull запрос к leader партиции
- Указывает offset откуда читать
- Получает batch сообщений
- Обрабатывает сообщения
- Commit offset (автоматически или вручную)
Важные моменты:
- Только leader принимает запись
- Consumer всегда читает от leader
- Zero-copy оптимизация при чтении
Когда использовать RabbitMQ, а когда Kafka? Назови ключевые критерии выбора и архитектурные отличия.
Используй RabbitMQ когда:
- Сложная маршрутизация (topic exchanges)
- Нужны традиционные очереди с удалением после чтения
- RPC паттерны
- Приоритеты сообщений
- TTL для сообщений
- Небольшой/средний объем данных
Используй Kafka когда:
- Высокий throughput (миллионы сообщений/сек)
- Event sourcing / лог событий
- Нужно перечитывать сообщения
- Stream processing
- Долгое хранение сообщений
- Аналитика в реальном времени
Архитектурные отличия:
1. Модель хранения:
- RabbitMQ: сообщения удаляются после обработки
- Kafka: лог с retention периодом
2. Доставка:
- RabbitMQ: push модель
- Kafka: pull модель
3. Масштабирование:
- RabbitMQ: вертикальное + кластеризация
- Kafka: горизонтальное через партиции
4. Упорядоченность:
- RabbitMQ: в рамках одной очереди
- Kafka: в рамках партиции
Частая ошибка: Использовать Kafka для простых work queues
Как RabbitMQ и Kafka обрабатывают сообщения: по очереди или параллельно?
RabbitMQ:
- По умолчанию последовательно в рамках одного консьюмера
- Параллельность через несколько консьюмеров на одну очередь
- Каждое сообщение обрабатывается только одним консьюмером
- Prefetch позволяет консьюмеру получить несколько сообщений
- Round-robin распределение между консьюмерами
Kafka:
- Параллельность через партиции
- Один консьюмер на партицию в пределах consumer group
- Внутри партиции — строго последовательно
- Параллельность = количество партиций (максимум)
Примеры:
RabbitMQ: 1 очередь, 3 консьюмера
- Сообщение 1 → консьюмер A
- Сообщение 2 → консьюмер B
- Сообщение 3 → консьюмер C
Kafka: 3 партиции, 3 консьюмера
- Партиция 0 → консьюмер A (все сообщения последовательно)
- Партиция 1 → консьюмер B (все сообщения последовательно)
- Партиция 2 → консьюмер C (все сообщения последовательно)
Ключевое отличие: RabbitMQ распределяет сообщения, Kafka распределяет партиции
Какие существуют гарантии доставки сообщений? Объясни at most once, at least once, exactly once.
At most once (максимум один раз):
- Сообщение доставляется 0 или 1 раз
- Возможна потеря
- Нет дубликатов
- Реализация: отправил и забыл, без подтверждений
- Пример: метрики, где потеря некритична
At least once (минимум один раз):
- Сообщение доставляется 1 или более раз
- Нет потерь
- Возможны дубликаты
- Реализация: retry до получения ack
- Пример: важные события, требует идемпотентности
Exactly once (ровно один раз):
- Сообщение доставляется ровно 1 раз
- Нет потерь и дубликатов
- Самая сложная гарантия
- Реализация: идемпотентность + транзакции
Как достигается в разных системах:
- RabbitMQ: publisher confirms + manual ack + идемпотентность
- Kafka: idempotent producer + transactions + exactly-once semantics
Практическое правило:
- Выбирай at least once + идемпотентные консьюмеры
- Это проще чем true exactly once
Возможна ли гарантия exactly once? Если да, то как её достичь?
В теории — невозможна как распределенная проблема (Byzantine Generals Problem).
На практике:
1. At least once + идемпотентность
- Самый практичный подход
- Дубликаты не влияют на результат
2. Kafka Streams/Transactions
- Работает только внутри Kafka
- Если весь pipeline в Kafka — может дать exactly once
- Как только выходишь за пределы Kafka (БД, внешний API) — снова at least once
3. Двухфазный коммит
- Сложно, медленно, хрупко
- Редко используется
Почему exactly once это миф:
- Сеть может дублировать пакеты
- Таймауты не гарантируют что операция не выполнилась
- Консьюмер может упасть между обработкой и ack
Практический совет:
- Не гонись за exactly once
- Делай операции идемпотентными
- Используй at least once доставку
Частая ошибка: Полагаться на exactly once гарантии брокера для критичных операций с внешними системами
Гарантирует ли RabbitMQ и Kafka порядок сообщений? При каких условиях? Как обеспечить строгую очередность в RabbitMQ?
RabbitMQ:
- Гарантирует порядок в пределах одной очереди
- НЕ гарантирует при multiple consumers (prefetch > 1)
- НЕ гарантирует между разными очередями
Условия для порядка в RabbitMQ:
1. Один producer, один consumer
2. Prefetch = 1
3. Без requeue при ошибках
4. Одно соединение
Kafka:
- Гарантирует порядок в пределах партиции
- НЕ гарантирует между партициями
- Ключ партиционирования обеспечивает порядок для группы
Как обеспечить строгую очередность в RabbitMQ:
- Используй только одного консьюмера
- Установи prefetch=1
- Обрабатывай сообщения последовательно
- При ошибке отправляй в DLQ (не requeue)
Альтернатива для RabbitMQ:
- Single Active Consumer feature (если поддерживается)
- Автоматически держит только одного активного консьюмера
Частая ошибка: Ожидать порядок при нескольких консьюмерах
Как справиться с неверным порядком сообщений в распределенной системе?
Простые стратегии:
1. Не полагаться на порядок
- Делай операции независимыми
- Идемпотентность решает многие проблемы
2. Версии или timestamps
- Каждое событие имеет версию
- Применяй только если новее текущей
- Игнорируй устаревшие
3. Ключ партиционирования
- Все события одной сущности в одну партицию/очередь
- Например: все события userId=123 идут вместе
4. Накопление и сортировка
- Собирай события за период
- Сортируй по времени
- Обрабатывай пачкой
Пример с версиями:
- Текущий баланс: 1000₽, версия 5
- Пришло событие: установить 800₽, версия 3
- Действие: игнорировать (старая версия)
Практический совет:
- Лучше проектировать систему без зависимости от порядка
- Если порядок критичен — используй один поток обработки
Частая ошибка: Пытаться синхронизировать распределенные очереди
Что такое Transactional Outbox паттерн? Какие проблемы решает и как реализовать на практике?
Проблема:
Нужно атомарно сохранить данные в БД и отправить событие в брокер. Если сохранили в БД, но не отправили событие — система в несогласованном состоянии.
Решение Outbox:
1. В той же БД создаем таблицу для исходящих событий
2. В транзакции сохраняем бизнес-данные И событие в outbox таблицу
3. Отдельный процесс читает необработанные события и отправляет в брокер
4. После успешной отправки помечает как обработанные
Как это конвертирует гарантии:
- Было: at most once для отправки событий (можем потерять)
- Стало: at least once (благодаря retry из БД)
Преимущества:
- Консистентность данных и событий
- События не теряются при сбоях
- Можно видеть какие события еще не отправлены
- Retry из коробки
Реализация:
- Polling: фоновый процесс проверяет таблицу
- CDC (Change Data Capture): отслеживание изменений в БД
- Trigger-based: триггеры БД для уведомлений
Частая ошибка: Забыть про очистку старых обработанных событий
Что такое Transactional Inbox паттерн? Зачем он нужен?
Inbox — защита от дубликатов и гарантия обработки входящих сообщений.
Проблема:
Получили сообщение, начали обрабатывать, упали до подтверждения. Брокер доставит повторно — получим дубликат обработки.
Как работает Inbox:
1. Получаем сообщение из брокера
2. Сохраняем в inbox таблицу с уникальным message_id
3. Если такой id уже есть — пропускаем (дубликат)
4. Обрабатываем бизнес-логику в той же транзакции
5. Помечаем как обработанное
Дополнительные возможности:
- Статусы: new, processing, completed, failed
- Timestamps: received_at, started_at, completed_at
- Обработка зависших сообщений (processing больше 5 минут)
Обработка зависших:
- Сообщение в статусе "processing" уже 10 минут
- Скорее всего старый консьюмер умер
- Сбрасываем в "new" и обрабатываем заново
Преимущества:
- Автоматическая дедупликация
- История всех сообщений
- Возможность переобработки
- Защита от потерь при сбоях
Что такое идемпотентность консьюмера? Как достичь идемпотентности и справиться с дубликатами сообщений?
Идемпотентность — свойство операции давать одинаковый результат при многократном выполнении.
Почему важно:
- At least once доставка = возможны дубликаты
- Сетевые сбои = retry = дубликаты
- Без идемпотентности: двойное списание, дубли записей
Способы достижения:
1. Естественная идемпотентность
- SET операции вместо INCREMENT
- PUT вместо POST в REST
- Upsert вместо Insert
2. Идемпотентные ключи
- Уникальный ID в каждом сообщении
- Проверка перед обработкой
- Сохранение обработанных ID
3. Версионирование
- Обрабатываем только если версия больше
- Игнорируем устаревшие
4. Inbox паттерн
- Сохраняем message_id в БД
- ON CONFLICT DO NOTHING
Пример реализации:
```
ProcessMessage(message):
if (cache.Contains(message.Id))
return // Already processed
BeginTransaction()
// Business logic
cache.Add(message.Id, TTL=24h)
Commit()
```
Частая ошибка: Полагаться только на exactly-once доставку брокера
Что такое Saga паттерн? Когда он применяется?
Saga — паттерн управления распределенными транзакциями через последовательность локальных транзакций.
Проблема:
- Нет распределенных ACID транзакций в микросервисах
- Операция затрагивает несколько сервисов
- Нужна консистентность
Как работает:
1. Разбиваем на шаги (локальные транзакции)
2. Каждый шаг публикует событие
3. При ошибке — компенсирующие действия
4. Откатываем в обратном порядке
Пример: Заказ товара
1. Order Service: создать заказ → OrderCreated
2. Payment Service: списать деньги → PaymentProcessed
3. Inventory Service: зарезервировать → ItemsReserved
4. Shipping Service: создать доставку → ShippingCreated
При ошибке на шаге 3:
- Inventory Service → ReservationFailed
- Payment Service: вернуть деньги
- Order Service: отменить заказ
Когда применять:
- Бизнес-транзакция через несколько сервисов
- Можно определить компенсации
- Eventual consistency приемлема
Ограничения:
- Нет изоляции (видны промежуточные состояния)
- Сложность компенсаций
Что такое оркестрация и хореография в контексте Saga? В чем разница, какие преимущества и недостатки у каждого подхода?
Хореография:
- Каждый сервис знает что делать при получении события
- Нет центрального координатора
- Event-driven взаимодействие
Оркестрация:
- Центральный координатор (orchestrator)
- Явно управляет порядком шагов
- Command-driven взаимодействие
Пример хореографии:
OrderService → OrderCreated → PaymentService
PaymentService → PaymentProcessed → InventoryService
InventoryService → ItemsReserved → ShippingService
Пример оркестрации:
OrderSaga:
1. CreateOrder() → OrderService
2. ProcessPayment() → PaymentService
3. ReserveItems() → InventoryService
4. CreateShipping() → ShippingService
Хореография преимущества:
- Слабая связанность
- Простота добавления сервисов
- Нет single point of failure
Хореография недостатки:
- Сложно понять общий flow
- Циклические зависимости
- Трудно отлаживать
Оркестрация преимущества:
- Явный flow в одном месте
- Проще отладка и мониторинг
- Централизованная обработка ошибок
Оркестрация недостатки:
- Дополнительный компонент
- Знает о всех сервисах
- Может стать bottleneck
Рекомендация: Начинай с хореографии, переходи на оркестрацию при усложнении
Какие ключевые метрики нужно мониторить в брокерах сообщений? Что такое backpressure и как с ним справляться?
Ключевые метрики:
RabbitMQ:
- Queue depth (размер очереди)
- Publish/Consume rate
- Consumer utilization
- Connection churn
- Memory/Disk usage
- Unacknowledged messages
Kafka:
- Consumer lag (отставание)
- Partition ISR (in-sync replicas)
- Request rate и latency
- Disk usage и I/O
- Network throughput
- Rebalancing frequency
Backpressure — ситуация когда производитель генерирует сообщения быстрее чем консьюмер обрабатывает.
Признаки:
- Растущие очереди/lag
- Увеличение памяти брокера
- Timeouts
- Отказы в приеме сообщений
Стратегии борьбы:
1. Масштабирование консьюмеров
- Больше инстансов
- Больше партиций в Kafka
2. Rate limiting на producer
- Ограничение скорости отправки
- Circuit breaker
3. Буферизация
- Увеличить размер очередей
- Disk-based очереди
4. Backpressure propagation
- Сообщить producer о перегрузке
- Временная остановка
5. Load shedding
- Отбрасывание некритичных сообщений
Как версионировать схему сообщений? Что делать при изменении структуры сообщения, чтобы не сломать существующих консьюмеров?
Стратегии версионирования:
1. Backward compatibility (рекомендуется)
- Новые консьюмеры понимают старые сообщения
- Добавляем поля, не удаляем
- Опциональные поля с дефолтами
2. Forward compatibility
- Старые консьюмеры понимают новые сообщения
- Игнорируют неизвестные поля
3. Full compatibility
- Backward + Forward
- Самая безопасная стратегия
Практические подходы:
1. Schema versioning в сообщении
```json
{
"version": "2.0",
"eventType": "OrderCreated",
"data": { ... }
}
```
2. Content-Type headers
- application/vnd.order.v1+json
- application/vnd.order.v2+json
3. Разные topics/queues
- orders-v1
- orders-v2
Правила изменений:
- ✅ Добавление опциональных полей
- ✅ Добавление новых типов событий
- ❌ Удаление полей
- ❌ Изменение типов полей
- ❌ Переименование полей
Миграция:
1. Деплой консьюмеров с поддержкой v2
2. Начинаем отправлять v2
3. Когда все v1 обработаны — удаляем поддержку