Atualmente em testes: o código do GitHub será aberto quando concluído.

Como Funciona

Esta página percorre o fluxo real de mensagens entre um cliente e o servidor (cluster) para explicar como o Ticketing funciona. A especificação exata, byte a byte, do que trafega na rede está em Protocolo (API) e Protocolo (Cluster).


Estrutura Geral

Os serviços que recebem as requisições dos usuários (por exemplo, os servidores de evento que tratam a abertura de uma venda de ingressos) entram na fila pela mesma chave junto ao cluster do Ticketing, e apenas o servidor cuja vez chegou prossegue com o trabalho, como decrementar o estoque ou atribuir um assento.

groups
Usuários
Seu serviço (ex.: servidores de evento)
dns Servidor de evento 1
dns Servidor de evento 2
dns Servidor de evento 3
Cluster Ticketing
confirmation_number ticketing-server 1
confirmation_number ticketing-server 2
confirmation_number ticketing-server 3

Adquirindo e Liberando um Lock

O fluxo básico. Uma chave livre é concedida imediatamente; uma chave ocupada espera sua vez na fila FIFO. Quando o detentor libera, o lock é repassado diretamente para o início da fila, sem nova disputa — o próximo da fila segue em frente imediatamente com um novo token, então nunca há um momento em que o lock fica vazio, e ninguém consegue furar a fila.

Cliente X
Cliente Y
Servidor
A · aquisição (order-1234)
A · concedido (token 41)
A · aquisição (order-1234)
ocupado → enfileirado, resposta retida
R · liberação
R · liberado
A · repasse direto (token 42)
requisiçãoresposta
  • lease é uma rede de segurança. Se um cliente morre ou esquece de liberar, assim que o tempo de lease expira o servidor recupera a chave e a repassa ao próximo da fila. Defina-o com folga — maior que o pior caso de tempo que sua seção crítica pode levar. A v1 aceita somente de 1 a 250 segundos; trabalhos mais longos não são suportados e são rejeitados.
  • wait é o limite superior de quanto tempo um cliente espera. Se sua vez não chegar dentro desse tempo, o servidor responde com T (timeout) em vez de conceder o lock, então nenhum lock jamais vaza. 0 significa uma única tentativa imediata sem entrar na fila. Não existe espera infinita.
  • Se o mesmo cliente tentar readquirir uma chave que já detém, ele entra na fila como qualquer outro — não é um lock reentrante.

Veja a Matriz Requisição × Estado da Chave para as regras completas de comportamento do servidor por estado de chave, e Restrições de Campo para os intervalos de valores dos campos.

Tokens de Fencing

Mesmo um servidor de lock perfeito não consegue controlar o relógio de um cliente. Sozinho, o Ticketing não consegue impedir o caso do detentor obsoleto: um cliente que travou por um tempo devido a pausas de GC ou sobrecarga enquanto detinha o lock, e depois acorda — sem saber que seu lease já expirou — e continua seu trabalho. É por isso que o token em toda resposta de aquisição é um inteiro u64 garantido a crescer a cada concessão. Em bancos financeiros ou persistentes, a comparação e a atualização do high-water de fencing por chave devem ser atômicas com a escrita de negócio real, na mesma transação de DB ou em uma única escrita condicional. Atualizar apenas a linha de fencing primeiro e executar a escrita real depois não é seguro.

Cliente X
Cliente Y
Servidor
Recurso protegido
A · aquisição
A · concedido (token 7)
A · aquisição → esperando
trava por GC / sobrecarga
lease expira → recuperado
A · repasse direto (token 8)
escrita (token 8)
registra token 8 → aceito
acorda tarde
escrita (token 7)
7 < 8 → rejeitado
requisiçãoresposta

A monotonicidade também vale no cluster — o próprio contador de tokens é replicado por consenso, então, mesmo após uma troca de líder, o novo líder sempre continua a partir de um número maior do que antes. Veja Token de Fencing (especificação) para o formato exato do campo.

Cluster — Sem Interrupções via Consenso Raft

No modo cluster, os nós compartilham um único estado de lock via consenso Raft. Somente o líder trata as requisições dos clientes, e toda mudança de estado do lock (aquisição, liberação, expiração) só é finalizada depois que a maioria dos nós a registrou. Conectar-se a um nó que não é líder resulta em uma resposta M (movido) apontando para o líder.

Cliente
Seguidor B
Líder A
Seguidor C
A · aquisição
M · movido (líder)
A · aquisição
proposta de replicação
proposta de replicação
confirmação
confirmação
maioria confirmou → commit
A · concedido (token)
requisiçãorespostaRPC de consenso (Raft)
  • Se o líder morre, a configuração atual elege um novo líder em cerca de 2,3 a 2,5 segundos. O mesmo acquire lógico só pode continuar dentro do orçamento wait restante quando nenhum byte da requisição foi enviado. Um acquire enviado sem resposta confirmada é Indeterminate e nunca é reenviado automaticamente.
  • Até a expiração do lease passa pelo consenso. Um lock só desaparece depois que o líder confirma o comando de expiração, então pequenas diferenças de relógio entre os nós nunca fazem o estado do lock divergir.
  • Se o cluster perde a maioria (por exemplo, 2 de 3 nós fora do ar), ele para de aceitar escritas em vez de arriscar emitir um lock inválido (segurança em primeiro lugar). Se o estado volátil do processo de 2 de 3 (ou 3 de 5) nós for perdido, esse domínio cluster/fencing não deve ser recuperado nem inicializado automaticamente com a mesma identidade.

Como o que importa é a maioria, apenas 3 ou 5 nós são permitidos. Um número par de nós apenas aumenta o custo sem melhorar a tolerância a falhas, então o servidor recusa iniciar com um número par.

Nº de nósSem interrupçõesFalhas simultâneas toleradasObservações
1Monotonicidade do token garantida apenas durante a vida do processo; sem suporte para proteger uma DB persistente reiniciável
31Configuração padrão sem interrupções. Suficiente para a maioria dos casos
52Redundância mantida mesmo com um nó em manutenção

Veja Protocolo (Cluster) para o enquadramento (framing) e as regras de porta do RPC entre pares, e a substituição sem interrupção por learner com novo NodeId para substituir os nós um de cada vez.

Comportamento do Cliente

Os clientes oficiais compartilham o seguinte comportamento em comum (veja Bibliotecas para as APIs específicas de cada linguagem).

  • Mantém uma conexão persistente por endereço em segundo plano. Dado um único endereço, ele mantém duas conexões com esse mesmo nó, de modo que uma queda breve em um dos sockets não interrompe o serviço.
  • Escolhe uma conexão priorizando o líder e depois round-robin. Ele lembra o líder para o qual foi redirecionado pela última vez via M e envia as requisições seguintes diretamente a ele.
  • As requisições são feitas em pipeline. A próxima requisição pode ser enviada sem esperar por uma resposta — veja as Regras de Correspondência de Resposta para como as respostas são pareadas com as requisições.
  • Uma conexão caída continua tentando reconectar com backoff exponencial (0,1 s até um limite de 3,2 s). Uma conexão morta não quebra o objeto broker — ele se recupera sozinho.
  • O mesmo acquire lógico só é tentado de novo internamente se nenhum byte da requisição foi enviado. Um B correlacionado é Busy/não adquirido definitivo e volta imediatamente ao caller, sem retry interno. M é apenas um leader hint para a próxima conexão; sem owner/key, não justifica reenviar uma requisição pending já enviada. Um acquire enviado sem resposta confirmada é informado como Indeterminate e não permite entrar na seção crítica.
  • A liberação explícita e a compensação de token conhecido usam correspondência exata de owner/token/key e uma fila dedicada limitada. As tentativas terminam em um deadline absoluto de 5 segundos desde request/enqueue, não no lease restante. Sem resposta de sucesso, uma liberação explícita não é informada nem presumida bem-sucedida; o lease do servidor é a rede final.

Conexões e Segurança

Toda conexão (de cliente ou entre pares) é estabelecida na ordem TCP → (TLS) → autenticação por desafio-resposta. O cliente faz o hash do nonce enviado pelo servidor junto com seu token para responder, então o token nunca é enviado em texto claro pela rede, e um nonce novo a cada conexão elimina ataques de replay. Veja Handshake de Autenticação para a especificação exata.

Uma Nota sobre Consistência

  • A exclusão mútua é garantida por consenso. Como toda concessão passa por um commit da maioria, a mesma chave nunca é concedida a dois detentores ao mesmo tempo, mesmo durante uma partição de rede ou troca de líder.
  • Bancos persistentes devem impor fencing. Somente uma condição high-water por chave, executada atomicamente com a escrita protegida na mesma transação/escrita condicional, impede um cliente que acorda após o lease expirar. O lock ordena o trabalho; o fencing de DB bloqueia a última escrita obsoleta.