Отказоустойчивая архитектура: почему Tarantool не падает?

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!



Основная проблема в высоконагруженных приложениях — отказоустойчивость. Нагрузка с упавших узлов в кластере должна переключаться на живые. Это кажется несложной задачей, но на практике появляется много подводных камней. Мы с yngvar_antonsson потратили много времени на поддержку различных кластеров, построенных на Tarantool и наших кластерных фреймворках Cartridge и TDG, и сегодня расскажем вам, как обеспечивается отказоустойчивость в наших приложениях. Будет интересно всем, кто хочет подробнее узнать, как устроен фейловер в Cartridge, и тем, кто хочет узнать о нашем опыте создания автоматических фейловеров.

Общая информация


За отказоустойчивость Tarantool в кластерах на Cartridge (Open Source) и Tarantool Data Grid (Enterprise edition) отвечает процесс, который называется Failover. При включение фейловера на каждом узле запускается специальный Failover fiber (корутина). Он управляет переключением лидеров (Read-write-узлов) в репликасетах (наборе узлов Tarantool, соединенных репликационными потоками) в зависимости от порядка узлов в списке Failover priority.

В Cartridge есть 4 типа фейловеров:

  • Disabled
  • Eventual
  • Raft
  • Stateful

и два типа хранилища состояний: Tarantool Stateboard и etcd.

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

Disabled failover


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

  1. Обновление кластера. Обновление приложений на Tarantool производится с помощью перезапуска, поэтому во время обновления появляется дополнительная нагрузка на фейловер. На больших кластерах мы рекомендуем выключать фейловер на время обновления.
  2. Плановая перезагрузка узлов. В процессе эксплуатации, помимо обновления, иногда приходится перезагружать узлы. Например, для ребутстрапа реплики с мастера или при возникших проблемах с фрагментацией.
  3. Хрупкие кластеры. В случае ненадежных кластеров — например, с нестабильной сетью или когда узлы перегружены тяжелыми запросами — фейловер может переключать лидеров в моменты, когда этого делать не нужно (подробнее ниже).

Как выключить фейловер? Он выключен по умолчанию. Если включен другой режим, нажать на кнопку Failover в WebUI и выбрать режим Disabled.



Как сменить лидера? Поменять список Failover priority. Это можно сделать с помощью WebUI: нажать на кнопку Edit replica set и поменять порядок узлов с помощью курсора.



Или с помощью GraphQL:

mutation($replicasets: [EditReplicasetInput]) {
    cluster {
        edit_topology(replicasets: $replicasets) {}
    }
}

replicasets = [{
    uuid = "replicaset_uuid",
    failover_priority = ["instance1_uuid", "instance2_uuid", ...],
}]

Eventual failover


Подходит для небольших кластеров с невысокой нагрузкой — 4–20 узлов до 30 ГБ каждый.

В этом режиме узлы распространяют слухи о состоянии других узлов по протоколу SWIM. Соседние узлы в репликасете собирают слухи и осуществляют переключение лидера в случае смерти текущего лидера. После восстановления узла происходит обратное переключение — восстановление старого лидера.

Проблема в следующем: в случае нестабильных узлов (тяжелые транзакции, тяжелая выборка данных, нестабильная сеть) переключение мастера может происходить очень часто, что вызывает так называемые «шторма фейловера». Узлы начинают неконтролируемо переключаться, и в один момент в репликасете может быть два лидера, что вызывает конфликты в репликации.



В случае штормов мы рекомендуем один из двух вариантов:

  1. Перейти на Stateful failover (почему он решит эту проблему, будет рассказано ниже).
  2. Включить настройки подавления фейловера (Failover supperessing). Узлы не станут лидерами в случае частого переключения и перейдут в Read only.

Лидером в Eventual-режиме всегда стремится стать первый узел в списке Failover priority. Чтобы изменить текущего лидера вручную, нужно поменять инстансы в этом списке местами, как в варианте с выключенным фейловером.

Как включить фейловер? Нажать на кнопку Failover в WebUI и выбрать режим eventual.

Как сменить лидера? Поменять список Failover priority.

Сейчас Eventual-фейловер перешел в состояние Deprecated. Мы не рекомендуем использовать его на больших кластерах. Тем не менее это все еще отличный вариант для небольших хранилищ данных.

Raft failover


Команда Tarantool уже неоднократно рассказывала про нашу реализацию алгоритма выбора лидеров Raft. Мы решили поддержать Raft в Cartridge и TDG, и так появился Raft(-based) failover.

Этот режим управляет настройками Raft в Tarantool. Tarantool сообщает Cartridge/TDG об изменении лидера и распространяет эту информацию по протоколу SWIM на остальные узлы. Cartridge использует ее для применения во внутренних процессах — например, для вызова удаленных функций cartridge.rpc, которые должны выполняться только на лидерах.



Как включить фейловер? При запуске инстансов Tarantool передать настройки Raft. После запуска узлов нажать на кнопку Failover в WebUI и выбрать режим Raft.

Как сменить лидера? Позвать функцию box.ctl.promote вручную (стоит помнить, что она начинает «честные» выборы и лидером не обязательно станет инстанс, на котором вы вызвали box.ctl.promote) или нажать на кнопку Promote a leader в WebUI.

Raft Failover сейчас находится в beta-версии. Мы пока не рекомендуем использовать его для Production-кластеров, но вы всегда можете его попробовать и написать нам о результатах в issues.

Stateful Failover


В Stateful failover появляется новая для кластера сущность — хранилище состояний (State provider). Cartridge сохраняет в нем информацию о текущих лидерах в различных реплика-сетах и состояние репликации: vclock — список номеров lsn, которые пришли на каждый узел в отдельности.

Логика по выбору и назначению лидеров здесь вынесена на отдельные узлы, которые называются Failover-coordinator. В кластере может быть несколько таких узлов, но в каждый момент времени работать будет только один. Координатор следит за состоянием узлов с помощью протокола SWIM и после смерти текущего лидера назначает следующий живой инстанс в списке Failover priority новым лидером и записывает его в State provider. Если в ваших реплика-сетах есть узлы, которые не должны становится лидерами, вы также можете пометить их как Non-electable в WebUI или через GraphQL.

Узлы, в свою очередь, опрашивают State provider и после появления информации о новом лидере применяют изменения к себе. Помимо этого, лидеры периодически записывают в State provider состояние своих vclock.

Особенность координатора в том, что он знает, какой узел является лидером в реплика-сете и не будет выбирать нового лидера, пока жив текущий. Это частично решает проблему «штормов», потому что переключение лидера будет происходить только после гибели текущего, а не после восстановление предыдущего. Здесь возникает другая проблема: большинство инсталляций кластеров Cartridge/TDG разделены на 2 или больше ЦОДов: лидеры существуют в основном ЦОДе, а реплики в побочном, который может находиться далеко от клиентов. Хочется, чтобы лидеры большую часть времени находились в основном ЦОДе. Для этого в Stateful-режиме есть специальная настройка leader_autoreturn. При ее включении раз в заданный тайм-аут фейловер будет пытаться сменить лидера на первый узел из списка Failover priority.



В виде псевдокода это можно представить следующим образом:

------- on each node -------
while true do 
    new_leaders = get_leaders_from_stateboard()
    if new_leaders ~= old_leaders then
        if i_am_new_leader then 
            old_leader, lsn = get_lsn_from_stateboard()
            wait_lsn(old_leader, lsn)
            set_lsn_to_stateboard(box.info.uuid, box.info.lsn)
        end
        box.cfg{read_only = not i_am_new_leader}
    end
    
end

------- on failover coordinator -------

leaders = {}
while true do 
    for replicaset in replicasets do
        if leaders[replicaset] and not leaders[replicaset].is_healthy then
            for instance in replicaset do
                if instance.is_healthy then
                    leaders[replicaset] = instance    
                end
            end
        end
    end
    set_leaders_to_stateboard(leaders)
end

Что выступает в роли State provider? Кластер etcd (рекомендуемый вариант) или Tarantool Stateboard. Учтите, что Cartridge поддерживает только etcd API V2, так что нужно пользоваться либо etcd v2, либо etcd v3 с включенной настройкой ETCD_ENABLE_V2.

Tarantool Stateboard


Отдельно стоящий инстанс Tarantool, непосредственно в кластере не участвует и не отображается в WebUI. Сохраняет необходимую информацию для работы фейловера вместо etcd. Подходит для небольших кластеров и тестирования. Кластеризацию стейтборда придется поддерживать самостоятельно: сейчас Cartridge не позволяет указать несколько инстансов стейтборда.

Как включить фейловер?

  1. Поднять State provider — Tarantool Stateboard или кластер etcd.
  2. Назначить на несколько узлов роль Failover-coordinator.
  3. Нажать на кнопку Failover в WebUI и выбрать режим Stateful.
  4. Указать адрес State provider (в случае etcd — несколько адресов).
  5. Применить изменения.

Как сменить лидера? Нажать на кнопку Promote a leader в WebUI.



Или с помощью GraphQL:

mutation { cluster { failover_promote() } }

Как происходит смена лидера

  1. Failover coordinator записывает в State provider uuid нового лидера.
  2. Новый лидер видит, что его назначили лидером.
  3. Старый лидер переходит в Read only.
  4. Новый лидер узнает из etcd, кто был последним лидером для его реплика-сета, и получает его vclock.
  5. Новый лидер дожидается синхронизации со старым лидером (ждет, пока их vclock’и сравняются).
  6. Новый лидер переходит в Read write.



Фенсинг

При проблемах с сетью несколько инстансов могут посчитать себя лидерами и работать параллельно. Чтобы этого не случилось, в Stateful-режиме есть специальная настройка — фенсинг. Она запускает файбер, который проверяет, есть ли соединение со State provider и хотя бы с одной репликой. Если оба соединения установить не удается, узел автоматически переходит в Read-only-режим до тех пор, пока соединение не восстановится.

Failover + Synchro


Синхронная репликация появилась в Tarantool еще в версии 2.6, но мы долго не поддерживали ее в Cartridge. Начиная с Cartridge 2.8.0, вы можете пользоваться синхронными спейсами в двух режимах фейловера: в Raft и Stateful-режиме. Raft поддерживает синхронную репликацию по умолчанию, а для Stateful-фейловера настройку необходимо явно передать в cartridge.cfg:

cartridge.cfg({
    ...
    enable_synchro_mode = true,
})

Почему это необходимо включать отдельной настройкой? Из-за особенностей реализации синхронной репликации в Tarantool лидеры должны получать владение над синхронной очередью транзакций. При включении sychro_mode в Cartridge фейловер вызывает функцию box.ctl.promote, которая переносит владение синхронной очередью. Если вы не пользуетесь синхронной репликацией, выполнение дополнительной логики будет только тормозить процесс фейловера.

В Eventual failover синхронная репликация работать не будет!

Мониторинг


Cartridge следит за состоянием и работой фейловера и сообщает пользователям, если что-то пошло не так, через систему Cartridge issues. Их можно увидеть в WebUI.

Виды предупреждений:

  • отсутствие или недоступность Failover coordinator,
  • различные ошибки при работе фейловера,
  • использование нескольких различных кластеров по одному префиксу в etcd / с общим Tarantool Stateboard,
  • использование синхронной репликации вместе с неподдерживаемым типом фейловера.

Итоги


Фейловер в Cartridge и TDG обеспечивает отказоустойчивость наших приложений. Самым продвинутым и рекомендуемым на данный момент является Stateful failover, который использует etcd как хранилище состояний. В таблице вы можете взглянуть на сравнение режимов фейловера:

Disabled
Eventual
Raft
Stateful
Состояние
Deprecated
Beta
Recommended
Поддерживаемый размер кластера
Любой
Маленький
Любой
Любой для etcd, средний для Stateboard
Лидер
Задается вручную
Первый живой узел в списке
Выбирается автоматически
Выбирается автоматически, выбор запоминается
Где сохраняется информация о текущем лидере
На узлах Tarantool
etcd/Stateboard
Кто управляет переключением
Пользователь
Failover fiber
Raft-алгоритм в Tarantool
Failover fiber + Failover-coordinator
Как поменять лидера вручную
Изменить список Failover priority
Изменить список Failover priority
box.ctl.promote
/ Кнопка promote в WebUI / GraphQL promote callback
Кнопка promote в WebUI / GraphQL promote callback
Запрет на становление лидером
Failover suppressing
Raft fencing
Fencing
Как включить
Failover -> Disabled
Failover -> Eventual
Передать параметры для настройки Raft (переменные окружения, параметры запуска), Failover -> Raft
Поднять State provider, назначить на один или несколько узлов роль Failover-coordinator, Failover -> Stateful, указать адрес State provide

А что дальше


Начиная с Tarantool 3.0, обеспечением отказоустойчивости будет заниматься сам Tarantool. Мы продолжаем поддерживать Cartridge и улучшать наш фейловер. Оставайтесь с нами и следите за обновлениями!

Полезные ссылки


  • Масштабирование приложений на Cartridge
  • Антипаттерны при работе с Tarantool
  • Документация Cartridge Failover
  • Про Raft в Tarantool: раз, два, три, четыре
  • SWIM
Источник: https://habr.com/ru/companies/vk/articles/768360/


Интересные статьи

Интересные статьи

Почему я пожалел о миграции на Neo4j.Я расскажу об опыте миграции Spring Boot приложения с Postgres на Neo4j.
Если вы зайдете на сайт Killed By Google, то увидите длинный список из 259 сервисов, которые компания Google уже закрыла, и еще несколько, которые будут (или были) закрыты в этом или следующем году. В...
Без своевременной обратной связи не решить рабочих задач, особенно если речь о вовремя замеченных недостатков и багов: 72% работников уверены, что правильная критика поможет им работать лучше. В бизне...
Узнаете, почему случаются возвраты и что делать, если возврат все-таки произошел...
Каким, по-вашему, будет результат выполнения следующего кода на Python? Подобные вопросы заставляют меня сразу же открыть Python REPL и проверить код, потому что я думаю, что знаю правильный отве...