Перейти к содержанию

txid: Генерация сортируемых, уникальных идентификаторов транзакций или запросов для nginx-module-lua/nginx

Установка

Если вы еще не настроили подписку на репозиторий RPM, зарегистрируйтесь. Затем вы можете продолжить с выполнением следующих шагов.

CentOS/RHEL 7 или Amazon Linux 2

yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install lua-resty-txid

CentOS/RHEL 8+, Fedora Linux, Amazon Linux 2023

dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-txid

Чтобы использовать эту библиотеку Lua с NGINX, убедитесь, что nginx-module-lua установлен.

Этот документ описывает lua-resty-txid v1.0.0, выпущенный 1 апреля 2018 года.


CircleCI

lua-resty-txid предоставляет функцию, которая может быть использована для генерации уникальных идентификаторов транзакций/запросов для OpenResty/nginx. Идентификаторы могут быть использованы для корреляции логов или запросов к upstream и имеют следующие характеристики:

  • 20 символов
  • закодированы в base32hex
  • Временная и лексическая сортируемость
  • Нечувствительность к регистру
  • 96-битный идентификатор

lua-resty-txid является портом LuaJIT для ngx_txid для OpenResty (или nginx с ngx_lua). Идентификаторы, сгенерированные lua-resty-txid, следуют точно такому же шаблону и совместимы с ngx_txid.

Использование

Этот модуль предоставляет одну функцию Lua txid() для генерации идентификаторов:

local txid = require "resty.txid"
local id = txid() -- b2g6q94qdn6h84an7vfg

Каждый раз, когда вызывается txid(), возвращается новый уникальный идентификатор, поэтому вам нужно будет кэшировать результат, если вы хотите повторно использовать тот же идентификатор в нескольких местах для одного запроса. В зависимости от вашего использования, ngx.ctx или set_by_lua предлагают несколько простых вариантов для кэширования значения на основе каждого запроса.

txid() -- b2g83t2oshrg092mjggg
txid() -- b2g83t2oodncokuges00

ngx.ctx.txid = txid() -- b2g83t2od939mdvb2l0g
ngx.ctx.txid          -- b2g83t2od939mdvb2l0g

Наконец, txid() принимает необязательный аргумент для указания временной метки (в миллисекундах), которую следует использовать при генерации идентификатора. По умолчанию используется текущая временная метка. Поскольку полученные идентификаторы являются временно и лексически сортируемыми, это может быть использовано для генерации идентификаторов, которые будут отсортированы на основе предыдущей даты или времени.

local timestamp_ms = 655829050000 -- 1990-10-13 14:44:10
txid(timestamp_ms) -- 4om9qi54la8ffr4bd9sg

local timestamp_ms = 655929050000 -- 1990-10-14 12:30:50
txid(timestamp_ms) -- 4on1lg74nt0ud2ssllu0

Пример

Более полный пример с кэшированием, установкой заголовков запроса/ответа и интеграцией с логированием nginx:

http {
  log_format agent "$lua_txid $http_user_agent";
  log_format addr "$lua_txid $remote_addr";

  init_by_lua_block {
    # Предварительная загрузка модуля.
    require "resty.txid"
  }

  server {
    listen 8080;
    access_log logs/agents.log agent;
    access_log logs/addrs.log addr;

    # Установите переменную nginx, которая кэшируется для каждого запроса и может быть использована в
    # log_format nginx.
    set_by_lua_block $lua_txid {
      local txid = require "resty.txid"
      return txid()
    }

    location / {
      # Установите заголовок в ответе, предоставляющий идентификатор.
      more_set_headers "X-Request-Id: $lua_txid";

      # Установите заголовок в запросе, предоставляющий идентификатор (который будет отправлен на
      # проксируемый upstream).
      more_set_input_headers "X-Request-Id: $lua_txid";

      proxy_pass http://localhost:8081;
    }
  }
}

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

Бенчмарки показывают, что производительность эквивалентна C-расширению ngx_txid.

Дизайн

Дизайн идентификатора транзакции является прямым портом ngx_txid, поэтому вот вся оригинальная информация о дизайне из ngx_txid:

Фон

Дизайн этого идентификатора транзакции должен соответствовать следующим требованиям:

  • Быть примерно численно временно сортируемым с ~секундной гранулярностью.
  • Иметь представление, которое примерно лексически сортируемо с ~секундной гранулярностью.
  • Иметь вероятность столкновения менее 1e-9 при 1 миллионе транзакций в секунду.
  • Быть эффективным и легким для декодирования в типы C фиксированного размера.
  • Всегда быть доступным с риском более высокой вероятности столкновения.
  • Использовать как можно меньше байтов.
  • Работать с сетями IPv4 и IPv6.

Техника

Используйте монотонные часы с разрешением в миллисекундах в старших 42 битах и системную энтропию для младших 54 битов. Используйте достаточное количество бит энтропии, чтобы удовлетворить вероятность столкновения при желаемой глобальной скорости запросов.

+------------- 64 bits------------+--- 32 bits ----+
+------ 42 bits ------+--22 bits--|----------------+
| msec since 1970-1-1 | random    | random         |
+---------------------+-----------+----------------+

Скорость запросов в 1 миллион в секунду на всех серверах означает 1000 случайных значений за миллисекунду. Оценка вероятности столкновения с использованием парадокса дня рождения может быть выполнена с помощью этой формулы: 1 - e^(-((m^2)/(2*n))), где m — это количество идентификаторов, а n — количество возможных случайных значений.

При использовании 54 бит энтропии:

1mil req/s  = 1 - exp(-((1000^2) /(2*2^54))) = 2.775558e-11
10mil req/s = 1 - exp(-((10000^2)/(2*2^54))) = 2.775558e-09

Вероятность столкновения мала даже при 10 миллионах запросов в секунду.

Nginx отслеживает текущее время с увеличением, заданным директивой конфигурации timer_resolution. Разрешение часов для $txid составляет 1 мс, поэтому разрешение таймера больше 1 мс означает, что вероятность столкновения увеличится. Если у вас есть timer_resolution 10 мс, 1 миллион запросов в секунду потребует 10,000 случайных значений в секунду в худшем случае.

Кодирование

Для кодирования используется base32hex с алфавитом в нижнем регистре и без символов дополнения по следующим причинам:

  • Лексически сортируемый порядок эквивалентен числовому порядку.
  • Нечувствительность к регистру.
  • Нижний регистр легче для визуального сравнения.
  • Более плотное, чем шестнадцатеричное кодирование на 4 байта.

Другие техники

  • snowflake: Использует время(41) + уникальный идентификатор(10) + последовательность(12).
  • Плюс: Гарантированные уникальные последовательности.
  • Плюс: Вмещается в 63 бита.
  • Минус: Требует координации уникальных идентификаторов для каждого сервера - 16 рабочих процессов на хост означает ограничение в 64 экземпляра nginx.
  • Минус: Только 11 бит доступно для уникального идентификатора, требует мониторинга.
  • Минус: Полная сортировка возможна только в одном процессе.
  • Минус: Возможны перебои в обслуживании, когда часы теряют синхронизацию.

  • flake: Использует время + mac id + последовательность.

  • Плюс: Гарантированные уникальные последовательности.
  • Минус: Использует 128 бит.
  • Минус: Тратит 22 бита данных временной метки.
  • Минус: Только один процесс на хосте может генерировать идентификаторы - требуется синхронизация доступа к последовательности от каждого рабочего процесса.
  • Минус: Возможны перебои в обслуживании, когда часы теряют синхронизацию.
  • Минус: Семена кросс-платформенного поиска MAC-адресов.

  • UUIDv4: 122 бита энтропии.

  • Плюс: Очень низкая вероятность столкновения.
  • Минус: Не сортируемый.

  • UUID с временной меткой: 48 бит времени + 74 бита энтропии.

  • Плюс: Очень низкая вероятность столкновения.
  • Минус: Строковое представление не является временно локальным.

  • httpd mod_unique_id: Host ip(32) + pid(32) + time(32) + sequence (16) + thread id (32).

  • Плюс: Детерминированный.
  • Минус: Использует 144 бита.
  • Минус: Предполагает уникальный IPv4 для интерфейса имени хоста.
  • Минус: Не сортируемое чувствительное к регистру пользовательское представление - base64 с пользовательским алфавитом.
  • Минус: Жесткое ограничение в 65535 идентификаторов в секунду на pid - небольшая толерантность к шагам часов.

Разработка

После клонирования репозитория можно использовать Docker для запуска тестового набора:

docker-compose run --rm app make test

Процесс релиза

Чтобы опубликовать релизы в OPM и LuaRocks:

VERSION=x.x.x make release

GitHub

Вы можете найти дополнительные советы по конфигурации и документацию для этого модуля в репозитории GitHub для nginx-module-txid.