📨 Раздел 15 · Вопрос #19

Как Kafka обеспечивает отказоустойчивость

Отказоустойчивость (Fault Tolerance) в Apache Kafka строится на трёх фундаментальных уровнях:


🟢 Junior Level

30-секундный ответ

Отказоустойчивость (Fault Tolerance) в Apache Kafka строится на трёх фундаментальных уровнях:

  1. Репликация данных на уровне брокеров: каждая партиция топика дублируется на нескольких независимых серверах (replication.factor >= 3). Один брокер выступает в роли Лидера (принимает запросы), а остальные являются Фолловерами (пассивно реплицируют данные).
  2. Автоматический выбор нового лидера (Failover): при падении брокера-лидера контроллер кластера мгновенно выбирает нового лидера из пула актуальных синхронных реплик (ISR).
  3. Устойчивость клиентов: продюсеры автоматически повторяют отправку при сбоях (retries, acks=all, идемпотентность), а консьюмеры объединяются в группы (Consumer Groups), автоматически перераспределяя партиции при выходе любого экземпляра из строя.

Наглядная аналогия из жизни

Представьте банковское хранилище с тремя клерками:

  • Главный клерк (Лидер) записывает операции в свой журнал, а двое помощников (Фолловеры) сидят рядом и синхронно копируют каждую строчку в свои журналы.
  • Если главный клерк внезапно потерял сознание, один из помощников, чей журнал полностью совпадает с главным (член ISR), немедленно занимает его место за кассой.
  • Клиенты (Продюсеры и Консьюмеры) ждут пару секунд, пока сменится табличка на окне, и продолжают работу без потери ни одной копейки.

Создание отказоустойчивого топика в CLI

# Создание топика с фактором репликации 3 на 3 партиции
kafka-topics.sh --bootstrap-server localhost:9092 --create \
  --topic critical-transactions \
  --partitions 3 \
  --replication-factor 3 \
  --config min.insync.replicas=2

🟡 Middle Level

«Золотой треугольник» отказоустойчивости Kafka

Для обеспечения гарантии отсутствия потери данных (Durability) на уровне продакшена используется связка трёх параметров:

          ┌───────────────────────────────────┐
          │      replication.factor = 3       │
          │    (3 копии данных на брокерах)   │
          └─────────────────┬─────────────────┘
                            │
            ┌───────────────┴───────────────┐
            ▼                               ▼
┌───────────────────────┐       ┌───────────────────────┐
│ min.insync.replicas=2 │       │       acks = all      │
│ (минимум 2 в ISR)     │◄─────►│ (подтверждение от всех│
│                       │       │      членов ISR)      │
└───────────────────────┘       └───────────────────────┘
  1. replication.factor = 3: данные сохраняются на 3 физически разных брокерах (кластер переживает отказ до 2 брокеров без потери данных).
  2. min.insync.replicas = 2: брокер запретит запись, если в живых останется меньше двух синхронизированных реплик. Это защищает систему от превращения в единую точку отказа.
  3. acks = all: продюсер считает сообщение отправленным только после того, как все участники текущего ISR подтвердили запись на диск.

Выбор лидера (Leader Election)

  • Clean Leader Election: контроллер назначает новым лидером реплику, входящую в ISR. Гарантируется нулевая потеря подтвержденных данных ($RPO = 0$).
  • Unclean Leader Election (unclean.leader.election.enable):
    • Если все члены ISR внезапно вышли из строя, кластер может либо ждать возвращения реплики из ISR (false, приоритет Consistency), либо назначить лидером отставшего фолловера не из ISR (true, приоритет Availability).
    • В продакшене unclean.leader.election.enable всегда устанавливают в false, чтобы исключить потерю данных и откат смещений.

🔴 Senior Level

Механизм Leader Epoch и устранение проблемы усечения логов

До появления механизма Leader Epoch (KIP-101) Kafka полагалась исключительно на High Watermark (HW) для восстановления после сбоев. Это приводило к двум катастрофическим сценариям при аварийном перезапуске брокеров:

  1. Log Truncation (Усечение лога): фолловер перезагружался, усекал свой лог до старого HW, после чего становился лидером и навсегда затирал данные, которые уже были закоммичены.
  2. Log Divergence (Расхождение логов): два брокера имели одинаковые смещения (offsets), но абсолютно разные сообщения внутри них.
Leader Epoch решает эту проблему:
Каждая смена лидера инкрементирует Leader Epoch (монотонно растущее число: 0, 1, 2...).
Каждая запись на диске маркируется [Epoch + Offset].
При переподключении фолловер не усекает свой лог до своего HW, а отправляет лидеру 
запрос LeaderEpochRequest("Какой твой последний оффсет для эпохи N?").
Лидер отвечает точным оффсетом окончания эпохи, гарантируя идеальное согласование логов.

Распределение реплик с учетом стоек (broker.rack)

Для предотвращения потери данных при аварии серверной стойки или целого дата-центра (Availability Zone в облаке) используется параметр broker.rack:

# server.properties на каждом брокере
broker.rack=eu-central-1a  # Брокер 1
broker.rack=eu-central-1b  # Брокер 2
broker.rack=eu-central-1c  # Брокер 3
  • Контроллер при создании топика распределяет реплики так, чтобы реплики одной партиции гарантированно попадали на разные стойки/зоны доступности.
  • Начиная с Kafka 2.4, консьюмеры могут читать данные из ближайшей реплики (fetch.max.wait.ms, rack-aware follower fetching), экономя межзональный сетевой трафик.

4 Tricky Questions

1. Почему одного High Watermark было недостаточно для предотвращения потери данных при сбоях, и как Leader Epoch исправил этот архитектурный недостаток?

Ответ:

  • High Watermark обновляется асинхронно: фолловер подтверждает запись лидеру, но узнает о новом значении HW только со следующим FetchResponse.
  • Сценарий сбоя без Leader Epoch: Брокер А (лидер) и Брокер Б (фолловер) получили сообщение с offset=1. Брокер Б записал его, но его HW остался равным 0. Если Брокер Б перезагрузится, он по старым правилам усекал свой лог до своего локального HW (0), удаляя сообщение 1. Если Брокер А тут же падал, Брокер Б становился лидером, и сообщение 1 бесследно исчезало, хотя продюсер уже получил подтверждение.
  • С Leader Epoch: после рестарта Брокер Б не трогает свой лог. Он запрашивает у Брокера А: «До какого смещения простирается эпоха 1?». Брокер А отвечает «offset=2», и Брокер Б ничего не усекает.

2. В кластере с RF = 3 и min.insync.replicas = 2 упало 2 брокера. Что происходит с операциями записи и чтения?

Ответ:

  • Запись (ProduceRequest с acks=all): полностью блокируется. Брокер-лидер возвращает ошибку NotEnoughReplicasException, так как текущий размер ISR равен 1, что меньше настроенного минимума 2.
  • Чтение (FetchRequest): продолжает работать. Оставшийся живой брокер продолжает отдавать консьюмерам все сообщения в пределах текущего зафиксированного High Watermark.
  • Восстановление: чтобы возобновить прием новых сообщений, необходимо либо поднять хотя бы один из упавших брокеров, либо административно временно уменьшить min.insync.replicas до 1 (что сопряжено с рисками потери данных при падении последнего брокера).

3. Как Kafka защищается от проблемы Split-Brain, если изолированный брокер-лидер считает, что он все еще управляет партицией?

Ответ: Kafka защищается на двух уровнях:

  1. Raft Quorum (KRaft): контроллер кластера выбирается строгим кворумом большинства $\frac{N}{2} + 1$. Изолированное меньшинство не может выбрать альтернативный контроллер.
  2. Generation/Leader Epoch в RPC-запросах: когда кворум контроллеров назначает нового лидера, эпоха партиции инкрементируется. Если старый изолированный брокер («зомби-лидер») попытается отправить запросы репликации или принять запросы от клиентов, новые клиенты и брокеры отклонят их с ошибкой FencedLeaderEpochException или NotLeaderOrFollowerException.

4. В чем разница в устойчивости между репликацией внутри одного кластера Kafka и межкластерной репликацией через MirrorMaker 2?

Ответ:

  • Внутрикластерная репликация:
    • Является квази-синхронной (при acks=all запись фиксируется только после репликации на кворум брокеров).
    • Обеспечивает $RPO = 0$ (Zero Data Loss) при соблюдении min.insync.replicas.
    • Оффсеты строго детерминированы.
  • Межкластерная репликация (MirrorMaker 2):
    • Является полностью асинхронной.
    • Строится на стандартных Consumer и Producer API поверх WAN-сети.
    • Всегда имеет задержку репликации ($RPO > 0$). При катастрофическом падении основного дата-центра сообщения, находящиеся в сетевом буфере MirrorMaker, будут потеряны.
    • Оффсеты сообщений в целевом кластере не совпадают с исходными (трансляция оффсетов осуществляется через вспомогательный топик heartbeats / checkpoints).

🎯 Шпаргалка для интервью

Главное об отказоустойчивости Kafka

  • Базовый стандарт: replication.factor = 3, min.insync.replicas = 2, acks = all.
  • RPO = 0 (No Data Loss): достигается благодаря ISR и unclean.leader.election.enable = false.
  • Leader Epoch: механизм согласования логов, полностью исключающий расхождение и фантомное усечение данных при падениях брокеров.
  • Rack Awareness: распределение копий партиций по физическим стойкам и облачным зонам доступности через broker.rack.
  • Клиентская защита: идемпотентные продюсеры (PID + Sequence Number) устраняют дубликаты при retry после сетевых сбоев.

Матрица доступности при отказе брокеров (RF=3, min.insync=2)

| Состояние кластера | Размер ISR | Доступность на запись | Доступность на чтение | Риск потери данных | | :— | :— | :— | :— | :— | | Все 3 брокера работают | 3 | ✅ Полная | ✅ Полная | 🟢 0% | | Упал 1 брокер | 2 | ✅ Полная | ✅ Полная | 🟢 0% | | Упало 2 брокера | 1 | ❌ Заблокирована | ✅ Доступна до HW | 🟡 Только если упадет 3-й | | Упали все 3 брокера | 0 | ❌ Недоступна | ❌ Недоступна | 🔴 Зависит от сохранности дисков |

Красные флаги на собеседовании (Чего говорить нельзя)

  • ❌ «Kafka гарантирует стопроцентное отсутствие потери данных при любых настройках из коробки» — по умолчанию acks=1 (до Kafka 3.0), при котором падение лидера ведет к потере данных.
  • ❌ «Зеркалирование через MirrorMaker 2 обеспечивает синхронную репликацию без потери сообщений между дата-центрами» — MirrorMaker всегда асинхронен, $RPO > 0$.
  • ❌ «Если в ISR остался 1 брокер, консьюмеры перестают вычитывать топик» — чтение закоммиченных данных никогда не блокируется из-за падения размера ISR.

Связанные вопросы