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

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

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



Рис. 1. Чотири розділи розподілені між трьома брокерами

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


Помилка розділу

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

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


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

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


Рис. 3. Залишився один брокер. Всі лідери знаходяться на одному брокері з нульовою надлишковістю

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


Рис. 4. Лідери залишаються на брокері 2

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


Рис. 5. Незбалансоване розміщення лідерів після відновлення брокерів 1 і 3

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

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

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


Рис. 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, без оверхеда на повільних або мертвих фоловерів, яких лідер не чекає.


Рис. 7. ISR з трьома репліками

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


Рис. 8. При збої губляться повідомлення

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


Рис. 9. Відправлення повідомлень поновлюється після короткого перерви

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


Рис. 10. Фолловер на брокері 3 видаляється з ISR

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


Рис. 11. Брокер 1 падає. При збої втрачається велика кількість повідомлень

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


Рис. 12. Після короткого перерви повідомлення знову відправляються в розділ 0

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

Acks=all і ISR

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


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

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


Рис. 14. Всі репліки зберігають повідомлення і відправляється ack

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


Рис. 15. Репліка на брокері 3 видаляється з ISR

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


Рис. 16. Брокер 2 падає

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


Рис. 17. Репліка на брокері 1 бере на себе лідерство без втрати повідомлень

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


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

Ми могли б не встановлювати опцію unclean.leader.election.enable значення true. За замовчуванням воно дорівнює 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.


Рис. 19. ISR з двох реплік

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


Рис. 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 завжди поінформований про будь-які зміни, щоб у разі збою керівництво плавно перейшло до нового лідера.


Рис. 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.

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

Помилка лідера

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

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

При возз'єднанні з кластером репліки надходять так само, як і при збої лідера: перевіряють репліку лідера і усекают свій лог до його 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



Рис. 22. Сценарій 1. ISR з трьох реплік

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


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

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

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


Рис. 24. Сценарій 2. Лідер і два фолловера

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


Рис. 25. Сценарій 2. ISR стиснувся до лідера

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

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


Рис. 26. Сценарій 3. Фолловер продовжує відправляти лідеру запити на вибірку

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


Рис. 27. Сценарій 4. Лідер і два фолловера

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


Рис. 28. Сценарій 4. Лідер ізольований від Zookeeper

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

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

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


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

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

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


Рис. 30. Сценарій 5. Ізольований фолловер видаляється з ISR

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


Рис. 31. Сценарій 6. Лідер і два фолловера

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


Рис. 32. Сценарій 6. Ізоляція лідера від інших вузлів Kafka і Zookeeper

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

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


Рис. 33. Сценарій 6. Два лідера

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


Рис. 34. Сценарій 6. Виробники переходять на нового лідера

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


Рис. 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 — і розумію неймовірну цінність обох цих систем.

Source: habr.com

Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.