txid: Gere IDs de transação ou solicitação únicos e ordenáveis para nginx-module-lua/nginx
Instalação
Se você ainda não configurou a assinatura do repositório RPM, inscreva-se. Depois, você pode prosseguir com os seguintes passos.
CentOS/RHEL 7 ou 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
Para usar esta biblioteca Lua com NGINX, certifique-se de que o nginx-module-lua esteja instalado.
Este documento descreve lua-resty-txid v1.0.0 lançado em 01 de abril de 2018.
lua-resty-txid fornece uma função que pode ser usada para gerar IDs de transação/solicitação únicos para OpenResty/nginx. Os IDs podem ser usados para correlacionar logs ou solicitações upstream e têm as seguintes características:
- 20 caracteres
- codificado em base32hex
- Ordenável temporal e lexicalmente
- Insensível a maiúsculas e minúsculas
- Identificador de 96 bits
lua-resty-txid é uma porta LuaJIT de ngx_txid para OpenResty (ou nginx com ngx_lua). Os IDs gerados pelo lua-resty-txid seguem exatamente o mesmo padrão e são compatíveis com ngx_txid.
Uso
Uma única função txid() Lua é exposta por este módulo para gerar IDs:
local txid = require "resty.txid"
local id = txid() -- b2g6q94qdn6h84an7vfg
Cada vez que txid() é chamado, um novo ID único será retornado, então você precisará armazenar em cache o resultado se desejar reutilizar o mesmo ID em vários lugares para uma única solicitação. Dependendo do seu uso, ngx.ctx ou set_by_lua oferecem algumas opções simples para armazenar o valor em uma base por solicitação.
txid() -- b2g83t2oshrg092mjggg
txid() -- b2g83t2oodncokuges00
ngx.ctx.txid = txid() -- b2g83t2od939mdvb2l0g
ngx.ctx.txid -- b2g83t2od939mdvb2l0g
Finalmente, txid() aceita um argumento opcional para qual timestamp (em milissegundos) usar ao gerar o ID. Por padrão, o timestamp atual é usado. Como os IDs resultantes são ordenáveis temporal e lexicalmente, isso pode ser usado para gerar IDs que serão ordenados com base em uma data ou hora anterior.
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
Exemplo
Um exemplo mais completo, com cache, configuração de cabeçalhos de solicitação/resposta e integração com o log do nginx:
http {
log_format agent "$lua_txid $http_user_agent";
log_format addr "$lua_txid $remote_addr";
init_by_lua_block {
# Pré-carregar o módulo.
require "resty.txid"
}
server {
listen 8080;
access_log logs/agents.log agent;
access_log logs/addrs.log addr;
# Defina uma variável nginx que é armazenada em cache por solicitação e pode ser usada no
# log_format do nginx.
set_by_lua_block $lua_txid {
local txid = require "resty.txid"
return txid()
}
location / {
# Defina um cabeçalho na resposta fornecendo o ID.
more_set_headers "X-Request-Id: $lua_txid";
# Defina um cabeçalho na solicitação fornecendo o ID (que será enviado para o
# upstream proxy).
more_set_input_headers "X-Request-Id: $lua_txid";
proxy_pass http://localhost:8081;
}
}
}
Desempenho
Os benchmarks indicam que o desempenho é equivalente à extensão C ngx_txid.
Design
O design do ID de transação é uma porta direta de ngx_txid, então aqui estão todas as informações originais sobre o design do ngx_txid:
Contexto
O design deste ID de transação deve atender aos seguintes requisitos:
- Ser aproximadamente ordenável temporalmente com granularidade de ~segundo.
- Ter uma representação que seja aproximadamente ordenável lexicalmente com granularidade de ~segundo.
- Ter uma probabilidade de colisão inferior a 1e-9 para 1 milhão de transações por segundo.
- Ser eficiente e fácil de decodificar em tipos C de tamanho fixo.
- Estar sempre disponível, mesmo que isso aumente a probabilidade de colisão.
- Usar o menor número possível de bytes.
- Funcionar com redes IPv4 e IPv6.
Técnica
Use um relógio de resolução de milissegundos monotônico nos 42 bits altos e entropia do sistema nos 54 bits baixos. Use bits de entropia suficientes para satisfazer uma probabilidade de colisão a uma taxa de solicitação global desejada.
+------------- 64 bits------------+--- 32 bits ----+
+------ 42 bits ------+--22 bits--|----------------+
| msec desde 1970-1-1 | random | random |
+---------------------+-----------+----------------+
Uma taxa de solicitação de 1 milhão por segundo em todos os servidores significa 1000 valores aleatórios por milissegundo. Estimar a probabilidade de colisão usando o paradoxo do aniversário pode ser feito com esta fórmula: 1 - e^(-((m^2)/(2*n))) onde m é o número de ids e n é o número de valores aleatórios possíveis.
Ao usar 54 bits de entropia:
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
As chances de colisão são pequenas mesmo a 10 milhões de solicitações por segundo.
O Nginx mantém o controle do relógio atual em incrementos da diretiva de configuração timer_resolution. A resolução do relógio para $txid é de 1ms, então uma resolução de timer maior que 1ms significa que a probabilidade de colisão aumentará. Se você tiver uma timer_resolution de 10ms, 1 milhão de solicitações por segundo exigiria 10.000 valores aleatórios por segundo no pior caso.
Codificação
base32hex é usado com um alfabeto em letras minúsculas e sem caracteres de preenchimento, escolhido pelas seguintes razões:
- Ordem de classificação lexical equivalente à ordem de classificação numérica.
- Igualdade insensível a maiúsculas e minúsculas.
- Letras minúsculas são mais fáceis para comparações visuais.
- Mais denso que a codificação hexadecimal por 4 bytes.
Outras técnicas
- snowflake: Usa time(41) + unique id(10) + sequence(12).
- Prós: Sequências únicas garantidas.
- Prós: Cabe em 63 bits.
- Contras: Requer coordenação de ID único para cada servidor - 16 processos de trabalho por host significa um limite de 64 instâncias de nginx.
- Contras: Apenas 11 bits disponíveis para ID único, precisa de monitoramento.
- Contras: Ordenação total só é possível no mesmo processo.
-
Contras: Interrupção de serviço possível quando os relógios perdem sincronização.
-
flake: Usa time + mac id + sequence.
- Prós: Sequências únicas garantidas.
- Contras: Usa 128 bits.
- Contras: Desperdiça 22 bits de dados de timestamp.
- Contras: Apenas um único processo por host pode gerar ids - precisa sincronizar o acesso à sequência de cada processo de trabalho.
- Contras: Interrupção de serviço possível quando os relógios perdem sincronização.
-
Contras: Seeds cross platform MAC Address lookup.
-
UUIDv4: 122 bits de entropia.
- Prós: Probabilidade de colisão muito baixa.
-
Contras: Não ordenável.
-
UUID com timestamp: 48 bits de tempo + 74 bits de entropia.
- Prós: Probabilidade de colisão muito baixa.
-
Contras: A representação em string não é local temporalmente.
-
httpd mod_unique_id: Host ip(32) + pid(32) + time(32) + sequence (16) + thread id (32).
- Prós: Determinístico.
- Contras: Usa 144 bits.
- Contras: Assume IPv4 único para a interface do hostname.
- Contras: Representação personalizada sensível a maiúsculas e minúsculas não ordenável - base64 com um alfabeto personalizado.
- Contras: Limite rígido de 65535 ids por segundo por pid - pequena tolerância para passos de relógio.
Desenvolvimento
Após clonar o repositório, o Docker pode ser usado para executar o conjunto de testes:
docker-compose run --rm app make test
Processo de Lançamento
Para publicar lançamentos no OPM e LuaRocks:
VERSION=x.x.x make release
GitHub
Você pode encontrar dicas adicionais de configuração e documentação para este módulo no repositório GitHub para nginx-module-txid.