Хабрахабр

[Перевод] RabbitMQ против Kafka: отказоустойчивость и высокая доступность

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

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

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

Фолловеры периодически посылают лидеру запросы на получение последних сообщений. Все запросы на чтение и запись поступают лидеру. Потребители никогда не обращаются к фолловерам, последние существуют только для избыточности и отказоустойчивости.

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

Из сети уходит брокер 3 — и для раздела 2 избирается новый лидер на брокере 2.

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

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

3.
Рис. Все лидеры находятся на одном брокере с нулевой избыточностью Остался один брокер.

Но все лидеры по-прежнему остались на брокере 2. Когда брокер 1 возвращается в сеть, то добавляет четырех фолловеров, обеспечивая некоторую избыточность каждому разделу.

4.
Рис. Лидеры остаются на брокере 2

Но все лидеры по-прежнему на брокере 2. Когда поднимается брокер 3, мы возвращаемся к трем репликам на раздел.

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

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

Когда создаются разделы топика, Kafka пытается равномерно распределить лидеров по узлам и отмечает этих первых лидеров как предпочтительных. У Kafka есть концепция «предпочтительных реплик» на роль лидера. Со временем из-за перезагрузки серверов, сбоев и нарушения связности лидеры могут оказаться на других узлах, как в вышеописанном крайнем случае.

Чтобы исправить это, Kafka предлагает два варианта:

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

6.
Рис. Реплики после перебалансировки

Все сводится к синхронизированным репликам (In-Sync Replicas, 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. Брокер 1 отстал всего на одну секунду. Брокер 3 отстает, он синхронизировался с лидером восемь секунд назад и сейчас отстает на 7456 сообщений. Наш производитель отправляет сообщение и быстро получает обратно ack, без оверхеда на медленных или мертвых фолловеров, которых лидер не ждет.

7.
Рис. ISR с тремя репликами

После перехода лидерства к брокеру 1 мы теряем 123 сообщения. Брокер 2 выходит из строя, и производитель получает ошибку соединения. Фолловер на брокере 1 входил в ISR, но не полностью синхронизировался с лидером, когда тот упал.

8.
Рис. При сбое теряются сообщения

Затем он устанавливает соединение с брокером 1 и продолжает отправлять сообщения. В конфигурации bootstrap.servers у производителя перечислено несколько брокеров, и он может спросить другого брокера, кто стал новым лидером раздела.

9.
Рис. Отправка сообщений возобновляется после краткого перерыва

Он делает запросы на выборку, но не может синхронизироваться. Брокер 3 отстает еще больше. д. Это может быть связано с медленным сетевым соединением между брокерами, проблемой хранения и т. Теперь ISR состоит из одной реплики — лидера! Он удаляется из ISR. Производитель продолжает отправлять сообщения и получать подтверждения.

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

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

11.
Рис. При сбое теряется большое количество сообщений Брокер 1 падает.

Он начинает отправлять сообщения брокеру 3. Производитель устанавливает соединение с последним брокером и видит, что тот теперь лидер раздела.

12.
Рис. После краткого перерыва сообщения снова отправляются в раздел 0

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

Acks=all и ISR

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

13.
Рис. Одна работает медленно, что приводит к задержке записи ISR с тремя репликами.

Все реплики теперь полностью обновлены. После четырех секунд дополнительной задержки брокер 2 отправляет ack.

14.
Рис. Все реплики сохраняют сообщения и отправляется ack

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

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

Затем падает брокер 2, и лидерство переходит к брокеру 1 без потери сообщений.

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

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

17.
Рис. Реплика на брокере 1 берет на себя лидерство без потери сообщений

Затем падает брокер 1, и лидерство переходит к брокеру 3 с потерей 14238 сообщений!

18.
Рис. Брокер 1 умирает, а переход лидерства с настройкой unclean приводит к обширной потере данных

По умолчанию оно равно false. Мы могли бы не устанавливать опцию unclean.leader.election.enable в значение true. Но, как вы видите, мы все еще можем потерять сообщения. Настройка 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 из двух реплик

Но теперь ISR состоит только из одной реплики. Брокер 2 падает, а лидерство переходит к брокеру 1 без потери сообщений. Это не соответствует минимальному числу для получения записей, и поэтому брокер отвечает на попытку записи ошибкой NotEnoughReplicas.

20.
Рис. Число ISR на один ниже, чем указано в min.insync.replicas

Прежде чем подтвердить сообщение, мы гарантируем, что оно записывается по крайней мере на две реплики. Эта конфигурация жертвует доступностью ради согласованности. Здесь потеря сообщений возможна только в случае одновременного сбоя двух реплик в краткий интервал, пока сообщение не реплицировано дополнительному фолловеру, что маловероятно. Это дает производителю гораздо большую уверенность. Тут сразу три брокера должны упасть одновременно, чтобы потерять запись! Но если вы суперпараноик, то можете установить коэффициент репликации 5, а min.insync.replicas на 3. Конечно, за такую надежность вы заплатите дополнительной задержкой.

Как и в случае с RabbitMQ, иногда доступность необходима для безопасности данных. Вам нужно подумать вот о чем:

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

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

По сути этот параметр означает, какую задержку мы готовы принять при acks=all. Мы сами выбираем значение replica.lag.time.max.ms в соответствии со своими потребностями. Если для вас это слишком долго, можете ее уменьшить. Значение по умолчанию — десять секунд. Тогда вырастет частота изменений в ISR, поскольку фолловеры будут чаще удаляться и добавляться.

Медленные зеркала привносят дополнительную задержку, а отклика мертвых зеркал можно ждать до истечения времени жизни пакетов, которые проверяют доступность каждого узла (net tick). В RabbitMQ просто набор зеркал, которые нужно реплицировать. Но мы рискуем потерять избыточность, поскольку ISR может сократиться только до лидера. ISR — интересный способ избежать этих проблем с увеличением задержки. Чтобы избежать этого риска, используйте настройку min.insync.replicas.

В настройках bootstrap.servers производителя и потребителя можно указать несколько брокеров для подключения клиентов. Идея в том, что при отключении одного узла остается несколько запасных, с которыми клиент может открыть соединение. Это не обязательно лидеры разделов, а просто плацдарм для для начальной загрузки. Клиент может спросить их, на каком узле размещается лидер раздела для чтения/записи.

Это означает, что вы можете установить перед RabbitMQ балансировщик нагрузки. В RabbitMQ клиенты могут подключаться к любому узлу, а внутренняя маршрутизация отправляет запрос куда надо. В такой ситуации балансировщик нагрузки не поставить. Kafka требует, чтобы клиенты подключались к узлу, на котором размещается лидер соответствующего раздела. Список bootstrap.servers критически важен, чтобы клиенты могли обращаться к нужным узлам и находить их после сбоя.

До сих пор мы не рассмотрели, как кластер узнает о падении брокера и как выбирается новый лидер. Чтобы понять, как Kafka работает с сетевыми разделами, сначала нужно понять архитектуру консенсуса.

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

Zookeeper хранит состояние кластера:

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

Узел контроллера — один из брокеров Kafka, который отвечает за избрание лидеров реплик. Zookeeper отправляет контроллеру уведомления о членстве в кластере и изменениях топика, и контроллер должен действовать в соответствии с этими изменениями.

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

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

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

Когда падает брокер с лидером, Zookeeper отправляет уведомление контроллеру, и тот выбирает нового лидера. Опять же, контроллер сначала обновляет Zookeeper, а затем отправляет команду каждому брокеру, уведомляя их об изменении лидерства.

Настройка replica.lag.time.max.ms определяет, кто туда войдет. Каждый лидер несет ответственность за набор ISR. При изменении ISR лидер передает Zookeeper новую информацию.

Zookeeper всегда информирован о любых изменениях, чтобы в случае сбоя руководство плавно перешло к новому лидеру.

21.
Рис. Консенсус Kafka

Понимание деталей репликации помогает лучше понять потенциальные сценарии потери данных.

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

Мы рассмотрели, что фолловеры периодически отправляют лидеру запросы на выборку (fetch). Интервал по умолчанию составляет 500 мс. Это отличается от RabbitMQ тем, что в RabbitMQ репликация инициируется не зеркалом очереди, а мастером. Мастер пушит изменения на зеркала.

Отметка LEO хранит смещение последнего сообщения в локальной реплике, а HW — смещение последнего коммита. Лидер и все фолловеры сохраняют смещение конца лога (Log End Offset, LEO) и метку Highwater (HW). Это означает, что LEO обычно чуть опережает HW. Помните, что для статуса «коммит» сообщение должно быть сохранено во всех репликах ISR.

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

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

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

  1. Усечёт локальный лог до известного HW и отправит новому лидеру запрос на сообщения после этой отметки.
  2. Отправит лидеру запрос, чтобы узнать HW на момент его избрания лидером, а затем усечет лог до этого смещения. Затем начнет делать периодические запросы на выборку, начиная с этого смещения.

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

  • Когда происходит сбой лидера, первый фолловер из набора ISR, зарегистрированный в Zookeeper, выигрывает выборы и становится лидером. Все фолловеры в ISR, хотя и считаются «синхронизированными», могли и не получить от бывшего лидера копии всех сообщений. Вполне возможно, что у избранного фолловера не самая актуальная копия. Kafka гарантирует, что между репликами нет расхождения. Таким образом, чтобы избежать расхождения, каждый фолловер должен усечь свой лог до значения HW нового лидера на момент его избрания. Это еще одна причина, почему настройка acks=all так важна для согласованности.
  • Сообщения периодически записываются на диск. Если все узлы кластера отказали одновременно, то на дисках сохранятся реплики с разным смещением. Вполне возможно, что когда брокеры снова вернутся в сеть, новый лидер, который будет избран, окажется позади своих фолловеров, потому что он сохранился на диск раньше других.

Воссоединение c кластером

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

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

Они просто работают рядом с группой, получая сообщения так быстро, как могут, пока не догонят лидера и не войдут в 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 больше не может отправлять запросы на выборку. Нарушение связности отделяет брокера 3 от брокеров 1 и 2, но не от Zookeeper. Как только связность восстановлена, он возобновит запросы на выборку и присоединится к ISR, когда догонит лидера. По истечении времени replica.lag.time.max.ms он удаляется из ISR и не участвует в коммитах сообщений. Zookeeper продолжит получать пинги и считать, что брокер жив и здоров.

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

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

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


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

Как и в первом сценарии, ISR сжимается, но на этот раз только до лидера, поскольку все фолловеры перестают отправлять запросы на выборку. Нарушение сетевой связности отделяет лидера от фолловеров, но брокер все еще видит Zookeeper. Вместо этого происходит потеря избыточности для новых сообщений, пока связность не восстановится. Опять же, нет никакого логического разделения. Zookeeper продолжает получать пинги и считает, что брокер жив и здоров.

25.
Рис. ISR сжался только до лидера Сценарий 2.

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

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

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

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


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

Лидер отделен от Zookeeper, но не от брокеров с фолловерами.

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

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

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

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

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

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

Фолловер полностью изолирован и от других узлов Kafka, и от Zookeeper. Он просто удаляется из ISR, пока сеть не восстановится, а потом догоняет остальных.

30.
Рис. Изолированный фолловер удаляется из ISR Сценарий 5.

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


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

В течение короткого периода он продолжит принимать записи с acks=1. Лидер полностью изолирован от своих фолловеров, контроллера и Zookeeper.

32.
Рис. Изоляция лидера от других узлов Kafka и Zookeeper Сценарий 6.

Не получив запросов по истечении 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 вызывает кратковременное логическое разделение с двумя лидерами. Если из-за потери связности лидер отделился от Zookeeper, это может привести к потере сообщений с acks=1. Эту проблему решает параметр 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 есть ахиллесова пята. При воссоединении после сбоя узлы отбрасывают свои данные, а синхронизация блокируется. Этот двойной удар ставит под вопрос долговечность больших очередей в RabbitMQ. Вам придётся смириться либо с сокращением избыточности, либо с длительными блокировками. Снижение избыточности увеличивает риск массовой потери данных. Но если очереди небольшие, то ради обеспечения избыточности с краткими периодами недоступности (несколько секунд) можно справиться с помощью повторных попыток подключения.

Она отбрасывает данные только с точки расхождения лидера и фолловера. В Kafka нет такой проблемы. Кроме того, репликация не блокирует систему. Все общие данные сохраняются. Конечно, по-прежнему остаются проблемы, такие как пропускная способность сети при репликации. Лидер продолжает принимать записи, пока новый фолловер его догоняет, так что для девопсов присоединение или воссоединение кластера становится тривиальной задачей. Если одновременно добавляются несколько фолловеров, можно столкнуться с лимитом пропускной способности.

Как мы уже говорили, RabbitMQ отправляет паблишеру подтверждение только после записи сообщения на диск у мастера и всех зеркал. RabbitMQ превосходит Kafka в надёжности при одновременном сбое нескольких серверов в кластере. Но это добавляет дополнительную задержку по двум причинам:

  • fsync каждые несколько сотен миллисекунд
  • Сбой зеркала могут заметить только по истечении времени жизни пакетов, которые проверяют доступность каждого узла (net tick). Если зеркало тормозит или упало, это добавляет задержку.

Kafka делает ставку на то, что если сообщение хранится на нескольких узлах, можно подтверждать сообщения, как только они попали в память. Из-за этого возникает риск потери сообщений любого типа (даже acks=all, min.insync.реплики=2) в случае одновременного отказа.

Количество фолловеров можно увеличить до 11-ти, если это нужно для надёжности. В целом Kafka демонстрирует более высокую производительность по и изначально спроектирована для кластеров. Если ваша инфраструктура способна обеспечить такой коэффициент репликации и уровень избыточности, то можете выбрать этот вариант. Коэффициент репликации 5 и минимальное число реплик в синхронизированном состоянии min.insync.replicas=3 сделают потерю сообщения очень редким событием.

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

Если не требовать полного упорядочения всей очереди, а только соответствующих сообщений (например, сообщений конкретного клиента), или вообще ничего не упорядочивать, то такой вариант приемлем: посмотрите мой проект Rebalanser для разбиения очереди (проект пока на ранней стадии). Одно из противоядий от уязвимости RabbitMQ в отношении больших очередей — разбить их на множество меньших.

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

Если я что-то пропустил, допустил ошибку или вы не согласны с любым из тезисов, не стесняйтесь написать комментарий или связаться со мной.

Правда в том, что это действительно зависит от вашей ситуации, текущего опыта и т. Меня часто спрашивают: «Что выбрать, Kafka или RabbitMQ?», «Какая платформа лучше?». Я не решаюсь высказывать своё мнение, поскольку будет слишком большим упрощением рекомендовать какую-то одну платформу для всех вариантов использования и возможных ограничений. д. Я написал этот цикл статей, чтобы вы могли сформировать собственное мнение.

Возможно, я немного предвзят, потому что по опыту своих проектов больше склонен ценить такие вещи, как гарантированное упорядочение сообщений и надёжность. Хочу сказать, что обе системы являются лидерами в данной области.

Я вижу другие технологии, которым не хватает этой надёжности и гарантированного упорядочения, затем смотрю на RabbitMQ и Kafka — и понимаю невероятную ценность обеих этих систем.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть