RabbitMQ проти Kafka: відмовостійкість та висока доступність

RabbitMQ проти Kafka: відмовостійкість та висока доступність

В минулій статті ми розглянули кластеризацію RabbitMQ для забезпечення відмовостійкості та високої доступності. Тепер глибоко покопаємось в Apache Kafka.

Тут одиницею реплікації є поділ (partition). У кожного топіка один або кілька розділів. У кожному розділі є лідер із фоловерами або без них. При створенні топіка вказується кількість розділів та коефіцієнт реплікації. Звичайне значення 3, це означає три репліки: один лідер і два фоловери.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 1. Чотири розділи розподілено між трьома брокерами

Всі запити на читання та запис надходять до лідера. Фоловери періодично надсилають лідеру запити на отримання останніх повідомлень. Споживачі ніколи не звертаються до фоловерів, останні існують тільки для надмірності та відмовостійкості.

RabbitMQ проти Kafka: відмовостійкість та висока доступність

Збій розділу

Коли відвалюється брокер, часто виходять з ладу лідери кількох розділів. У кожному їх лідером стає фоловер з іншого вузла. Насправді це не завжди так, оскільки впливає ще фактор синхронізації: чи є синхронізовані фоловери, а якщо ні, то чи дозволено перехід на несинхронізовану репліку. Але поки не ускладнюватимемо.

З мережі йде брокер 3 - і розділу 2 обирається новий лідер на брокері 2.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 2. Брокер 3 вмирає, і його фоловер на брокері 2 обирається новим лідером розділу 2

Потім йде брокер 1 і розділ 1 теж втрачає свого лідера, роль якого переходить до брокера 2.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 3. Залишився один брокер. Усі лідери знаходяться на одному брокері з нульовою надмірністю

Коли брокер 1 повертається в мережу, додає чотирьох фоловерів, забезпечуючи деяку надмірність кожному розділу. Але всі лідери, як і раніше, залишилися на брокері 2.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 4. Лідери залишаються на брокері

Коли піднімається брокер 3, ми повертаємося до трьох реплік на розділ. Але всі лідери, як і раніше, на брокері 2.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 5. Незбалансоване розміщення лідерів після відновлення брокерів 1 та 3

Kafka має інструмент для більш якісного перебалансування лідерів, ніж RabbitMQ. Там доводилося використовувати сторонній плагін або скрипт, який змінював політики для міграції головного вузла за рахунок зниження надмірності під час міграції. Крім того, для великих черг доводилося змиритись з недоступністю під час синхронізації.

Kafka має концепцію «переважних реплік» на роль лідера. Коли створюються розділи топіка, Kafka намагається рівномірно розподілити лідерів по вузлах і відзначає цих перших лідерів як кращих. Згодом через перезавантаження серверів, збоїв та порушення зв'язності лідери можуть опинитися на інших вузлах, як у вищеописаному крайньому випадку.

Щоб виправити це, Kafka пропонує два варіанти:

  • Опція auto.leader.rebalance.enable=true дозволяє вузлу контролера автоматично перепризначити лідерів на переважні репліки і цим відновити рівномірне розподіл.
  • Адміністратор може запустити скрипт kafka-preferred-replica-election.sh для перепризначення вручну.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 6. Репліки після перебалансування

Це була спрощена версія збою, але реальність складніша, хоча нічого надто складного тут немає. Все зводиться до синхронізованих реплік (In-Sync Replicas, ISR).

Синхронізовані репліки (ISR)

ISR - це набір реплік розділу, який вважається синхронізованим (in-sync). Тут є лідер, а фоловерів може не бути. Фолловер вважається синхронізованим, якщо він зробив точні копії всіх повідомлень лідера до закінчення інтервалу replica.lag.time.max.ms.

Фоловер видаляється з набору ISR, якщо він:

  • не зробив запит на вибірку за інтервал replica.lag.time.max.ms (вважається мертвим)
  • не встиг оновитися за інтервал replica.lag.time.max.ms (вважається повільним)

Фоловери роблять запити на вибірку в інтервалі replica.fetch.wait.max.ms, Який за замовчуванням становить 500 мс.

Щоб чітко пояснити мету ISR, потрібно подивитися на підтвердження від виробника (producer) та деякі сценарії відмови. Виробники можуть вибрати, коли брокер відправляє підтвердження:

  • acks=0, підтвердження не надсилається
  • acks=1, підтвердження відправляється після того, як лідер записав повідомлення у свій локальний лог
  • acks=all, підтвердження надсилається після того, як усі репліки в ISR записали повідомлення в локальні логи

У термінології Kafka, якщо ISR зберіг повідомлення, відбувається його «комміт». Acks=all – найбезпечніший варіант, але й додаткова затримка. Розглянемо два приклади відмови і різні опції 'acks' взаємодіють з концепцією ISR.

Acks=1 та ISR

У цьому прикладі ми побачимо, що якщо лідер не чекає на збереження кожного повідомлення від усіх фоловерів, то при збої лідера можлива втрата даних. Перехід до несинхронізованого фоловеру може бути дозволено або заборонено налаштуванням unclean.leader.election.enable.

У цьому прикладі виробника встановлено значення acks=1. Розділ розподілений за всіма трьома брокерами. Брокер 3 відстає, він синхронізувався з лідером вісім секунд тому і зараз відстає на 7456 повідомлень. Брокер 1 відстав лише на одну секунду. Наш виробник відправляє повідомлення і швидко отримує назад ack, без зверху на повільних або мертвих фоловерів, яких лідер не чекає.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 7. ISR з трьома репліками

Брокер 2 виходить з ладу, і виробник отримує помилку з'єднання. Після переходу лідерства до брокера 1 ми втрачаємо 123 повідомлення. Фоловер на брокері 1 входив до ISR, але не повністю синхронізувався з лідером, коли той упав.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 8. При збої губляться повідомлення

У конфігурації bootstrap.servers у виробника перераховано кілька брокерів, і він може запитати іншого брокера, який став новим лідером розділу. Потім він встановлює з'єднання з брокером 1 і продовжує надсилати повідомлення.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 9. Надсилання повідомлень відновлюється після короткої перерви

Брокер 3 ще більше відстає. Він робить запити на вибірку, але не може синхронізуватись. Це може бути пов'язане з повільним мережевим з'єднанням між брокерами, проблемою зберігання і т. д. Він видаляється з ISR. Тепер ISR складається з однієї репліки – лідера! Виробник продовжує надсилати повідомлення та отримувати підтвердження.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 10. Фоловер на брокері 3 видаляється з ISR

Брокер 1 падає, і роль лідера переходить до брокера 3 із втратою 15286 повідомлень! Виробник отримує повідомлення про помилку підключення. Перехід до лідера за межами ISR був можливий лише через налаштування unclean.leader.election.enable=true. Якщо вона встановлена ​​в false, то перехід не відбувся, проте запити читання і записи були відхилені. У цьому випадку ми чекаємо на повернення брокера 1 з його недоторканими даними в репліці, яка знову візьме на себе лідерство.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 11. Брокер 1 падає. При збої втрачається велика кількість повідомлень

Виробник встановлює з'єднання з останнім брокером і бачить, що тепер лідер розділу. Він починає надсилати повідомлення брокеру 3.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 12. Після короткої перерви повідомлення знову надсилаються до розділу 0

Ми бачили, що крім коротких переривань на встановлення нових з'єднань та пошук нового лідера, виробник постійно надсилав повідомлення. Така конфігурація забезпечує доступність з допомогою узгодженості (безпеки даних). Kafka втратив тисячі повідомлень, але продовжував приймати нові записи.

Acks=all та ISR

Давайте повторимо цей сценарій ще раз, але з acks=all. Затримка брокера 3 у середньому чотири секунди. Виробник відправляє повідомлення з acks=all, і тепер не отримує швидкої відповіді. Лідер чекає, поки повідомлення збережуть усі репліки в ISR.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 13. ISR із трьома репліками. Одна працює повільно, що призводить до затримки запису

Після чотирьох секунд додаткової затримки брокер 2 відправляє ack. Усі репліки тепер повністю оновлено.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 14. Всі репліки зберігають повідомлення і надсилається ack

Брокер 3 відстає ще більше і видаляється з ISR. Затримка значно зменшується, оскільки в ISR не залишилося повільних реплік. Брокер 2 тепер чекає лише брокера 1, а в нього середній лаг 500 мс.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 15. Репліка на брокері 3 видаляється з ISR

Потім падає брокер 2 і лідерство переходить до брокера 1 без втрати повідомлень.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 16. Брокер 2 падає

Виробник знаходить нового лідера і починає надсилати йому повідомлення. Затримка зменшується, адже тепер ISR складається з однієї репліки! Тому опція acks=all не додає надмірності.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 17. Репліка на брокері 1 перебирає лідерство без втрати повідомлень

Потім падає брокер 1 і лідерство переходить до брокера 3 з втратою 14238 повідомлень!

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 18. Брокер 1 вмирає, а перехід лідерства з налаштуванням unclean призводить до великої втрати даних

Ми могли б не встановлювати опцію unclean.leader.election.enable на значення правда. За умовчанням воно одно false. Налаштування acks=all с unclean.leader.election.enable=true забезпечує доступність із деякою додатковою безпекою даних. Але, як ви бачите, ми все ще можемо втратити повідомлення.

Але що якщо ми хочемо збільшити безпеку даних? Можна поставити unclean.leader.election.enable = falseАле це не обов'язково захистить нас від втрати даних. Якщо лідер упав жорстко і забрав із собою дані, то повідомлення, як і раніше, втрачені, плюс втрачається доступність, поки адміністратор не відновить ситуацію.

Краще гарантувати надмірність усіх повідомлень, а в іншому випадку відмовитись від запису. Тоді хоча б з погляду брокера втрата даних можлива лише за двох чи більше одночасних збоях.

Acks=all, min.insync.replicas та ISR

З конфігурацією топіка min.insync.replicas ми підвищуємо рівень безпеки даних. Давайте ще раз пройдемося за останньою частиною минулого сценарію, але цього разу з min.insync.replicas=2.

Отже, брокер 2 має лідера репліки, а фоловер на брокері 3 видалено з ISR.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 19. ISR із двох реплік

Брокер 2 падає, а лідерство переходить до брокера 1 без втрати повідомлень. Але тепер ISR складається лише з однієї репліки. Це не відповідає мінімальній кількості для отримання записів, і тому брокер відповідає на спробу запису помилкою NotEnoughReplicas.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 20. Число ISR на один нижче, ніж зазначено в min.insync.replicas

Ця конфігурація жертвує доступністю задля узгодженості. Перш ніж підтвердити повідомлення, ми гарантуємо, що записується принаймні на дві репліки. Це дає виробнику набагато більшу впевненість. Тут втрата повідомлень можлива лише у разі одночасного збою двох реплік у короткий інтервал, доки повідомлення не репліковане додатковому фоловеру, що є малоймовірним. Але якщо ви суперпараноїк, то можете встановити коефіцієнт реплікації 5, а min.insync.replicas на 3. Тут відразу три брокери повинні впасти одночасно, щоб втратити запис! Звісно, ​​за таку надійність ви заплатите додатковою затримкою.

Коли доступність необхідна для безпеки даних

як і в випадку з RabbitMQіноді доступність необхідна для безпеки даних. Вам потрібно подумати ось про що:

  • Чи може паблішер просто повернути помилку, а вища служба або користувач повторити спробу пізніше?
  • Чи може паблішер зберегти повідомлення локально або в базі даних, щоб повторити спробу пізніше?

Якщо відповідь негативна, то оптимізація доступності підвищує безпеку даних. Ви втратите менше даних, якщо виберіть доступність замість відмови від запису. Таким чином, все зводиться до пошуку балансу, а рішення залежить від конкретної ситуації.

Сенс ISR

Набір ISR дозволяє вибрати оптимальний баланс між безпекою даних та затримкою. Наприклад, забезпечити доступність за умов збою більшості реплік, мінімізуючи вплив мертвих чи повільних реплік з погляду затримки.

Ми самі обираємо значення replica.lag.time.max.ms відповідно до своїх потреб. По суті цей параметр означає, яку затримку ми готові прийняти при acks=all. Значення за промовчанням – десять секунд. Якщо вам це занадто довго, можете її зменшити. Тоді зросте частота змін в ISR, оскільки фоловери частіше видалятимуться і додаватися.

У RabbitMQ просто набір дзеркал, які потрібно реплікувати. Повільні дзеркала привносять додаткову затримку, а відгуку мертвих дзеркал можна чекати до закінчення часу пакетів, які перевіряють доступність кожного вузла (net tick). ISR – цікавий спосіб уникнути цих проблем із збільшенням затримки. Але ми ризикуємо втратити надмірність, оскільки ISR може скоротитися лише до лідера. Щоб уникнути цього ризику, використовуйте налаштування min.insync.replicas.

Гарантія підключення клієнтів

В налаштуваннях bootstrap.servers виробника та споживача можна вказати кілька брокерів для підключення клієнтів. Ідея в тому, що при відключенні одного вузла залишається кілька запасних, з якими клієнт може відкрити з'єднання. Це не обов'язково лідери розділів, а просто плацдарм для початкового завантаження. Клієнт може запитати їх, на якому вузлі розміщується лідер розділу для читання/запису.

У RabbitMQ клієнти можуть підключатися до будь-якого вузла, а внутрішня маршрутизація надсилає запит куди треба. Це означає, що ви можете встановити перед RabbitMQ балансувальник навантаження. Kafka вимагає, щоб клієнти підключалися до вузла, де розміщується лідер відповідного розділу. У такій ситуації балансувальник навантаження не поставити. перелік bootstrap.servers критично важливий, щоб клієнти могли звертатися до потрібних вузлів та знаходити їх після збою.

Архітектура консенсусу Kafka

Досі ми не розглянули, як кластер дізнається про падіння брокера та як вибирається новий лідер. Щоб зрозуміти, як Kafka працює з мережевими розділами, потрібно спочатку зрозуміти архітектуру консенсусу.

Кожен кластер Kafka розгортається разом із кластером Zookeeper — це служба розподіленого консенсусу, яка дозволяє системі досягти консенсусу у певного заданого стану із пріоритетом узгодженості над доступністю. Для схвалення операцій читання та запису потрібна згода більшості вузлів Zookeeper.

Zookeeper зберігає стан кластера:

  • Список топіків, розділів, конфігурацію, поточні репліки лідера, кращі репліки.
  • Члени кластеру. Кожен брокер пінг в кластер Zookeeper. Якщо той не отримує пінг протягом заданого періоду часу, Zookeeper записує брокера в недоступні.
  • Вибір основного та запасного вузлів для контролера.

Вузол контролера - один із брокерів Kafka, який відповідає за обрання лідерів реплік. Zookeeper надсилає контролеру повідомлення про членство в кластері та зміни топіка, і контролер повинен діяти відповідно до цих змін.

Наприклад, візьмемо новий топік із десятьма розділами та коефіцієнтом реплікації 3. Контролер повинен вибрати лідера кожного розділу, намагаючись оптимально розподілити лідерів між брокерами.

Для кожного розділу контролер:

  • оновлює інформацію в Zookeeper про ISR та лідера;
  • надсилає команду LeaderAndISRCommand кожному брокеру, який розміщує репліку цього розділу, інформуючи брокерів про ISR та лідера.

Коли падає брокер із лідером, Zookeeper відправляє повідомлення контролеру, і той обирає нового лідера. Знову ж таки, контролер спочатку оновлює Zookeeper, а потім відправляє команду кожному брокеру, повідомляючи їх про зміну лідерства.

Кожен лідер відповідає за набір ISR. Налаштування replica.lag.time.max.ms визначає хто туди увійде. При зміні ISR лідер передає Zookeeper нову інформацію.

Zookeeper завжди інформований про будь-які зміни, щоб у разі збою керівництво плавно перейшло до нового лідера.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 21. Консенсус Kafka

Протокол реплікації

Розуміння деталей реплікації допомагає краще зрозуміти потенційні сценарії втрати даних.

Запити на вибірку, Log End Offset (LEO) та Highwater Mark (HW)

Ми розглянули, що фоловери періодично надсилають лідеру запити на вибірку (fetch). Інтервал за промовчанням становить 500 мс. Це відрізняється від RabbitMQ тим, що у RabbitMQ реплікація ініціюється не дзеркалом черги, а майстром. Майстер пушить зміни на дзеркала.

Лідер і всі фоловери зберігають зміщення кінця лога (Log End Offset, LEO) та позначку Highwater (HW). Позначка LEO зберігає зміщення останнього повідомлення у локальній репліці, а HW – зміщення останнього комміту. Пам'ятайте, що для статусу "комміт" повідомлення має бути збережене у всіх репліках ISR. Це означає, що LEO зазвичай трохи випереджає HW.

Коли лідер отримує повідомлення, він зберігає локально. Фолловер запитує на вибірку, передавши свій LEO. Потім лідер відправляє пакет повідомлень, починаючи з LEO, а також передає поточний HW. Коли лідер отримує інформацію, що всі репліки зберегли повідомлення із заданим усуненням, він переміщає позначку HW. Тільки лідер може перемістити HW, і так всі фоловери дізнаються про поточне значення у відповідях на свій запит. Це означає, що фоловери можуть відставати від лідера і за повідомленнями, і щодо знання HW. Споживачі отримують повідомлення лише до HW.

Зверніть увагу, що «збережений» означає записаний в пам'ять, а не на диск. Для продуктивності Kafka виконує синхронізацію на диск з певним інтервалом. RabbitMQ теж має такий інтервал, але він відправить підтвердження паблішеру тільки після того, як майстер і всі дзеркала записали повідомлення на диск. Розробники Kafka з міркувань продуктивності вирішили відправляти ack, щойно повідомлення записано на згадку. Kafka робить ставку на те, що надмірність компенсує ризик короткострокового зберігання підтверджених повідомлень лише у пам'яті.

Збій лідера

Коли падає лідер, Zookeeper повідомляє контролера, і той вибирає нову репліку лідера. Новий лідер встановлює нову позначку HW відповідно до свого LEO. Потім інформацію про нового лідера одержують фоловери. Залежно від версії Kafka, фоловер вибере один із двох сценаріїв:

  1. Злічить локальний лог до відомого HW і надішле нового лідера запит на повідомлення після цієї позначки.
  2. Відправить лідеру запит, щоб дізнатися HW на момент його обрання лідером, а потім усіче лог до цього усунення. Потім почне робити періодичні запити вибірку, починаючи з цього усунення.

Фоловер може знадобитися урізати лог з наступних причин:

  • Коли відбувається збій лідера, перший фоловер із набору ISR, зареєстрований у Zookeeper, виграє вибори і стає лідером. Усі фоловери в ISR, хоч і вважаються «синхронізованими», могли і не отримати від колишнього лідера копії всіх повідомлень. Цілком можливо, що обраний фолловер не має найактуальнішої копії. Kafka гарантує, що між репліками немає розбіжності. Таким чином, щоб уникнути розбіжності, кожен фоловер повинен усікти свій лог до значення HW нового лідера на момент його обрання. Це ще одна причина, чому налаштування acks=all така важлива для узгодженості.
  • Повідомлення періодично записуються на диск. Якщо всі вузли кластера відмовили одночасно, на дисках збережуться репліки з різним усуненням. Цілком можливо, що коли брокери знову повернуться до мережі, новий лідер, який буде обраний, опиниться позаду своїх фоловерів, тому що він зберігся на диск раніше за інших.

Возз'єднання з кластером

При возз'єднанні з кластером репліки роблять так само, як і при збої лідера: перевіряють репліку лідера і усікають свій бал до його HW (на момент обрання). Для порівняння, RabbitMQ однаково розцінює з'єднані вузли як нові. В обох випадках брокер відкидає будь-який стан. Якщо використовується автоматична синхронізація, то майстер повинен реплікувати абсолютно весь вміст у нове дзеркало способом «і нехай весь світ зачекає». Під час цієї операції майстер не приймає жодних операцій читання чи запису. Такий підхід створює проблеми у великих чергах.

Kafka - це розподілений лог, і в цілому він зберігає більше повідомлень, ніж черга RabbitMQ, де дані видаляються із черги після їх читання. Активні черги мають залишатися відносно невеликими. Але Kafka — це лог із власною політикою зберігання, яка може встановити термін у дні чи тижні. Підхід із блокуванням черги та повною синхронізацією абсолютно неприйнятний для розподіленого лога. Натомість фоловери Kafka просто обрізають свій лог до HW лідера (на момент його обрання) у тому випадку, якщо їх копія випереджає лідера. У більш ймовірному випадку, коли фоловер знаходиться позаду, він просто починає робити запити на вибірку, починаючи зі свого поточного LEO.

Нові або возз'єднані фоловери починають за межами ISR і не беруть участь у комітах. Вони просто працюють поруч із групою, отримуючи повідомлення так швидко, як можуть, доки не наздоженуть лідера і не увійдуть до ISR. Тут немає блокування та не потрібно викидати всі свої дані.

Порушення зв'язності

У Kafka більше компонентів, ніж у RabbitMQ, тому тут складніший набір поведінки, коли в кластері порушується зв'язність. Але Kafka спочатку проектувалася для кластерів, тому рішення дуже добре продумані.

Нижче наведено кілька сценаріїв порушення зв'язності:

  • Сценарій 1. Фолловер не бачить лідера, але бачить Zookeeper.
  • Сценарій 2. Лідер не бачить жодного фоловеру, але все ще бачить Zookeeper.
  • Сценарій 3. Фоловер бачить лідера, але не бачить Zookeeper.
  • Сценарій 4. Лідер бачить фоловерів, але не бачить Zookeeper.
  • Сценарій 5. Фолловер повністю відокремлений від інших вузлів Kafka, і від Zookeeper.
  • Сценарій 6. Лідер повністю відділений від інших вузлів Kafka, і від Zookeeper.
  • Сценарій 7. Вузол контролера Kafka не бачить інший вузол Kafka.
  • Сценарій 8. Контролер Kafka не бачить Zookeeper.

Для кожного сценарію передбачено свою поведінку.

Сценарій 1. Фолловер не бачить лідера, але все ще бачить Zookeeper

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 22. Сценарій 1. ISR із трьох реплік

Порушення зв'язності відокремлює брокера 3 від брокерів 1 та 2, але не від Zookeeper. Брокер 3 більше не може надсилати запити на вибірку. По закінченню часу replica.lag.time.max.ms він видаляється з ISR і бере участь у коммітах повідомлень. Як тільки зв'язок відновлено, він відновить запити на вибірку і приєднається до ISR, коли наздожене лідера. Zookeeper продовжуватиме отримувати пінги і вважати, що брокер живий і здоровий.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 23. Сценарій 1. Брокер видаляється з ISR, якщо від нього не отримано запит на вибірку протягом інтервалу replica.lag.time.max.ms

Немає ніякого логічного поділу (split-brain) чи припинення вузла, як у RabbitMQ. Натомість зменшується надмірність.

Сценарій 2. Лідер не бачить жодного фолловеру, але все ще бачить Zookeeper

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 24. Сценарій 2. Лідер і два фоловери

Порушення мережевої зв'язності відокремлює лідера від фоловерів, але брокер все ще бачить Zookeeper. Як і в першому сценарії, ISR стискається, але цього разу лише до лідера, оскільки всі фоловери перестають надсилати запити на вибірку. Знову ж таки, немає ніякого логічного поділу. Натомість відбувається втрата надмірності нових повідомлень, поки зв'язність не відновиться. Zookeeper продовжує отримувати пінги і вважає, що брокер живий і здоровий.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 25. Сценарій 2. ISR стиснувся лише до лідера

Сценарій 3. Фоловер бачить лідера, але не бачить Zookeeper

Фоловер відокремлюється від Zookeeper, але не від брокера з лідером. В результаті фоловер продовжує робити запити вибірки та бути членом ISR. Zookeeper більше не отримує пінги та реєструє падіння брокера, але оскільки це тільки фолловер, немає жодних наслідків після відновлення.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 26. Сценарій 3. Фолловер продовжує надсилати лідерові запити на вибірку

Сценарій 4. Лідер бачить фоловерів, але не бачить Zookeeper

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 27. Сценарій 4. Лідер і два фоловери

Лідер відокремлений від Zookeeper, але не від брокерів із фоловерами.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 28. Сценарій 4. Лідер ізольований від Zookeeper

Через деякий час Zookeeper зареєструє падіння брокера та повідомить про це контролер. Той вибере серед фоловерів нового лідера. Однак вихідний лідер продовжуватиме думати, що він є лідером і продовжуватиме приймати записи з acks=1. Фоловери більше не надсилають йому запити на вибірку, тому він вважатиме їх мертвими і спробувати стиснути ISR до самого себе. Але оскільки він не має підключення до Zookeeper, він не зможе це зробити, і в цей момент відмовиться від подальшого прийому записів.

Повідомлення acks=all не отримають підтвердження, тому що спочатку ISR включає всі репліки, а повідомлення до них не доходять. Коли початковий лідер спробує видалити їх із ISR, він зможе цього зробити і взагалі перестане приймати будь-які повідомлення.

Клієнти незабаром помічають зміну лідера та починають відправляти записи на новий сервер. Як тільки мережа відновлюється, вихідний лідер бачить, що він більше не лідер і обрізає свій лог до значення HW, яке було у нового лідера в момент збою, щоб уникнути розбіжності логів. Потім він почне надсилати запити на вибір нового лідера. Втрачаються всі записи вихідного лідера, не репліковані новому лідеру. Тобто будуть втрачені повідомлення, які не підтвердили початковий лідер у ті кілька секунд, коли працювало два лідери.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 29. Сценарій 4. Лідер на брокері 1 стає фолловером після відновлення мережі

Сценарій 5. Фолловер повністю відокремлений і від інших вузлів Kafka, і від Zookeeper

Фолловер повністю ізольований і з інших вузлів Kafka, і з Zookeeper. Він просто видаляється з ISR, поки мережа не відновиться, а потім наздоганяє решту.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 30. Сценарій 5. Ізольований фоловер видаляється з ISR

Сценарій 6. Лідер повністю відділений від інших вузлів Kafka, і від Zookeeper

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 31. Сценарій 6. Лідер і два фоловери

Лідер повністю ізольований від своїх фоловерів, контролера та Zookeeper. Протягом короткого періоду він продовжить приймати записи з acks=1.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 32. Сценарій 6. Ізоляція лідера від інших вузлів Kafka та Zookeeper

Не отримавши запитів після закінчення replica.lag.time.max.msВін спробує стиснути ISR до самого себе, але не зможе цього зробити, оскільки немає зв'язку з Zookeeper, тоді він припинить приймати записи.

Тим часом Zookeeper відзначить ізольованого брокера як мертвого, а контролер вибере нового лідера.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 33. Сценарій 6. Два лідери

Вихідний лідер може приймати записи протягом кількох секунд, але потім перестає приймати будь-які повідомлення. Клієнти оновлюються кожні 60 секунд з останніми метаданими. Вони будуть проінформовані про зміну лідера та почнуть надсилати записи новому лідерові.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 34. Сценарій 6. Виробники перемикаються на нового лідера

Буде втрачено всі підтверджені записи, зроблені вихідним лідером з моменту втрати зв'язності. Як тільки мережу відновлено, вихідний лідер через Zookeeper виявить, що більше не є лідером. Потім усіче свій лог до HW нового лідера на момент обрання і почне надсилати запити як фоловер.

RabbitMQ проти Kafka: відмовостійкість та висока доступність
Мал. 35. Сценарій 6. Вихідний лідер стає фоловером після відновлення зв'язності мережі

У цій ситуації протягом короткого періоду може спостерігатися логічне поділ, але якщо acks=1 и min.insync.replicas 1. Логічне поділ автоматично завершується або після відновлення мережі, коли вихідний лідер розуміє, що він більше не лідер, або коли всі клієнти розуміють, що лідер змінився і починають писати новому лідеру - залежно від того, що станеться раніше. У будь-якому випадку відбудеться втрата деяких повідомлень, але тільки з acks=1.

Існує інший варіант цього сценарію, коли перед поділом мережі фоловери відстали, а лідер стиснув ISR до одного себе. Потім він ізолюється через втрату зв'язності. Обирається новий лідер, але початковий лідер продовжує приймати записи, навіть acks=allтому що в ISR крім нього нікого немає. Ці записи будуть втрачені після відновлення мережі. Єдиний спосіб уникнути такого варіанту. min.insync.replicas = 2.

Сценарій 7. Вузол контролера Kafka не бачить інший вузол Kafka

Загалом після втрати зв'язку з вузлом Kafka контролер не зможе передати йому жодної інформації щодо зміни лідера. У найгіршому випадку це призведе до короткострокового логічного поділу, як у сценарії 6. Найчастіше брокер просто не стане кандидатом на лідерство у разі відмови останнього.

Сценарій 8. Контролер Kafka не бачить Zookeeper

Від контролера, що відвалився, Zookeeper не отримає пінгу і вибере контролером новий вузол Kafka. Вихідний контролер може продовжувати уявляти себе таким, але він не отримує повідомлення від Zookeeper, тому він не матиме жодних завдань для виконання. Як тільки мережа відновиться, він зрозуміє, що більше не контролер, а став звичайним вузлом Kafka.

Висновки зі сценаріїв

Ми бачимо, що втрата зв'язності фоловерів не призводять до втрати повідомлень, а просто тимчасово зменшує надмірність, доки мережа не відновиться. Це, звичайно, може призвести до втрати даних, якщо втрачено один або кілька вузлів.

Якщо через втрату зв'язності лідер відокремився від Zookeeper, це може призвести до втрати повідомлень з acks=1. Відсутність зв'язку з Zookeeper викликає короткочасний логічний поділ із двома лідерами. Цю проблему вирішує параметр acks=all.

Параметр min.insync.replicas дві або більше реплік дає додаткові гарантії, що такі короткострокові сценарії не призведуть до втрати повідомлень, як у сценарії 6.

Резюме зі втрати повідомлень

Перерахуємо всі способи, як можна втратити дані в Kafka:

  • Будь-який збій лідера, якщо повідомлення підтверджувалися за допомогою acks=1
  • Будь-який нечистий (unclean) перехід лідерства, тобто на фолловері за межами ISR, навіть з acks=all
  • Ізоляція лідера від Zookeeper, якщо повідомлення підтверджувалися за допомогою acks=1
  • Повна ізоляція лідера, який вже стиснув групу ISR до самого себе. Будуть втрачені всі повідомлення, навіть acks=all. Це вірно лише в тому випадку, якщо min.insync.replicas=1.
  • Одночасні збої всіх вузлів розділу. Оскільки повідомлення підтверджуються з пам'яті, деякі можуть ще не записуватись на диск. Після перезавантаження серверів деяких повідомлень може не вистачати.

Нечистих переходів лідерства можна уникнути, або заборонивши їх, або забезпечивши надмірність щонайменше двох. Найбільш міцна конфігурація - це поєднання acks=all и min.insync.replicas більше ніж 1.

Пряме порівняння надійності RabbitMQ та Kafka

Для забезпечення надійності та високої доступності обидві платформи реалізують систему первинної та вторинної реплікації. Однак у RabbitMQ є ахіллесова п'ята. При з'єднанні після збою вузли відкидають дані, а синхронізація блокується. Цей подвійний удар ставить під сумнів довговічність великих черг у RabbitMQ. Вам доведеться упокоритися або зі скороченням надмірності, або з тривалими блокуваннями. Зниження надмірності збільшує ризик масової втрати даних. Але якщо черги невеликі, то для забезпечення надмірності з короткими періодами недоступності (кілька секунд) можна впоратися за допомогою повторних спроб підключення.

У Kafka немає такої проблеми. Вона відкидає дані лише з точки розбіжності лідера та фоловеру. Усі загальні дані зберігаються. З іншого боку, реплікація не блокує систему. Лідер продовжує приймати записи, доки новий фоловер його наздоганяє, отже для девопсов приєднання чи возз'єднання кластера стає очевидним завданням. Звичайно, як і раніше, залишаються проблеми, такі як пропускна здатність мережі при реплікації. Якщо одночасно додаються кілька фоловерів, можна зіткнутися з лімітом пропускної здатності.

RabbitMQ перевершує Kafka у надійності при одночасному збої кількох серверів у кластері. Як ми вже говорили, RabbitMQ відправляє паблішеру підтвердження лише після запису повідомлення на диск у майстра та всіх дзеркал. Але це додає додаткову затримку з двох причин:

  • fsync кожні кілька сотень мілісекунд
  • Збій дзеркала можуть помітити лише після закінчення життя пакетів, які перевіряють доступність кожного вузла (net tick). Якщо дзеркало гальмує чи впало, це додає затримки.

Kafka робить ставку на те, що якщо повідомлення зберігається на кількох вузлах, можна підтверджувати повідомлення як тільки вони потрапили в пам'ять. Через це виникає ризик втрати повідомлень будь-якого типу (навіть acks=all, min.insync.репліки=2) у разі одночасної відмови.

Загалом Kafka демонструє більш високу продуктивність і спочатку спроектована для кластерів. Кількість фоловерів можна збільшити до 11, якщо це потрібно для надійності. Коефіцієнт реплікації 5 та мінімальне число реплік у синхронізованому стані min.insync.replicas=3 зроблять втрату повідомлення дуже рідкісною подією. Якщо ваша інфраструктура здатна забезпечити такий коефіцієнт реплікації та рівень надмірності, то можете обрати цей варіант.

Кластеризація RabbitMQ хороша для невеликих черг. Але навіть маленькі черги можуть швидко зрости при великих трафіках. Як тільки черги стають більшими, доведеться робити жорсткий вибір між доступністю та надійністю. Кластеризація RabbitMQ найкраще підходить для не типових ситуацій, де переваги гнучкості RabbitMQ переважують будь-які недоліки його кластеризації.

Одна з протиотрут від уразливості RabbitMQ щодо великих черг - розбити їх на безліч менших. Якщо не вимагати повного впорядкування всієї черги, а лише відповідних повідомлень (наприклад, повідомлень конкретного клієнта), або взагалі нічого не впорядковувати, то такий варіант є прийнятним: подивіться мій проект Rebalanser для розбиття черги (проект поки що на ранній стадії).

Зрештою, не забувайте про ряд багів у механізмах кластеризації та реплікації як у RabbitMQ, так і у Kafka. Згодом системи стали зрілішими та стабільнішими, але жодне повідомлення ніколи не буде на 100% захищене від втрати! Крім того, у дата-центрах трапляються великомасштабні аварії!

Якщо я щось пропустив, припустився помилки або ви не згодні з будь-якою з тез, не соромтеся написати коментар або зв'язатися зі мною.

Мене часто запитують: "Що вибрати, Kafka або RabbitMQ?", "Яка платформа краще?". Правда в тому, що це дійсно залежить від вашої ситуації, поточного досвіду тощо. Я написав цей цикл статей, щоб ви могли сформувати власну думку.

Хочу сказати, що обидві системи є лідерами у цій галузі. Можливо, я трохи упереджений, тому що з досвіду своїх проектів більше схильний цінувати такі речі, як гарантоване впорядкування повідомлень та надійність.

Я бачу інші технології, яким не вистачає цієї надійності та гарантованого впорядкування, потім дивлюся на RabbitMQ та Kafka – і розумію неймовірну цінність обох цих систем.

Джерело: habr.com

Додати коментар або відгук