Ну, вы можете много слышать о «распределенной системе» или о «плоте». Но вы можете задаться вопросом, как его использовать.
В этом уроке мы обсудим, как работать с базой данных kv, работающей в кластерном режиме, с использованием Golang и библиотеки Raft. Это руководство больше сосредоточено на коде и использовании кластеров raft, чем на обсуждении алгоритмов raft.
Что такое Raft?
Raft — это протокол, с помощью которого кластер узлов может поддерживать реплицированный конечный автомат.
Конечный автомат синхронизируется за счет использования реплицированного журнала. Однако подробности протокола Raft выходят за рамки этого руководства. Дополнительные сведения о Raft см. в разделе В поисках понятного алгоритма консенсуса.
Реализация плота на Golang
Алгоритм Raft ищет понятный алгоритм консенсуса. К сожалению, для большинства библиотек go требовалось глубокое знание их реализации и API.
Библиотека raft, которую мы будем использовать в этом руководстве, была создана в соответствии с принципом понятности raft, и ее единственная цель — обеспечить консенсус с минималистичным, простым, чистым и идиоматичным API.
Etcd Raft
— наиболее широко используемая библиотека Raft в производстве. Но он следует минималистичной философии дизайна, реализуя только базовый алгоритм плота, который оставляет пробелы и двусмысленности.
Итак, вместо того, чтобы изобретать велосипед, библиотека shaj13/raft
использует etcd raft
в качестве ядра.
Вот как вы можете извлечь выгоду из мощности и стабильности etcd raft
с понятным API. Действительно, это заставляет вас сосредоточиться на создании отличного программного обеспечения.
Создание нашего проекта
Начнем с создания нашего проекта.
mkdir raft && cd raft && go mod init raft && touch raft.go
Это создаст новый каталог с именем raft
и инициализирует проект с go.mod
.
Наши первые строки кода
Прежде чем мы напишем какой-либо код, нам нужно написать некоторый обязательный код, чтобы программа работала.
package main import ( "log" ) func main() { log.Println("Raft !!") }
Создание командной строки нашего процесса
Мы удалим строку, выводящую Raft!!
, добавим пакет флагов и инициализируем его.
package main import "flag" func main() { addr := flag.String("raft", "", "raft server address") join := flag.String("join", "", "join cluster address") api := flag.String("api", "", "api server address") state := flag.String("state_dir", "", "raft state directory (WAL, Snapshots)") flag.Parse() }
Создание нашей базы данных kv
Мы собираемся реализовать структуру с именем stateMachine
определить kv
базу данных, которая считывает и применяет значение ключа, а также делает database
моментальный снимок и восстанавливает его.
Создание наших конечных точек
Мы собираемся добавить пакет мультиплексирования гориллы и инициализировать маршрутизатор внутри функции main.
router := mux.NewRouter()
Теперь мы собираемся установить конечные точки нашего API, способ, которым мы это настроим, — создать все наши конечные точки в основной функции, каждой конечной точке нужна функция для обработки запроса, и мы определим их ниже основной функции.
router.HandleFunc("/", http.HandlerFunc(save)).Methods("PUT", "POST") router.HandleFunc("/{key}", http.HandlerFunc(get)).Methods("GET") router.HandleFunc("/mgmt/nodes", http.HandlerFunc(nodes)).Methods("GET") router.HandleFunc("/mgmt/nodes/{id}", http.HandlerFunc(removeNode)).Methods("DELETE")
Обработчики маршрутов
Теперь нам просто нужно определить функции, которые будут обрабатывать запросы.
Прежде чем мы начнем, нам нужно объявить две переменные, чтобы разрешить доступ к данным маршрутов.
- Узел представляет процесс плота
- FSM представляет собой конечный автомат процесса плота
Создание нашего плотного узла и сервера
В основной функции и под маршрутизатором нам нужно объявить наш узел плота и gRPC
сервер, чтобы позволить текущему узлу плота взаимодействовать с другими узлами плота.
Теперь ваш файл должен выглядеть примерно так:
Тестирование нашего кода
Строительство плотного кластера
go mod tidy && go build raft.go
Спуск одноузлового плота
Сначала запустим одночленный кластер raft:
./raft -state_dir=$TMPDIR/1 -raft :8080 -api :9090
Каждый процесс raft поддерживает один экземпляр raft и сервер ключей-значений.
Адрес сервера плота (-raft
), каталог состояния (-state_dir
) и адрес сервера ключ-значение http (-api
) передаются через командную строку.
Затем сохраните значение («medium
») для ключа («hello
»):
curl -L http://127.0.0.1:9090/ -X PUT -d '{"Key":"hello", "Value":"
medium"}'
Наконец, получите сохраненный ключ:
curl -L http://127.0.0.1:9090/hello
Запуск локального кластера
Давайте приведем два дополнительных экземпляра плота.
./raft -state_dir $TMPDIR/2 -raft :8081 -api :9091 -join :8080 ./raft -state_dir $TMPDIR/3 -raft :8082 -api :9092 -join :8080
Теперь можно записать пару ключ-значение в любой член кластера и аналогичным образом получить ее из любого члена.
Отказоустойчивость
Чтобы протестировать восстановление кластера, запишите значение «foo
» в ключ «foo
»:
curl -L http://127.0.0.1:9090/ -X PUT -d '{"Key":"foo", "Value":"foo"}'
Далее остановите узел (9092) и замените значение на «bar», чтобы проверить доступность кластера:
curl -L http://127.0.0.1:9090/ -X PUT -d '{"Key":"foo", "Value":"bar"}' curl -L http://127.0.0.1:9090/foo
Наконец, верните узел обратно и убедитесь, что он восстанавливается с обновленным значением «bar
»:
curl -L http://127.0.0.1:9092/foo
Реконфигурация кластера
Узлы можно добавлять, удалять или обновлять из работающего кластера. Удалим узел с помощью запросов к REST API.
Во-первых, перечислите все узлы плотного кластера и получите идентификатор узла.
curl -L http://127.0.0.1:9090/mgmt/nodes
Затем удалите узел с помощью запроса DELETE:
curl -L http://127.0.0.1:9090/<ID> -X DELETE
Узел отключится, как только кластер обработает этот запрос.
Подробнее о библиотеке raft
можно узнать на GitHub и GoDoc. Большое спасибо за чтение. Я надеюсь, что вы нашли эту статью полезной.