menuTicketing

Принцип работы


Получение и освобождение блокировки

Когда клиент получает ключ (A), сервер отвечает одним из трёх способов.

Состояние ключаДействие сервераОтвет
Не зарегистрированНемедленная регистрация (истечение = now + lease), выдача токенаA + token + key
Зарегистрирован + срок истёкОбновление (now + lease), выдача нового токенаA + token + key
Зарегистрирован + действителен (занят)Постановка в очередь, ответ откладываетсяПри освобождении/истечении — A, при превышении waitT
  • Освобождение обрабатывается справедливо, по правилу FIFO. Когда владелец освобождает (R) ключ, он передаётся напрямую первому в очереди — без повторной конкуренции, сразу с новым токеном.
  • lease — это страховочная сеть. Если клиент упал или забыл освободить ключ, по истечении времени lease сервер автоматически заберёт ключ обратно и сможет передать его следующему ожидающему. Поэтому, независимо от штатного потока освобождения, lease стоит устанавливать с запасом, но так, чтобы в случае гибели клиента ключ не был занят слишком долго.
  • wait — верхняя граница ожидания получения. При значении 0 ожидание бесконечно, в остальных случаях, если за это время (в секундах) получить ключ не удалось, сервер сдаётся и отвечает T (таймаут) — без выдачи гранта, поэтому утечки блокировки не происходит.

Fencing-токен

token, содержащийся в ответе A — это монотонно возрастающий u64 этого гранта. При каждом получении блокировки выдаётся значение больше любого предыдущего токена. Даже при failover новый активный узел продолжает отсчёт от значения больше максимального реплицированного токена (наследование) — поэтому токен непрерывно растёт в масштабе всего кластера.

Если защищаемый ресурс (счёт, файл, заказ и т.п.) проверяет только одно условие — "больше ли текущий токен последнего увиденного" — можно отклонить доступ клиента, который проснулся с опозданием после истечения lease и пытается использовать уже недействительный, более старый токен. Благодаря этому безопасность сохраняется, даже если сервер блокировок не абсолютно консистентен в каждый момент времени.

Поведение клиента

Официальные клиенты в целом ведут себя одинаково (подробности API для конкретного языка — в разделе Библиотеки).

  • Поддерживают одно постоянное соединение на каждый адрес, управляемое в фоне. Если указан только один адрес, клиент внутренне держит два соединения к одному и тому же узлу, чтобы временный обрыв одного из них не прерывал обслуживание.
  • Выбирают соединение по round-robin. Разорванные соединения автоматически исключаются, а переподключение в фоне пытается происходить каждые 3 секунды.
  • Запросы конвейеризируются (pipelining). Следующий запрос можно отправить, не дожидаясь ответа на предыдущий, и порядок ответов может отличаться от порядка запросов — клиент определяет, к какому запросу относится ответ, по эхо-паре (op, ключ). Если запросов с одинаковой парой (op, ключ) несколько, они сопоставляются в порядке отправки.
  • При обрыве соединения во время попытки получения блокировки клиент автоматически переключается на другое соединение. Если после числа попыток, равного количеству зарегистрированных соединений, рабочего соединения не нашлось, только тогда сообщается об ошибке.
  • Освобождение повторяется в течение 5 секунд с интервалом 200 мс — чтобы, даже если в момент освобождения соединение оказалось разорвано, блокировка не оставалась занятой на сервере дольше необходимого (в конце концов её заберёт lease, но так следующий ожидающий получит её быстрее).

Кластер — бесперебойность на основе приоритета

  • Порядок в списке peers определяет приоритет повышения (первый в списке — наивысший приоритет). Среди живых узлов узел с наивысшим приоритетом становится активным, остальные — резервными (standby), получающими его состояние репликацией в реальном времени.
  • Клиентские запросы обрабатывает только активный узел. Клиент, подключившийся к резервному узлу, получает перенаправление (M) на адрес активного и переходит туда.
  • При сбое активного узла или его перезапуске резервный узел повышается и подхватывает роль.
  • При штатном завершении (Ctrl+C) активный узел сначала передаёт повышение преемнику (handoff), а затем перенаправляет клиентов — так простой активного узла сводится к минимуму.
  • Автоматическое понижение: если из-за сетевого разделения (partition) активными одновременно оказались два узла, узел с более низким приоритетом обнаруживает второй активный узел и сам добровольно понижается до резервного (защита от постоянного split-brain).

Этот механизм повышения не основан на кворуме (голосовании большинством). Поэтому чётность или нечётность числа узлов значения не имеет, и сервис остаётся работоспособным, пока жив хотя бы один узел.

Число серверовБесперебойностьДопустимое число одновременных сбоевПримечание
1Самый быстрый вариант. При перезапуске возникает кратковременный простой
21Минимальная конфигурация для бесперебойности. Обычно этого достаточно
32Резервирование сохраняется даже при обслуживании одного узла
4+N−1Растут только затраты на распространение — не рекомендуется

О согласованности

Повышение на основе приоритета не является консенсусом, поэтому в момент сетевого разделения обе стороны могут ненадолго одновременно оказаться активными, а из-за особенностей асинхронной репликации в момент failover часть грантов может быть потеряна. То есть в периоды failover/partition взаимное исключение не гарантируется на 100%. Если нужны строгие гарантии, реализуйте проверку описанного выше fencing-токена на стороне защищаемого ресурса — отклонение устаревших (меньших) токенов обеспечивает безопасность, даже если сервер блокировок не абсолютно консистентен.