Як Kafka забезпечує відмовостійкість
Відмовостійкість (Fault Tolerance) в Apache Kafka будується на трьох фундаментальних рівнях:
🟢 Junior Level
30-секундна відповідь
Відмовостійкість (Fault Tolerance) в Apache Kafka будується на трьох фундаментальних рівнях:
- Реплікація даних на рівні брокерів: кожна партиція топіка дублюється на кількох незалежних серверах (
replication.factor >= 3). Один брокер виступає в ролі Лідера (приймає запити), а решта є Фолловерами (пасивно реплікують дані). - Автоматичний вибір нового лідера (Failover): при падінні брокера-лідера контролер кластера миттєво обирає нового лідера з пулу актуальних синхронних реплік (ISR).
- Стійкість клієнтів: продюсери автоматично повторюють надсилання при тимчасових збоях (
retries,acks=all, ідемпотентність), а консьюмери об’єднуються в групи (Consumer Groups), автоматично перерозподіляючи партиції при виході будь-якого екземпляра з ладу.
Наочна аналогія з життя
Уявіть банківське сховище з трьома клерками:
- Головний клерк (Лідер) записує операції у свій журнал, а двоє помічників (Фолоcontent/Фоловери) сидять поруч і синхронно копіюють кожен рядок у власні гросбухи.
- Якщо головний клерк раптово знепритомнів, один із помічників, чий журнал повністю збігається з головним (член активного 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) │
└───────────────────────┘ └───────────────────────┘
replication.factor = 3: дані зберігаються на 3 фізично різних брокерах (кластер витримує відмову до 2 брокерів без втрати даних).min.insync.replicas = 2: брокер заборонить запис, якщо в живих залишиться менше двох синхронізованих реплік. Це захищає систему від перетворення на єдину точку відмови.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, щоб виключити втрату даних та відкат оффсетів.
- Якщо всі члени ISR раптово вийшли з ладу, кластер може або чекати повернення репліки з ISR (
🔴 Senior Level
Механізм Leader Epoch та усунення проблеми усікання логів
До появи механізму Leader Epoch (KIP-101) Kafka покладалася виключно на High Watermark (HW) для відновлення після збоїв. Це призводило до двох катастрофічних сценаріїв при аварійному перезапуску брокерів:
- Log Truncation (Усікання лога): фолловер перезавантажувався, урізав свій лог до старого HW, після чого ставав лідером і назавжди стирав дані, які вже були закомічені.
- 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 захищається на двох рівнях:
- Raft Quorum (KRaft): контролер кластера обирається суворим кворумом більшості $\frac{N}{2} + 1$. Ізольована меншість не здатна обрати альтернативний контролер.
- 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.