Vladislav Shpilevoy PRO
Database C developer at Tarantool. Backend C++ developer at VirtualMinds.
Шпилевой Владислав
Горизонтальное
масштабирование
Принятие решений
Ping
Ping
Ping
Ping
Ping
Вывод нерабочих узлов из кластера
Починка отказов
Ping - проверять лидера/мастера/...
Heartbeat - узел сам делает рассылку
узлов
сообщений в сети
"Все со всеми" не работает
узлов
сообщений в сети
Протоколы "слухов", "инфекций"
!
!
!
!
!
!
!
!
Распространяют событие все, а не один узел
Алгоритмы рандомизированы:
Сеть сломана
Ping
Ack
PingRequest
Ping
Ack
Ack
Жив
Мертв
VS
Критерии:
...
Ping
!
!
!
!
Ping +
Meta
!
Ack
!
!
!
Ping +
Meta
!
Ack
!
!
!
Распространение:
Обнаружение:
Lifeguard
Scalable
!
Weakly-Consistent
!
!
!
!
!
!
A
B
C
A
A
Infection-Style Process
!
!
!
!
Group Membership
D
E
H
G
F
A, B, C, D, E, F, G, H
A, B, C, D, E, F, G, H
A, B, C, D, E, F, G, H
A, B, C, D, E, F, G, H
A, B, C, D, E, F, G, H
A, B, C, D, E, F, G, H
A, B, C, D, E, F, G, H
Ping Request
Ack
Ack
Подозрение отказа
!
Ping
События
UDP пакет
T
T
Ping +
!
!
!
!
!
Каждое событие посылается всем по разу
Подозрение не подтвердилось
C
A
B
B
B
B жив
?
B
B мертв
?
B
B
B
Предполагай худшее
Но как B очистит свое имя?
Вводится понятие "инкарнация" для опровержения слухов
B 1
B 1
B 1
Знаешь? Как бы сказать ... ты мертв
?!
B 2
B 2
?
B 2
Предполагай худшее, но больше инкарнация - новее информация
B 2
Итог:
UUID
URI1
URI2
URI3
Адрес можно менять
Heartbeat Rate
Модуль
Можно создавать много узлов
Конфигурируется почти все
Есть ручное управление таблицей узлов кластера
Можно обращаться к узлам
Асинхронная подписка на события
Аккуратный вывод из кластера с уведомлением всех
Случайное перемешивание
swim = require('swim') s = swim.new([cfg]) s:cfg({heartbeat_rate = ..., ack_timeout = ..., gc_mode = ..., uri = ..., uuid = ...}) s:probe_member(uri) s:add_member({uuid = ..., uri = ...}) s:remove_member(uuid) s:broadcast([port]) s:size() s:member_by_uuid(uuid) s:pairs() s:on_member_event(new_trigger[, old_trigger]) s:quit() s:delete()
На двух Тарантулах запущены два SWIM узла
Первый отправляет Ping запрос второму
Теперь узлы друг друга знают, пингуют, делятся информацией
При помощи триггеров можно узнавать момент и детали обновления таблицы
Если долго нет ответа от кого-либо, то он будет помечен мертвым спустя несколько пингов
swim = require('swim') s = swim.new({uri = 3333, uuid = uuid1, ack_timeout = 0.1}) local event s:on_member_event(function(m, ev, ctx) event = {m, ev, ctx} end)
swim = require('swim') s = swim.new({uri = 3334, uuid = uuid2, ack_timeout = 0.1})
s:probe_member(3334)
tarantool> s:size() -- - 2 ...
tarantool> event --- - - uri: 127.0.0.1:3334 status: alive incarnation: 0 uuid: 00000000-0000-1000-8000-000000000002 - is_new: true ...
tarantool> member = event[1] --- ... tarantool> member:status() --- - dead ...
tarantool> s:size() -- - 2 ...
s:delete()
A
B
A, B
A, B
Узлы A и B знают друг про друга
С
Появляется третий, знающий B. Как он узнает про А?
B, C
B разошлет событие "новый узел", но оно теряется
Нужна синхронизация таблиц. Каждое сообщение несет в себе случайную часть таблицы отправителя.
Ping
События
UDP пакет
Антиэнтропия
A, B, C
A, B, C
A, B, C
Новый узел С!
Моя таблица - A, B, C
Моя таблица - A, B, C
Рано или поздно A и C узнают друг про друга - антиэнтропия шлется всегда
Работает само, не нужно настраивать
Как, подключившись к узлу, понять, куда подключаться по TCP, или как распространять свои данные?
swim = require('swim') s = swim.new({uri = 3333, uuid = uuid1})
swim = require('swim')
s = swim.new({uri = 3335, uuid = uuid2})
box.cfg{listen = 3336}
s:set_payload({tport = 3336, any_other_data = value
Метод set_payload позволяет распространить произвольную информацию
s:broadcast(3335)
Достаточно любым образом соединить узлы SWIM
p = s:member_by_uuid(uuid2):payload() tarantool> p --- - {'tport': 3336, 'any_other_data': value} ...
На всех узлах payload будет доступен
tarantool> require('net.box').connect(p.tport) --- - peer_uuid: 1262ec51-46f4-488e-8925-853f55367e61 schema_version: 73 protocol: Binary state: active peer_version_id: 131584 port: '3336' ...
Можно подключаться!
tarantool> s:size() -- - 2 ...
SWIM может быть использован в открытой сети
Сеть подвержена атакам
В Tarantool SWIM есть встроенное шифрование
swim = require('swim') s = swim.new() s:set_codec({algo = 'aes192', mode = 'cbc', key = private_key})
Алгоритмы:
DES, AES128/192/256
Режимы:
ECB, CBC, CFB, OFB
A
B
C
D
Узел A хочет выйти из кластера
swim_a:delete()
Плохой способ - удалить узел
Ping
A
A
A
Очень долго и неотличимо от смерти
A
B
C
D
Узел A хочет выйти из кластера
swim_a:quit()
Хороший способ - уведомить всех
A
A
A
A сам разошлет всем уведомление
swim = require('swim') s = swim.new({uri = 3333, uuid = uuid1})
swim = require('swim') s = swim.new({uri = 3334, uuid = uuid2})
s:probe_member(3334)
tarantool> s:member_by_uuid(uuid2):status() --- - left ...
s:quit()
Быстро, можно реагировать не как на смерть
Дата-центр
Дата-центр
Задача:
Создать N репликасетов по 2 реплики каждый, сгруппированных по дата-центрам.
datacenter = arg[1]
swim_uri = arg[2]
box_uri = arg[3]
uuid = arg[4]
Характеристики одного узла
fiber = require('fiber')
fiber.create(function()
while true do
for port in pairs(swim_ports) do
swim:broadcast(port)
end
fiber.sleep(60)
end
end)
swim = require('swim').new()
nodes = {}
function on_event(m, e)
if e:is_drop() then
nodes[m:uuid()] = nil
elseif e:is_new_payload() and
m:payload() then
local dc = m:payload().dc
if dc == datacenter then
nodes[m:uuid()] = m
end
end
end
swim:on_member_event(on_event)
swim:cfg({uri = swim_uri, uuid = swim_uuid})
swim:set_payload({dc = datacenter,
box = box_uri})
while map_size(nodes) ~= 2 do
fiber.sleep(5)
end
local rep = {}
for uuid, n in pairs(nodes) do
table.insert(rep, n:uri())
end
box.cfg{
listen = box_uri,
replication = rep,
instance_uuid = uuid,
}
>
>
Один SWIM на узел
>
>
Таблица с узлами датацентра
>
Обновляется автоматически
>
>
Рассказать всем свои ДЦ и порт
>
Периодически искать новые узлы
>
Для сборки репликасета надо найти 2 узла
>
Сборка и запуск репликации готовы!
A
B
C
D
Есть кластер с проблемной сетью B-D
Спустя время B начинает подозревать D
D 1
Успевает это распространить
D 1
D 1
Теперь D начинает отвечать, но не знает, что его подозревают ...
...
...
D не менял инкарнацию, а значит B не сможет распространять обновление
>
Придется ждать, пока до D дойдут слухи
A
B
C
D
D 1
D 1
D 1
...
...
Ping + D
D 2
Ack + D 2
D 2
D 2
D 2
Ping
Ack + D 1
Каждый Ping будет содержать статус получателя с точки зрения отправителя
Получатель немедленно реинкарнируется в случае статуса != живой
Слух опровергнут сразу после восстановления сети
A
B
Ping +
!
!
!
!
Ack
A
B
Ping +
!
!
!
!
Ack +
!
!
swim = require('swim') s = swim.new({ uri = 3333, uuid = uuid1, generation = number }) function on_event(m, event) if event:is_restart() then ... end end
Generation - счетчик перезапусков, можно сохранить на диск и при перезапуске увеличить
Обновление от 23.06,2019 - это уже сделано
By Vladislav Shpilevoy
SWIM - это протокол обнаружения и мониторинга узлов кластера, распространения событий и данных между ними. Протокол особенен своей легковесностью, децентрализованностью и независимостью скорости работы от размера кластера. В докладе рассказывается о том, как устроен протокол SWIM, как и с какими расширениями он реализован в Тарантуле.
Database C developer at Tarantool. Backend C++ developer at VirtualMinds.