Что такое Ticketing?
Ticketing — это выделенный сервер блокировок, созданный ровно для одной задачи — распределённой блокировки. Это единый бинарный файл сервера на Rust в паре с официальными клиентами для разных языков, каждый из которых реализует один и тот же бинарный протокол, байт в байт. Всего две операции — захват и освобождение, — очередь FIFO на каждый ключ, fencing-токены и опциональный кластер на Raft, который включается при необходимости. Всё, что нужно распределённой блокировке, и ничего лишнего.
Чем это отличается от блокировок на основе хранилища?
Большинство распределённых блокировок в продакшене опираются на хранилище ключ-значение — Lua-скрипт или Redlock поверх Redis, либо блокировку, построенную на сессиях и арендах (leases) ZooKeeper или etcd. Redis — отличное хранилище, но это хранилище ключ-значение, переиспользованное для распределённой блокировки, а не специализированная система блокировок, и это проявляется как структурные накладные расходы: стоимость разбора универсального протокола и накладные расходы универсальных структур данных прилагаются в довесок.
Ticketing с самого начала спроектирован исключительно для распределённой блокировки, поэтому каждый слой построен вокруг блокировок.
- Протокол — бинарные поля фиксированной ширины, не текст и не универсальный формат сериализации. Значения читаются прямо по смещениям байтов без разбора, а конвейеризация позволяет отправлять следующий запрос, не дожидаясь предыдущего ответа.
- Внутреннее устройство сервера — всё состояние — это очередь FIFO на каждый ключ плюс таймер аренды (lease). Путь обработки запроса не выполняет даже выделения памяти в куче.
- Эксплуатация — запустите один бинарный файл сервера (или контейнер), настройте его несколькими переменными окружения — и всё готово. Не нужно проектировать схему, поднимать отдельное хранилище или разбираться в эксплуатационных тонкостях, не связанных с блокировкой.
Производительность
- Обрабатывает более 1 миллиона операций в секунду, используя менее 10 МБ памяти
Почему Ticketing
🚦 Целостность порядка — очередь FIFO с прямой передачей
Кто первый пришёл, тот первым обслужен. Когда владелец освобождает блокировку, она передаётся напрямую первому в очереди без повторной конкуренции. Никакого голодания (starvation) от конкуренции за блокировку через опрос и повтор, никаких штормов повторов.
🛟 Мёртвый клиент не оставляет блокировку висящей — lease
Если процесс, удерживающий блокировку, умирает или его соединение обрывается, сервер автоматически забирает блокировку по истечении времени lease, заданного при захвате, и передаёт её следующему ожидающему. Забытое освобождение не останавливает всю систему.
🔑 Предотвращение работы не по порядку — fencing-токены
Есть один сбой, который одна лишь блокировка предотвратить не может: владелец, который просыпается с опозданием — не зная, что его lease уже истёк, — и всё равно просит завершить свою работу. Ticketing выдаёт монотонно возрастающий fencing-токен при каждом захвате. Пока защищаемый ресурс соблюдает одно правило — «отклонять любой токен ниже последнего увиденного», — закрывается даже этот последний пробел. Смотрите Как это работает для полного объяснения.
🗳️ Непрерывный кластер на основе Raft
Одного сервера достаточно само по себе, но когда нужен нулевой простой, сформируйте кластер из 3 (или 5) узлов. Любое изменение состояния блокировки фиксируется только после согласия большинства узлов через консенсус Raft, поэтому одна и та же блокировка никогда не выдаётся дважды одновременно, даже если лидер умирает или сеть разделяется. При отказе лидера новый избирается примерно за секунду, и клиенты переключаются автоматически. Сервис продолжает работать, даже когда узлы отключаются один за другим — например, при перезагрузке облачных инстансов или последовательном обслуживании.
🔒 Встроенная аутентификация и TLS
Каждое соединение сразу после установления проходит аутентификацию токеном по схеме challenge-response, и токен никогда не передаётся по сети в открытом виде. При необходимости можно зашифровать всё соединение через TLS.
🌐 Один и тот же протокол, байт в байт, для всех основных языков
Rust, Java/Kotlin, JavaScript/TypeScript, Python, C#, Go, Ruby, C/C++ — каждый официальный клиент реализует один и тот же бинарный протокол, с API, естественным для экосистемы каждого языка (async или блокирующим). Блокировка, захваченная из любого языка, справедливо встаёт в очередь вместе с клиентами, написанными на любом другом. Инструкции по установке и примеры — в разделе Библиотеки.
Всего две операции
Верный своей природе сервера только для блокировок, набор возможностей тоже минимален.
- Привяжите ключ к ресурсу, который хотите защитить — например,
order-1234,account-77,daily-batch. - Захватите этот ключ перед началом работы — если он занят кем-то другим, вы ждёте своей очереди в FIFO.
- Освободите его по завершении работы — следующий ожидающий получает его немедленно, без повторной конкуренции.
Единственные опции поверх этих двух операций — lease (автоматический возврат) и waitTimeout (отказ от ожидания) — это весь API.
Что читать дальше
- Как на самом деле распределяются блокировки и как кластер остаётся в строю — Как это работает
- Точная побайтовая спецификация того, что передаётся по сети — Протокол (API)
- Сгенерировать команду запуска сервера — Развёртывание сервера Ticketing