Что такое raft алгоритм

Обновлено: 02.07.2024

Consensus: Bridging Theory and Practice

This repository contains Diego Ongaro's PhD dissertation, Consensus: Bridging Theory and Practice, published by Stanford University in 2014. The dissertation PDFs and the dissertation sources are licensed under the Creative Commons Attribution license, as described in the LICENSE file.

Several pre-built PDFs are included:

    : formatted for a printed book (8.5x11" pages, bigger inside margins for binding, black hyperlinks) : formatted for normal digital or print use (8.5x11" pages, consistent margins on all pages, blue hyperlinks) : formatted for digital viewing (6.6x9.35" pages, minimal margins, blue hyperlinks) : exact file distributed by Stanford University (similar to online.pdf ; copyright, signature, and preface pages differ)

All of these use the same page numbers starting at page 1, though book.pdf has an additional blank page before the introduction (page xviii).

The source materials for the dissertation are made available here in the hopes that they might be useful, for example, to reformat the dissertation for a different medium or to copy sections for use in other documents (per the LICENSE). It requires the following to build:

  • GNU make
  • pdflatex
  • bibtex
  • Inkscape (to convert SVG images and layers from SVG images to PDF format)

Updates and Errata

Chapter 3: Basic Raft algorithm

  • Figure 3.1 (cheatsheet): Although lastApplied is listed as volatile state, it should be as volatile as the state machine. If the state machine is volatile, lastApplied should be volatile. If the state machine is persistent, lastApplied should be just as persistent.

Chapter 4: Cluster membership changes

  • There's an important bug in single-server changes, fortunately with an easy fix. See the raft-dev post.

Chapter 6: Client interaction

  • "it would extends its lease" should read "it would extend its lease" (Figure 6.3 caption).

Chapter 7: Raft user study / Appendix A: User study materials

  • "Log Completeness" should read "Leader Completeness" (3x).

Chapter 8: Correctness / Appendix B: Safety proof and formal specification

Реализация консенсус-алгоритма RAFT для распределенного K-V хранилища на Java

И снова здравствуйте. Несколько дней назад началось обучение в новой группе по курсу «Архитектор ПО», а сегодня мы хотели бы поделиться статьей, которую написал один из студентов курса — Плешаков Антон (руководитель направления разработки в компании «Программная логистика» и co-founder в Clusterra).




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

Важно также отметить, что на текущий момент очень важным фактором, влияющим на выбор стратегии разработки в пользу микросервисов, является — наличие всевозможных готовых инфраструктурных решений, берущих на себя решение проблем, связанных с дополнительными издержками на эксплуатацию распределенной системы. Речь идет о системах контейнерной оркестрации, service mash, средствах распределенной трассировки, мониторинга, логирования и прочее прочее. Можно смело утверждать что большинство факторов, ранее упоминавшихся как минусы микросервисного подхода, на сегодняшний день не имеют такого большого влияния, как пару лет назад.

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

При попытке достигнуть горизонтальной масштабируемости, очень быстро и очень остро встает вопрос синхронизации данных внутри кластера. К счастью, все современные СУБД так или иначе поддерживают репликацию данных между узлами. Разработчику нужно просто подобрать СУБД под задачу и определиться, какие свойства системы (согласно CAP теоремы) ему необходимы, CP или AP, и вопрос решен. В том случае, когда требуется именно CP и требования к консистентности высокие, одним из методов решения проблемы синхронизации данных является использование кластера, поддерживающего консенсус-алгоритм RAFT.

Этот достаточно новый алгоритм (был разработан в 2012 году) дает высокую гарантию консистентности и очень популярен. Я решил разобраться, как он работает, и написал свою реализацию консистентного key-value хранилища на Java (Spring Boot).

Есть ли смысл реализовывать какой-либо распределенный алгоритм самостоятельно? Понятно что можно взять готовую реализацию какого-либо распределенного алгоритма, и с высочайшей долей вероятности эта реализация будет лучше самодельного “велосипеда”. Например, можно использовать СУБД, поддерживающую необходимый уровень консистентности. Или же можно развернуть Zookeeper. Или можно найти подходящий для вашего языка фреймворк. Для java есть Atomix, который отлично решает проблемы синхронизации распределенных данных.

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

Поскольку спецификация алгоритма содержит в себе комплекс мер по поддержанию целостности данных, вы можете воспользоваться полученным знаниями и даже использовать алгоритм не целиком. Любая часть алгоритма может быть полезна в реальной жизни. Допустим у вас есть набор воркеров для параллельного парсинга файлов. Воркеры равнозначны, но вы хотите назначить один из воркеров в качестве координатора и при падении воркера-координатора назначать координатором любой другой свободный воркер. В этом вам поможет первая половина алгоритма RAFT, в которой описывается как выбирать лидера среди равнозначных узлов. Или же например, если у вас есть всего два узла в отношении master-slave, вы вполне можете воспользоваться правилами репликации, описанных спецификации RAFT для организации обмена данными в вашем более простом случае.

Статья по сути является практическим руководством как реализовать RAFT самостоятельно. Сам алгоритм и теоретические аспекты его работы разбираться не будут. Можно почитать краткое описание вот в этой отличной статье или же изучить полную спецификацию здесь. Там же можно найти очень наглядную визуализацию работы алгоритма.

Общее описание решения

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

Задача ставилась следующая. Разработать распределенную систему, позволяющую хранить данные в key-value БД. Данные каждой ноды должны быть согласованы, а именно, если данные попали в БД одной ноды и большинство нод подтвердило, что ими эти данные тоже получены, то рано или поздно эти данные окажутся в БД каждой ноды. При отключении части кластера и при его подключении обратно ноды, которые были вне кластера, должны догнать основной кластер и синхронизироваться. Каждая нода предоставляет REST API для записи и чтения данных БД. Система состоит из двух модулей для двух типов нод: клиент и сервер. Ниже мы рассмотрим особенности реализации непосредственно сервера. Код клиента есть в репозитории.

Серверная нода может работать в трех состояниях:

  • Фоловер(follower). Принимает запросы на чтение от клиента. Принимает heartbeat от лидера
  • Кандидат(candidate). Принимает запросы на чтение от клиента. Рассылает vote запросы другим нодам
  • Лидер(leader). Принимает запросы на чтение и на запись. Рассылает heartbeat запросы другим нодам. Рассылает append запросы данными другим нодам.

Хранение данных

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


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

Каждая нода имеет доступ к БД, в которой хранятся непосредственно данные.


В текущей реализации используются embedded in-memory решения как для лога, так и для БД (обычные конкурентные List и Map). В случае необходимости можно просто имплементировать соответствующий интерфейс для поддержки иных типов хранилищ.

Применение операций из лога на БД осуществляет распределенная машина состояния (state machine). Машина состояния — это такой механизм, который отвечает за изменение состояния кластера, ограничивая применение неправильных изменений (операции не по порядку или отключившийся узел, считающий себя лидером). Для того чтобы изменения считались валидными и для того чтобы их можно было применить к БД они должны пройти ряд проверок и соответствовать определенным критериям, что как раз и обеспечивается машиной состояния.

Для лидера операция применяется к БД, если большинство узлов подтвердили тот факт, что операция реплицирована в их лог тоже. Для фолловера операция применяется к БД, если от лидера получен сигнал о том, что она попала в его БД.

Таймеры

Каждая нода обеспечивает обмен данными с другими нодами.

Поддерживаются два типа запросов:

  • vote при проведении раунда голосования
  • append, он же heartbeat (если без данных), для репликации данных лога фолловерам и для предотвращения старта нового раунда голосования.
  • vote. Для запуска раунда голосовании. У каждой ноды определен свой интервал, по истечении которого он попытается начать новое голосование. Отсчет начинается заново при получении heartbeat от лидера.
  • heartbeat. Для отправки лидером append запроса фолловерам. Если узел не получает heartbeat и таймер голосования истек, он становится кандидатом и инициирует выборы, повышает номер раунда голосования и рассылает vote запросы другим нодам. Если нода соберет большинство голосов, то она становится лидером и начинает рассылать heartbeat.

Текущее состояние узла

Каждая нода хранит данные о текущем состоянии.


Нода-лидер также хранит метаданные тех узлов, которым она реплицирует данные.

Голосование

За проведение голосования отвечает класс ElectionService

Отправка запроса на голосование

Если узел является фолловером и не получает heartbeat в течение заданного для ожидания периода, то он повышает свой текущий раунд, объявляет себя кандидатом и начинает рассылать vote запросы другим узлам. Если ему удастся собрать кворум и большинство узлов отдаст ему голос, то он станет новым лидером. В терминах RAFT, кворум — это больше половины всех узлов (51%).

Разберем метод processElection класса ElectionServiceImpl , который вызывается vote-таймером при наступлении срока голосования и отправляет узлам запрос на голосование.

Обработка запроса на голосование

При голосовании каждый узел получает от кандидата запрос вот такого вида:


Теперь давайте рассмотрим процедуру vote класса ElectionServiceImpl , она обрабатывает vote запрос от кандидата и возвращает решение по поводу его кандидатуры на роль лидера.


При получении запроса от кандидата узел делает две проверки: проверяет раунд кандидата и длину его лога. Если раунд кандидата выше и его лог длиннее или равен, то узел отдает свой узел голос за кандидата

  1. Если текущий раунд узла больше, чем раунд кандидата, отвечаем отказом, потому что это запрос какого-то отставшего узла, который, видимо, был какое-то время вне кластера и начал процедуру выборов из-за того, что не видел действующего лидера.
  2. Может сложиться такая ситуация, что раунды равны, например, узел уже успел получить запрос от кандидата и проголосовал за него, и поднял свой раунд до уровня раунда кандидата, но его ответ не дошел, и кандидат прислал ему запрос еще раз; в данной ситуации проверка раунда считается пройденным. Или же узел уже успел проголосовать за другого кандидата с таким же раундом — тогда проверка не пройдена.
  3. Если раунд кандидата больше раунда узла, то проверка раунда пройдена
  4. Проверка лога. Если раунд последней операции лога кандидата больше или равен раунду последней операции лога узла или же если раунды равны, но индекс последней операции кандидата больше или равен индексу последней операции узла, иными словами, если лог кандидата длиннее или равен логу узла, то проверка лога считается пройденной.
  5. При положительном исходе фиксируем факт того, что узел принял участие в выборах и отдал голос за кандидата.
  6. Отправляем результат обратно кандидату

Репликация

Лидер по таймеру отправляет всем узлам фолловерам heartbeat, чтобы сбросить их таймеры голосований. Так как лидер хранит у себя в метаданных индексы последних операций у всех фолловеров, то он может оценить, требуется ли отправка операции узлам. Если лог операций лидера становится длиннее лога какого-либо фолловера, то он вместе с heartbeat последовательно отправляет ему недостающие операции. Назовем это append запросом. Если большинство узлов подтвердили получение новых операций, то лидер применяет эти операции на свою БД и повышает индекс последней примененной операции. Этот индекс тоже отправляется фолловерам вместе с heartbeat запросом. И если индекс лидера выше индекса фолловеров, то фолловер тоже применяет операции к своей БД, чтобы уравнять индексы.


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

За отправку и обработку heartbeat-append запроса отвечает класс:

Отправка запроса на изменение данных

Рассмотрим фрагмент метода sendAppendForOnePeer класса ReplicationServiceImpl

  1. Метаданные фолловера
  2. Оцениваем индекс следующей операции, которую ожидает фолловер. Если индекс последней операции лидера больше или равен индексу операции ожидаемой фолловером (лог операций лидера длиннее лога операций фолловера), то подготавливаем недостающую операцию к отправке и вычисляем индекс операции, которая должна предшествовать этой новой операции, присланной лидером, в логе фолловера. Когда фолловер получит запрос, он сравнит свой последний индекс с тем, что прислал лидер и, если он совпадает, то новую операцию можно добавить
  3. Если лог лидера короче, то операцию не передаем, но фолловеру сообщаем индекс последней операции лидера; для фолловера это может стать сигналом о том, что операции в его логе, индекс которых выше последнего индекса лога лидера, невалидны
  1. Повторяем запрос, пока не получим ответ от всех фолловеров, что репликация прошла успешно. Поскольку отправляется одна операция за запрос, может потребоваться несколько итераций, чтобы синхронизировать логи фолловеров
  2. Отправили запросы всем фолловерам и получили список с ответами
  3. Рассматриваем ответы только от доступных фолловеров
  4. Если выяснилось, что раунд одного из фолловеров больше раунда лидера, все останавливаем и превращаемся в фолловера
  5. Если фолловер ответил, что все прошло успешно, обновляем метаданные фолловера: сохраняем последний индекс лога фолловера и индекс следующей ожидаемой фолловером операции.
  6. Если фолловер отвечает, что репликация не удалась, это значит, что индекс последней операции фолловера не равен предшествующему индексу текущей операции, который мы ему отправили и он не может вставить эту операцию. Другими словами, лог фолловеров отстал от лога лидера более чем на одну операцию и фолловер не может принять последнюю операцию, потому что еще не принял предыдущие. Для данного фолловера меняем метаданные, снижаем ему индекс следующей ожидаемой операции и повторяем отправку. Эта процедура будет повторяться, пока лог фолловера не придет в соответствие с логом лидера.
  7. Если большинство узлов подтвердили занесение операции себе в лог, то она будет применена к БД. Метод подробно будет рассмотрен ниже.

Обработка запроса на изменение данных

Теперь рассмотрим, как именно фолловер обрабатывает append запрос от лидера.
Метод append класса ReplicationServiceImpl

  1. Если раунд лидера меньше, чем раунд фолловера, то отправляем лидеру свой раунд и признак того, что его запрос отвергнут. Как только лидер получит в ответе раунд больше своего, он превратится в фолловера
  2. Если раунд лидера больше раунда фолловера, устанавливаем этот раунд фолловеру
  3. Поскольку получен запрос от лидера, независимо от того, есть ли данные там или нет, сбрасываем vote таймер и, если не были фолловером, становимся им
  4. Если индекс предыдущей операции, полученный от лидера, больше реального индекса последнего элемента лога фолловера или их терм не совпадает, это значит, что перед тем, как вставить присланную операцию, надо вставить предыдущие. Отвечаем отказом и отправляем текущий последний индекс лога, лидер должен повторить попытку, но уже с предыдущей операцией
  5. В логе фолловера уже есть операция с таким индексом и она не совпадает с операцией, полученной от лидера. Удаляем эту операцию в логе фолловера и все операции с индексом больше. Такое может произойти, если лидер отключился от кластера, по-прежнему считает, что он лидер, и принимает новые операции в лог, но на БД их не применяет, потому что не может собрать кворум. Когда узел вернется в кластер и получит подтвержденные кворумом данные от истинного лидера, невалидные операции будут удалены.
  6. Повторно прилетела операция, которая уже вставлялась. Просто отвечаем, что все ОК
  7. Все проверки пройдены, добавляем операцию в лог
  8. Если индекс последней примененной на БД операции фолловера меньше, чем индекс лидера, применяем операции на БД фолловера из лога, пока индекс на сравняется.

Изменение данных в БД

Разберем метод tryToCommit класса ReplicationServiceImpl

  1. Получаем следующий индекс применяемой к БД операции
  2. Считаем, сколько фолловеров имеют у себя в логе операцию с таким индексом, и не забываем прибавить лидера
  3. Если число таких фолловеров составляют кворум и операция с таким индексом есть в логе лидера, и раунд этой операции эквивалентен текущему, то лидер применяет операцию к БД и повышает индекс последней примененной операции. Операции из предыдущего раунда не могут быть применены, т.к за них отвечал другой лидер и может возникнуть коллизия. Каждый лидер применяет операции только своего текущего раунда.

Заключение

Любой распределенный алгоритм, представителем семейства которых является RAFT — это мощное комплексное решение, гарантирующее достижение результата, при соблюдении всех описываемых в спецификации правил.

Распределенных алгоритмов много и они разные. Есть ZAB, который реализован в Zookeeper и используется, например, для синхронизации данных в Kafka. Есть алгоритмы с менее жесткими требованиями к консистентности, например масса имплементаций Gossip протокола, которые применяются в AP системах. Существуют алгоритмы, которые следуют принципам RAFT, и при этом используют gossip протокол для обмена логами например MOKKA, который причем еще и использует шифрование.

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

Что такое raft алгоритм

Реализация консенсус-алгоритма RAFT для распределенного K-V хранилища

alt text

Система состоит из двух модулей, для двух типов нод: клиент и сервер. Можно развернуть неограниченное количество инстансов как сервера так и клиента. В текущей конфигурации настроены 3 ноды (для простоты эмуляции нешататных ситуаций) и 1 клиент,

Доступны три ноды. Идентификаторы нод: 1,2,3.

С API можно работать через swagger(подробенее в разделе API)

Серверная нода может работать в трех состояниях:

  • Follower. Принимет запросы на чтение от клиента. Принимает heartbeat от лидера
  • Candidate. Принимает запросы на чтение от клиента. Рассылает vote запросы другим нодам
  • Leader. Принимает запросы на чтение и на запись. Рассылает heartbeat запросы другим нодам. Рассылает append запросы c данными другим нодам.

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

Также каждая серверная нода имеет доступ к БД в которой хранятся непосредственно данные.

БД и лог у каждой ноды свои отдельные.

В текущей реализации используются embedded in-memory решения как для лога, так и для БД. (конкурентные List и Map) В случае необходимости можно просто имплементировать соответствующий интерфейс для поддержки иных типов хранилищ.

Данные лога(операции) реплицируются лидером остальным нодам. После подтверждения получения операции большинством нод, операция применяюся state machine и данные попадают в БД. После этого, факт того что операция применена, отправляется другим нодам и они применяют её на своей БД.

Сереверная нода обеспечивает обмен данными с другими нодами поддерживаются два типа запросов:

  • vote при проведении раунда голосования
  • append он же heartbeat(если без данных) для репликации данных лога фоловерам и для предотвращения старта нового раунда голосования.

На сервере запущены два вида таймеров:

  • vote. Для запуска раунда голосовании. Сбрасывается при получении heartbeat от сервера. Настраивается отдельно для каждого сервера. В текущей конфигурации (5 секунд, 7 секунд, 9 секунд)
  • heartbeat. Для отправки append запроса фоловерам. В текущией конфигурации таймаут 2 секунды.

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

Для того чтобы можно было эмулировать отключения нод без опускания контейнеров, есть возможность через API останавливать ноды. После остановки нода молчит, она недоступна для других нод и для записи клиентом.
Но есть возможность получить содержимое лога и БД, что очень удобно для исследований поведения кластера в нешататной ситуации. Так же есть backdoor для вставки данных в лог, что полезно для эмуляции ситуации когда лидер не знает что он отрезан от кластера и продолжает принимать данные. Подробнее в
примерах

В API доступны следующее группы методов

  • Context. Получение метаданных ноды. Остановка/запуск ноды
  • Log. CRUD для лога.
  • Storage. Чтение данных из БД.
  • Replication. Эндпоинт для append/heartbeat запросов
  • Election. Эндпоинт для vote запросов
    . Метаданные узла. Раунд голосования, индекс последней примененной операции, данные нодов соседей и т.д. . Таймер начала выборов. Сервис для отправки и обработки vote реквеста . Таймер heeartbeat. Сервис для отправки и обработки append реквеста. . Интерфейс для доступа к логу операций. Его in memory реализация. Сервис для операций с логом. . Интерфейс для доступа к БД. Его in memory реализация. Сервис для операций с БД. . Фасад для удобного доступа к метаданным узла.

В текущей конфигурации запускается в единственном экземпляре. С API можно работать через swagger(подробенее в разделе API)

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

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

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

В API доступны следующее группы методов

  • Context. Получение метаданных с всего кластера. Остановка/запуск нод. Получения id лидера.
  • Log. Просмотр лога
  • Storage. CRUD для работы с БД.

Все остальное это просто редиректы к ендпоинтам серверных нод для чтения и записи данных.

В корне репозитория лежит docker-compose.yml его надо запустить, поднимются три серверных ноды и клиент.

После запуска через 5 секунд ноды выберут лидера и кластер будет готов к работе.

Таймауты можно перенастроить в docker-compose.yml

docker-compose logs -f raft-server-1

docker-compose logs -f raft-server-2

docker-compose logs -f raft-server-3

Если есть желание видеть логи с более подробной информацией то надо в docker-compose.yml раскомментировать для тэга command параметр с профилем debug

Пример: command: --raft.election-timeout=5 --raft.id=1 --server.port=8081 --spring.profiles.active=debug

Ниже рассмотрен ряд примеров работы с кластером

Все примеры нужно выполнять через swagger клиентской ноды. Все то же самое можно сделать обращаясь непосредстваенно к серверным нодам, но через клиента удобнее.

Можно переключить лог в debug режим, подробнее см. Get started

При отключении узла через API, CRUD недоступен. Но операция просмотра лога не блокируется, лог можно посмотреть.

Проверяем как поведет себя кластер при потере лидера

Например он равен 1

Через swagger отключаем лидера от кластера.

Видим что новый лидер выбран, а старый лидер по прежнему лидер, но он отключен (active: false)

Подключаем узлы, смотрим кто победил.

Вставляем,удаляем,редактируем данные через ендопоинты группы storage

Добавляем данные как описано в пункте штатная репликация

Потом отключаем узлы от кластера как описано в перевыборы

Добавляем данные, включаем/отключаем узлы. Желательно в разном порядке.

Проверяем что все узлы синхронизировались.

Эмулируем ситуацию когда лидер отключился от кластера и по прежнему считает что он лидер и продолжает принимать данные. В это время кластер выбрал нового лидера и тоже продолжает принимать данные.

Сначала надо отключить лидера и добавть данные в его лог.

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

Обращаемся по прежнему к серверной ноде напрямую

Теперь добавляем данные в кластер через доступного лидера. На этот раз штатно через клиентскую ноду, как описано в пункте штатная репликация

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

Поднимаем отключенного лидера проверяем его лог и хранилище, там должны быть правильные данные из кластера

Отказоустойчивый кластер OpenNebula используя алгоритм Raft

Настройка High Available облачной платформы OpenNebula используя распределенный консенсусный протокол для обеспечения отказоустойчивости и согласованности состояния Raft.

Что такое Raft?

Raft - это консенсусный алгоритм, который предназначен для простого понимания. Он эквивалентен привычному Paxos в отказоустойчивости и производительности. Разница заключается в том, что он разлагается на относительно независимые подзадачи и четко рассматривает все основные части, необходимые для практических систем.

Консенсусный алгоритм построен на основе двух концепций:

Состояние системы, в OpenNebula состояние системы - это данные, хранящиеся в таблицах базы данных (пользователи, ACL или виртуальные машины в системе).

Log, последовательность операторов SQL, которые последовательно применяются к базе данных OpenNebula на всех серверах для развития состояния системы.

Чтобы сохранить согласованное представление системы между серверами, изменения состояния системы выполняются через специальный узел, лидер (в дальнейшем Leader). Серверы в кластере OpenNebula выбирают один узел, чтобы быть лидером. Лидер периодически посылает запросы (heartbeats) другим серверам, последователям (в дальнейшем Follower), чтобы сохранить свое лидерство. Если лидер не может послать запрос, последователи продвигаются к кандидатам и начинают новые выборы.

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

Рекомендации и требования

Рекомендуемый размер для разворачивания кластера - это минимум 3 или 5 серверов, что обеспечивает отказоустойчивость при сбое 1-2 серверов. Добавлять дополнительные сервера или удалять старые можно после запуска кластера.

Для настройки High Available требуется:
  • Нечетное количество серверов (от 3-х минимум).
  • Рекомендуется идентичная конфигурация серверов (объем памяти, вычислительная мощность).
  • Идентичная настройка программного обеспечения серверов (различие в поле SERVER_ID файла /etc/one/oned.conf ).
  • Рекомендуется использовать соединение с базой данных того же типа, MySQL (Galera сервер не требуется).
  • Серверы должны иметь беспарольный доступ для связи друг с другом.
  • Плавающий IP, который будет назначен лидеру (Floating IP).
  • Общая файловая система(Ceph, NFS и пр.)
  • Sunstone без проксирования должен быть установлен и запущен на всех узлах кластера.

Настройка High Available кластера

В данном примере я покажу как настроить HA кластер из 5-и серверов:

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

Шаг 1. Первоначальная конфигурация Leader

Запускаем сервис OpenNebula и добавляем локальный сервер в существующую или новую зону.

Останавливаем сервис opennebula и обновляем конфигурацию SERVER_ID в файле /etc/one/oned.conf .

Активируем Raft-хуки для того, чтобы добавить плавающий адрес в кластер.

Так же ему был присвоен плавающий адрес (Floating IP).

Шаг 2. Добавление дополнительных серверов

Данная процедура удалит полностью базу на сервере и заменит её актуальной с Leader-сервера.

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

На Leader создайте полный бэкап актуальной базы и перенесите его на другие сервера вместе с файлами из директории /var/lib/one/.one/

Копируем дамп базы на сервера, которые будут участниками кластера:

Удаляем на хостах директорию /var/lib/one/.one и копируем её же с Leader-сервера (сохраняйте при копировании привилегии oneadmin:oneadmin ):

Останавливаем сервис opennebula на Follower хостах и ресторим скопированную базу.

Переходим на Leader сервере и добавляем в зону новые хосты (рекомендую добавлять сервера по-одному!)

Проверяем зону на Leader-сервере. Новый сервер находится в состоянии ошибки, так как OpenNebula на новом сервере не запущена. Запомните идентификатор сервера, в этом случае он равен 1.

Ошибка будет отображаться пр причине того, что сервис OpenNebula не запущен на Follower хосте. Это нормально.

Переключаемся на добавленный Follower-сервер и обновляем конфигурацию SERVER_ID в файле /etc/one/oned.conf , указывая в качестве SERVER_ID значение из предыдущего шага, исходя из предыдущего шага. Обязательно включаем хуки Raft, как это было выполнено на Leader.

Запускаем сервис OpenNebula на Follower-сервере и проверяем на Leader-сервере состояние зоны.

Может случиться так, что значения TERM/INDEX/COMMIT не соответствуют указанным выше. Это не важно! Они будут автоматически синхронизироваться при изменении базы данных.

Повторяем Шаг 2 для того, чтобы добавить дополнительные сервера в кластер.

Обратите внимание, что вы можете добавлять сервера в кластер, только в случае его нормальной работы. Это означает, что работает Leader, а остальные находятся в состоянии Follower. Если в состоянии error присутствует хотя бы один сервер - исправьте это перед продолжением.

Читайте также: