Обзор
Это спецификация для протокола Garlic Farm, основанного на JRaft, его коде “exts” для реализации через TCP, и его примерном приложении “dmprinter” JRAFT. JRaft — это реализация протокола Raft RAFT.
Мы не смогли найти ни одной реализации с задокументированным протоколом передачи данных. Однако реализация JRaft достаточно проста, так что мы могли исследовать код и затем задокументировать его протокол. Этот проект — результат этих усилий.
Это будет серверная часть для координации маршрутизаторов, публикующих записи в Meta LeaseSet. См. предложение 123.
Цели
- Малый размер кода
- Основан на существующей реализации
- Без сериализованных объектов Java или каких-либо специфичных для Java функций или кодировок
- Любое начальное конфигурирование не является целью. Предполагается, что как минимум один сервер жестко закодирован или сконфигурирован вне этого протокола.
- Поддержка как внеполосных, так и внутри I2P сценариев использования.
Дизайн
Протокол Raft не является конкретным протоколом; он определяет только конечный автомат. Поэтому мы документируем конкретный протокол JRaft и основываем на нем наш протокол. В протокол JRaft не вносятся изменения за исключением добавления аутентификационного рукопожатия.
Raft выбирает Лидера, задача которого — публиковать журнал. Журнал содержит данные конфигурации Raft и данные приложения. Данные приложения содержат статус каждого маршрутизатора сервера и Пункт назначения для кластера Meta LS2. Серверы используют общий алгоритм для определения издателя и содержания Meta LS2. Издатель Meta LS2 НЕ обязательно является Лидером Raft.
Спецификация
Протокол передачи данных работает через SSL сокеты или не-SSL сокеты I2P. Сокеты I2P проксируются через HTTP-прокси. Поддержка сокетов clearnet без SSL отсутствует.
Рукопожатие и аутентификация
Не определено JRaft.
Цели:
- Метод аутентификации пользователь/пароль
- Идентификатор версии
- Идентификатор кластера
- Расширяемость
- Простота проксирования при использовании для I2P сокетов
- Не раскрывать сервер как сервер Garlic Farm без необходимости
- Простой протокол, чтобы не требовать полноценную веб-серверную реализацию
- Совместимость с общими стандартами, чтобы реализации могли использовать стандартные библиотеки, при желании
Мы будем использовать рукопожатие, похожее на websocket WEBSOCKET и аутентификацию HTTP Digest RFC-2617. Базовая аутентификация RFC 2617 НЕ поддерживается. При проксировании через HTTP-прокси, мы общаемся с прокси, как указано в RFC-2616.
Учетные данные
Хотя бы имена пользователей и пароли являются кластерообразующими, или сервер-зависимыми, это зависит от реализации.
HTTP-запрос 1
Инициатор отправит следующее.
Все строки заканчиваются CRLF, как это требует HTTP.
GET /GarlicFarm/CLUSTER/VERSION/websocket HTTP/1.1
Host: (ip):(port)
Cache-Control: no-cache
Connection: close
(любые другие заголовки игнорируются)
(пустая строка)
CLUSTER — это имя кластера (по умолчанию "farm")
VERSION — это версия Garlic Farm (в настоящее время "1")
HTTP-ответ 1
Если путь неверный, получатель отправит стандартный ответ “HTTP/1.1 404 Not Found”, как в RFC-2616.
Если путь правильный, получатель отправит стандартный ответ “HTTP/1.1 401 Unauthorized”, включая заголовок аутентификации HTTP digest WWW-Authenticate, как в RFC-2617.
Обе стороны затем закроют сокет.
HTTP-запрос 2
Инициатор отправит следующее, как в RFC-2617 и WEBSOCKET.
Все строки заканчиваются CRLF, как это требует HTTP.
GET /GarlicFarm/CLUSTER/VERSION/websocket HTTP/1.1
Host: (ip):(port)
Cache-Control: no-cache
Connection: keep-alive, Upgrade
Upgrade: websocket
(Заголовки Sec-Websocket-* если проксируются)
Authorization: (Заголовок аутентификации HTTP digest, как в RFC 2617)
(любые другие заголовки игнорируются)
(пустая строка)
CLUSTER — это имя кластера (по умолчанию "farm")
VERSION — это версия Garlic Farm (в настоящее время "1")
HTTP-ответ 2
Если аутентификация неверна, получатель отправит еще один стандартный ответ “HTTP/1.1 401 Unauthorized”, как в RFC-2617.
Если аутентификация верна, получатель отправит следующий ответ, как в WEBSOCKET.
Все строки заканчиваются CRLF, как это требует HTTP.
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket
(Заголовки Sec-Websocket-*)
(любые другие заголовки игнорируются)
(пустая строка)
После получения этого, сокет остается открытым. Протокол Raft, как определено ниже, продолжается на том же сокете.
Кэширование
Учётные данные должны кэшироваться по крайней мере на один час, чтобы последующие соединения могли сразу перейти к “HTTP-запрос 2” выше.
Типы сообщений
Существует два типа сообщений: запросы и ответы. Запросы могут содержать записи журнала и имеют переменный размер; ответы не содержат записей журнала и имеют фиксированный размер.
Типы сообщений 1-4 — это стандартные сообщения RPC, определенные в Raft. Это ядро протокола Raft.
Типы сообщений 5-15 — это расширенные сообщения RPC, определенные в JRaft, для поддержки клиентов, динамических изменений сервера и эффективной синхронизации журналов.
Типы сообщений 16-17 — это сообщения RPC сжатия журнала, определенные в разделе 7 Raft.
| Сообщение | Номер | Отправлено | Кому отправлено | Примечания |
|---|---|---|---|---|
| RequestVoteRequest | 1 | Candidate | Follower | Стандартное RPC Raft; журнал не должен содержать записи |
| RequestVoteResponse | 2 | Follower | Candidate | Стандартное RPC Raft |
| AppendEntriesRequest | 3 | Leader | Follower | Стандартное RPC Raft |
| AppendEntriesResponse | 4 | Follower | Leader / Client | Стандартное RPC Raft |
| ClientRequest | 5 | Client | Leader / Follower | Ответ — AppendEntriesResponse; должен содержать только записи приложения в журнале |
| AddServerRequest | 6 | Client | Leader | Должен содержать только одну запись ClusterServer в журнале |
| AddServerResponse | 7 | Leader | Client | Лидер также отправит запрос JoinClusterRequest |
| RemoveServerRequest | 8 | Follower | Leader | Должен содержать только одну запись ClusterServer в журнале |
| RemoveServerResponse | 9 | Leader | Follower | |
| SyncLogRequest | 10 | Leader | Follower | Должен содержать только одну запись LogPack в журнале |
| SyncLogResponse | 11 | Follower | Leader | |
| JoinClusterRequest | 12 | Leader | New Server | Приглашение присоединиться; должен содержать только одну запись Configuration в журнале |
| JoinClusterResponse | 13 | New Server | Leader | |
| LeaveClusterRequest | 14 | Leader | Follower | Команда на выход |
| LeaveClusterResponse | 15 | Follower | Leader | |
| InstallSnapshotRequest | 16 | Leader | Follower | Раздел 7 Raft; должен содержать только одну запись SnapshotSyncRequest в журнале |
| InstallSnapshotResponse | 17 | Follower | Leader | Раздел 7 Raft |
Установление
После HTTP-рукопожатия, последовательность установления следующая:
Новый сервер Алиса Случайный Follower Боб
ClientRequest ------->
<--------- AppendEntriesResponse
Если Боб говорит, что он лидер, продолжаем, как ниже.
Иначе, Алиса должна отключиться от Боба и подключиться к лидеру.
Новый сервер Алиса Лидер Чарли
ClientRequest ------->
<--------- AppendEntriesResponse
AddServerRequest ------->
<--------- AddServerResponse
<--------- JoinClusterRequest
JoinClusterResponse ------->
<--------- SyncLogRequest
ИЛИ InstallSnapshotRequest
SyncLogResponse ------->
ИЛИ InstallSnapshotResponse
Последовательность отключения:
Follower Алиса Лидер Чарли
RemoveServerRequest ------->
<--------- RemoveServerResponse
<--------- LeaveClusterRequest
LeaveClusterResponse ------->
Последовательность выборов:
Кандидат Алиса Follower Боб
RequestVoteRequest ------->
<--------- RequestVoteResponse
если Алиса заканчивает выборы:
Лидер Алиса Follower Боб
AppendEntriesRequest ------->
(heartbeat)
<--------- AppendEntriesResponse
Определения
- Источник: Определяет инициатора сообщения
- Назначение: Определяет получателя сообщения
- Термы: См. Raft. Инициализируется 0, увеличивается монотонно
- Индексы: См. Raft. Инициализируется 0, увеличивается монотонно
Запросы
Запросы содержат заголовок и ноль или более записей журнала. Запросы содержат заголовок фиксированного размера и необязательные записи в журнале переменного размера.
Заголовок запроса
Заголовок запроса составляет 45 байт, как показано ниже. Все значения — неотрицательные большие-endian.
Тип сообщения: 1 байт
Источник: ID, 4 байта целое число
Назначение: ID, 4 байта целое число
Терм: Текущий терм (см. примечания), 8 байт целое число
Последний терм: 8 байт целое число
Последний индекс: 8 байт целое число
Индекс коммита: 8 байт целое число
Размер записей: Общий размер в байтах, 4 байта целое число
Записи: см. ниже, общая длина, как указано
Примечания
В RequestVoteRequest, Терм — это терм кандидата. В противном случае, это текущий терм лидера.
В AppendEntriesRequest, когда размер записей журнала равен нулю, это сообщение сердцебиение (keepalive).
Записи в журнале
Журнал содержит ноль или более записей. Каждая запись в журнале следующим образом. Все значения — неотрицательные большие-endian.
Терм: 8 байт целое число
Тип значения: 1 байт
Размер записи: В байтах, 4 байта целое число
Запись: длина, как указано
Содержание журнала
Все значения — неотрицательные большие-endian.
| Тип значения журнала | Номер |
|---|---|
| Приложение | 1 |
| Конфигурация | 2 |
| ClusterServer | 3 |
| LogPack | 4 |
| SnapshotSyncRequest | 5 |
Приложение
Содержимое приложения кодируется в UTF-8 JSON. См. раздел Слой Приложения ниже.
Конфигурация
Используется для лидера, чтобы сериализовать новую конфигурацию кластера и распространить её на участников. Содержит ноль или более конфигураций ClusterServer.
Индекс журнала: 8 байт целое число
Последний индекс: 8 байт целое число
Данные ClusterServer для каждого сервера:
ID: 4 байта целое число
Длина данных конца: в байтах, 4 байта целое число
Данные конца: ASCII строка вида "tcp://localhost:9001", длина, как указано
ClusterServer
Конфигурационная информация для сервера в кластере. Это включено только в сообщение AddServerRequest или RemoveServerRequest.
При использовании в сообщении AddServerRequest:
ID: 4 байта целое число
Длина данных конца: В байтах, 4 байта целое число
Данные конца: ASCII строка вида "tcp://localhost:9001", длина, как указана
При использовании в сообщении RemoveServerRequest:
ID: 4 байта целое число
LogPack
Это включено только в сообщение SyncLogRequest.
Следующее сжимается перед передачей:
Длина данных индекса: В байтах, 4 байта целое число
Длина данных журнала: В байтах, 4 байта целое число
Данные индекса: 8 байт для каждого индекса, длина, как указана
Данные журнала: длина, как указана
SnapshotSyncRequest
Это включено только в сообщение InstallSnapshotRequest.
Последний индекс: 8 байт целое число
Последний терм: 8 байт целое число
Длина данных конфигурации: В байтах, 4 байта целое число
Данные конфигурации: длина, как указана
Смещение: Смещение данных в базе данных, в байтах, 8 байт целое число
Длина данных: В байтах, 4 байта целое число
Данные: длина, как указана
Завершено: 1 — завершено, 0 — не завершено (1 байт)
Ответы
Все ответы составляют 26 байт, как показано ниже. Все значения — неотрицательные большие-endian.
Тип сообщения: 1 байт
Источник: ID, 4 байта целое число
Назначение: Обычно фактический ID назначения (см. примечания), 4 байта целое число
Терм: Текущий терм, 8 байт целое число
Следующий индекс: Инициализируется как лидер последний индекс + 1, 8 байт целое число
Принято: 1, если принято, 0, если не принято (см. примечания), 1 байт
Примечания
ID Назначения обычно указан как фактический ID назначения для этого сообщения. Однако, для AppendEntriesResponse, AddServerResponse, и RemoveServerResponse, это ID текущего лидера.
В RequestVoteResponse, Принято 1 — это голос за кандидата (запрашивающего), и 0 для отказа в голосе.
Слой Приложения
Каждый сервер периодически публикует Данные Приложения в журнале в ClientRequest. Данные приложения содержат статус каждого маршрутизатора сервера и Пункт назначения для кластера Meta LS2. Серверы используют общий алгоритм для определения издателя и содержания Meta LS2. Сервер с “лучшими” недавними статусами в журнале — это издатель Meta LS2. Издатель Meta LS2 НЕ обязательно является Лидером Raft.
Содержание данных приложения
Содержимое приложения кодируется в UTF-8 JSON, для упрощения и расширяемости. Полная спецификация TBD. Цель — предоставить достаточно данных, чтобы написать алгоритм для определения “лучшего” маршрутизатора для публикации Meta LS2, а также чтобы у издателя было достаточно информации для взвешивания Пунктов назначения в Meta LS2. Данные будут содержать как статистику маршрутизатора, так и Пунктов назначения.
Данные могут содержать опционально удаленные данные о состоянии других серверов, а также возможность извлечения Meta LS. Эти данные не будут поддерживаться в первом выпуске.
Данные могут содержать опционально конфигурационную информацию, опубликованную администратором клиента. Эти данные не будут поддерживаться в первом выпуске.
Если “name: value” указан, это определяет ключ и значение карты JSON. Иначе, спецификация TBD.
Данные кластера (верхний уровень):
- cluster: Имя кластера
- date: Дата этих данных (долгий срок, мс с эпохи)
- id: Raft ID (целые числа)
Данные конфигурации (config):
- Любые конфигурационные параметры
Статус публикации MetaLS (meta):
- destination: пункт назначения в металле, base64
- lastPublishedLS: если присутствует, кодирование баз64 последнего опубликованного метала
- lastPublishedTime: в мс, или 0 если никогда не было
- publishConfig: статус конфигурации публикации выключен/включен/авто
- publishing: статус издателя метала истинный/ложный
Данные маршрутизатора (router):
- lastPublishedRI: если присутствует, кодирование баз64 последней опубликованной информации о маршрутизаторе
- uptime: Время работы в мс
- Задержка задач
- Исследовательские туннели
- Участвующие туннели
- Настроенная пропускная способность
- Текущая пропускная способность
Пункты назначения (destinations): Список
Данные пункта назначения:
- destination: пункт назначения, base64
- uptime: Время работы в мс
- Настроенные туннели
- Текущие туннели
- Настроенная пропускная способность
- Текущая пропускная способность
- Настроенные соединения
- Текущие соединения
- Данные черного списка
Данные удаленного анализа маршрутизатора:
- Последняя версия RI, которую видели
- Время получения LS
- Данные теста соединения
- Профильные данные ближайших floodfills за периоды вчера, сегодня и завтра
Данные удаленного анализа пункта назначения:
- Последняя версия LS, которую видели
- Время получения LS
- Данные теста соединения
- Профильные данные ближайших floodfills за периоды вчера, сегодня и завтра
Данные анализа Meta LS:
- Последняя версия, которую видели
- Время получения
- Профильные данные ближайших floodfills за периоды вчера, сегодня и завтра
Административный интерфейс
TBD, возможно, отдельное предложение. Не требуется для первой версии.
Требования к административному интерфейсу:
- Поддержка нескольких главных пунктов назначения, то есть нескольких виртуальных кластеров (ферм)
- Обеспечить всесторонний просмотр состояния общего кластера — все статистики, публикуемые участниками, кто является текущим лидером и т. д.
- Возможность насильственного удаления участника или лидера из кластера
- Возможность принудительной публикации metaLS (если текущий узел является издателем)
- Возможность исключать хэши из metaLS (если текущий узел является издателем)
- Функциональность импорта/экспорта конфигурации для массовых развертываний
Интерфейс маршрутизатора
TBD, возможно, отдельное предложение. i2pcontrol не требуется для первой версии, и подробные изменения будут включены в отдельное предложение.
Требования для Garlic Farm к API маршрутизатора (в JVM java или i2pcontrol)
- getLocalRouterStatus()
- getLocalLeafHash(Hash masterHash)
- getLocalLeafStatus(Hash leaf)
- getRemoteMeasuredStatus(Hash masterOrLeaf) // вероятно, не в MVP
- publishMetaLS(Hash masterHash, List
contents) // или подписанный MetaLeaseSet? Кто подписывает? - stopPublishingMetaLS(Hash masterHash)
- аутентификация TBD
Обоснование
Atomix слишком велик и не позволит нам настроить маршрутизацию протокола через I2P. Также, его формат данных не задокументирован и зависит от сериализации в Java.
Примечания
Проблемы
- Нет способа для клиента узнать о неизвестном лидере и подключиться к нему. Было бы незначительное изменение для Follower отправить Конфигурацию в виде записи журнала в AppendEntriesResponse.
Миграция
Проблем с обратной совместимостью нет.