SWIMпротокол построения кластера

Шпилевой Владислав

План доклада

  1. Failure Detection
  2. Gossip-протоколы
  3. Протокол SWIM
  4. Схема работы SWIM
  5. Реализация SWIM в Тарантуле
  6. Расширения SWIM
  7. Примеры применения
  8. Производительность
  9. Планы

Горизонтальное

масштабирование

Failure detection

Принятие решений

Ping

Ping

Ping

Ping

Ping

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

Починка отказов

Ping  проверять лидера/мастера/...

Heartbeat  узел сам делает рассылку

Failure detection

узлов

сообщений в сети

O(N^2)
N

"Все со всеми" не работает

Gossip

узлов

сообщений в сети

O(N)
N

Протоколы "слухов", "инфекций"

Gossip

!

!

!

!

!

!

!

!

Распространяют событие все, а не один узел

Алгоритмы рандомизированы:

  • нет синхронизации;
  • нагрузка равномерна

Gossip. Обнаружение отказов

Прямой ping

Непрямой ping

Сеть сломана

Ping

Ack

PingRequest

Ping

Ack

Ack

Свой ping

Сеть сломана

... или Я?

Gossip. Гранулярность отказов

Непрерывное состояние

Жив

Мертв

VS

Дискретное состояние

Критерии:

  • Количество неотвеченных ACK
  • Задержка
  • Прямая или непрямая доступность

...

Gossip. Направление слухов

Push

Событие

!

!

!

Pull

!

Запрос

!

Событие

!

Push-Pull

!

!

!

!

!

!

Распространение:

O(log(N))

Обнаружение:

O(1)

Нагрузка на сеть:

O(N)

Нагрузка на узел:

O(1)

Gossip. Применения

Lifeguard

SWIM

SWIM

Scalable

!

O(1)

Weakly-Consistent

!

!

!

!

!

!

O(log(N))

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

SWIM

Обнаружение отказов

Распространение событий

Ping Request

Ack

Ack

Подозрение отказа

!

Ping

События

UDP-пакет

T

T

Ping +

!

!

!

!

!

Каждое событие посылается                    раз

Подозрение не подтвердилось

log(N)

SWIM

Инкарнация

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

  • Опровержение ложных слухов
  • Защита от проблем UDP
  • Если равны  предполагай худшее

Tarantool SWIM

Раунды

Адресация

UUID

URI1

URI2

URI3

Адрес можно менять

Heartbeat Rate

Модуль

Можно создавать много узлов

Конфигурируется почти все

Есть ручное управление таблицей узлов кластера

Можно обращаться к узлам

Асинхронная подписка на события

Аккуратный вывод из кластера с уведомлением всех

Случайное перемешивание

swim = require('swim')

s = swim.new([cfg])

s:quit()
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])

SWIM  Пример

На двух Тарантулах запущены два 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()

 

 

 

 

Ping + UUID + IP:Port

Расширения  Антиэнтропия

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  узнают друг про друга  антиэнтропия шлется всегда

Расширения  Payload

Как, подключившись к узлу, понять, куда подключаться по 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()

Плохой способ  удалить узел

  1. Дождаться, пока кто-то отправит Ping A
  2. Дождаться неответов на 5 Ping'ов
  3. Дождаться распространения смерти A  логарифм размера кластера

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()

Быстро, можно реагировать не как на смерть

Расширения  Детектор перезагрузки

Инкарнации не всегда достаточно

A

B

A 1

Payload:

Два узла. B видит инкарнацию A = 1 и пейлоад "синий кружок".

A 1

Payload:

A 1

Payload:

A перезапущен, инкарнация снова 1, пейлоад другой.

Хей, у меня новый пейлоад с инкарнацией 1!

A1 vs A1?  Ничего нового. Пейлоад не изменился.

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

А выключен.

!

Расширения  Детектор перезагрузки

swim = require('swim')
s = swim.new({generation = 1})

swim = require('swim')
s = swim.new()
saved_e = nil
s:on_member_event(function(m, e)
    saved_e = e
end)
s:cfg({uri = 3334, uuid = uuid2})

s:probe_member(3333)
s:cfg({uri = 3333, uuid = uuid1})

Перезапуск

swim = require('swim')

s = swim.new({generation = 2})

s:cfg({uri = 3333, uuid = uuid1})
tarantool> saved_e
---
- is_new_version: true
  is_new_generation: true
  is_update: true
  is_new_incarnation: true
...

В Тарантуле инкарнация состоит из двух частей, одна из которых возрастает всегда

При создании можно указать generation  это число будет частью инкарнации. По умолчанию  текущее время

Указать надо до или во время первой конфигурации

Запускается второй узел. Они связываются.

Первый перезапускается

После старта указывается новый generation

Второй сразу об этом узнает, через событие

Практика  Построение кластера

Дата-центр

Дата-центр

Задача:

Создать 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:payload().box)
end

box.cfg{
    listen = box_uri,
    replication = rep,
    instance_uuid = uuid,
}

>

>

Один SWIM на узел

>

>

Таблица с узлами дата-центра

>

Обновляется автоматически

>

>

Рассказать всем свои ДЦ и порт

>

Периодически искать новые узлы

>

Для сборки репликасета надо найти 2 узла

>

Сборка и запуск репликации готовы!

Практика  Прочие применения

Среди нескольких Тарантулов выбрать одного для принятия решений о конфигурации, о смене мастера

  • Выборы лидера

  • Мониторинг

Находить отказавшие узлы, уведомлять администратора, перенаправлять нагрузку, начинать смену мастера

Производительность

При рассылке сообщений раз в секунду

Размер кластера

Время распространения события, секунды

800

25

200

50

100

150

300

250

500

5

4

6

7

8

 реализация SWIM

 настоящий

log_2(x)

Итоги

SWIM

  • Обнаружение
  • Распространение
  • Простота
  • Доступность

Планы  Интеграция с репликацией

  • Raft для автосмены лидера кластера
  • Автосборка кластера из коробки
  • Обнаружение топологии кластера

Highload 2019: SWIM - протокол построения кластера

By Vladislav Shpilevoy

Highload 2019: SWIM - протокол построения кластера

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

  • 1,364