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.
Проверьте себя
1. Что происходит с событием, если сервис-получатель временно недоступен?
AСобытие теряется
BОно ждёт в логе Kafka и будет обработано после восстановления получателя
CИсточник падает вместе с ним
DСобытие возвращается отправителю
2. Какую проблему решает паттерн transactional outbox?
AМедленную сеть
BДвойную запись: данные и событие фиксируются атомарно в одной транзакции БД, затем CDC шлёт их в Kafka
CПерекос партиций
DСжатие сообщений
3. Когда асинхронное событие НЕ подходит?
AДля уведомлений
BКогда нужен немедленный синхронный ответ (запрос-ответ, например «дай цену сейчас»)
CДля обновления аналитики
DДля развязки сервисов