Kafka в микросервисах: асинхронная развязка
Урок про роль Kafka как нервной системы микросервисной архитектуры — асинхронного клея между сервисами.
Асинхронная развязка — стиль взаимодействия микросервисов через события в Kafka вместо прямых синхронных вызовов, снижающий связность и повышающий устойчивость к сбоям.
Зачем это нужно
В микросервисах на синхронных HTTP-вызовах сервисы образуют хрупкую цепочку: заказ зовёт оплату, оплата зовёт склад, склад зовёт доставку. Упал один в цепочке — падает запрос целиком. Добавился новый потребитель события «заказ оплачен» — правим сервис оплаты. Kafka разрывает эти связи: сервис публикует факт, остальные реагируют сами.
Синхронно против асинхронно
| Синхронный вызов | Событие через Kafka | |
| Связность | высокая (знает адрес) | низкая (не знает читателей) |
| Падение получателя | ломает запрос | событие ждёт в логе |
| Новый потребитель | правка источника | просто подписка |
| Нагрузочный пик | каскад отказов | лог сглаживает (буфер) |
Развязка во времени
[ Сервис заказов ] --o ==Kafka: order.paid== --o [ Склад ]
\--o [ Доставка ]
\--o [ Лояльность ]
Склад на обслуживании? Событие ждёт в логе, дочитается потом.
Проблема двойной записи и outbox
Тонкий момент: сервис должен и сохранить заказ в свою БД, и опубликовать событие в Kafka. Сделать это «двумя отдельными операциями» опасно — между ними сервис может упасть: БД записана, событие не отправлено (или наоборот). Решение — паттерн transactional outbox: событие пишется в специальную таблицу outbox в той же транзакции БД, что и сам заказ; отдельный процесс (часто Debezium CDC) вычитывает outbox и публикует в Kafka.
BEGIN
INSERT заказ в orders
INSERT событие в outbox <- одна транзакция БД
COMMIT
-> Debezium читает outbox (WAL) -> публикует в Kafka
Как работает под капотом
Outbox решает «двойную запись», потому что и бизнес-данные, и событие фиксируются атомарно одной транзакцией реляционной БД — либо оба, либо ни одного. CDC-коннектор затем гарантированно (at-least-once) перенесёт записи outbox в Kafka, читая журнал транзакций. Дубликаты на этом шаге возможны, поэтому потребители делают идемпотентными по id события. В сумме это даёт надёжную публикацию событий без распределённой транзакции между БД и Kafka, которой в общем случае не существует.
Частые ошибки
- Двойная запись без outbox. Сбой между записью в БД и отправкой в Kafka рассинхронизирует системы.
- Асинхронность там, где нужен синхронный ответ. Для «дай цену сейчас» событие не подходит — это запрос-ответ.
- Раздача внутренней схемы БД через события. Публикуйте доменные факты, а не сырые строки таблиц — иначе снова жёсткая связность.
Итоги
- Kafka заменяет синхронные вызовы событиями: ниже связность, выше устойчивость, проще добавлять потребителей.
- Лог буферизует пики и переживает падения получателей — событие дождётся обработки.
- Transactional outbox решает двойную запись: событие и данные фиксируются атомарно, CDC доносит их в Kafka.