menuTicketing

Princípio de funcionamento


Aquisição e liberação do lock

Quando o cliente adquire uma chave (A), o servidor responde de uma destas três formas.

Estado da chaveAção do servidorResposta
Não registradaRegistra imediatamente (expiração = now + lease), emite tokenA + token + key
Registrada + expiradaRenova (now + lease), emite novo tokenA + token + key
Registrada + válida (em uso)Registra na fila de espera, resposta pendenteA na liberação/expiração, T se exceder wait
  • A liberação é tratada de forma justa, em FIFO. Quando o detentor libera (R), a chave é repassada diretamente para quem está na frente da fila — sem nova disputa, ela passa imediatamente com um novo token.
  • O lease é uma rede de segurança. Mesmo que o cliente morra ou esqueça de liberar, quando o tempo de lease expira o servidor pode recuperar automaticamente a chave e repassá-la ao próximo da fila. Por isso, independentemente do fluxo normal de liberação, é recomendável sempre configurar o lease com folga, mas em um valor que não prenda o recurso por tempo demais em caso de morte do cliente.
  • wait é o limite máximo de espera para aquisição. Se for 0, a espera é infinita; caso contrário, se a chave não for obtida dentro desse tempo (em segundos), o servidor desiste e responde com T (timeout) — como termina sem grant, não há vazamento de lock.

Token de fencing

O token presente na resposta A é um u64 monotonicamente crescente daquele grant. A cada aquisição, é emitido um valor maior que qualquer token anterior. Mesmo que ocorra um failover, o nó recém-eleito ativo continua a partir de um valor maior que o maior token replicado (sucessão) — assim, o token continua crescendo em todo o cluster.

Se o recurso que se deseja proteger (conta, arquivo, pedido etc.) apenas validar se "o token que tenho agora é maior que o último token visto", é possível recusar o acesso de um cliente que acordou atrasado — depois da expiração do lease — com um token já invalidado e menor. Graças a isso, a segurança se mantém mesmo que o servidor de lock não seja perfeitamente consistente a cada instante.

Comportamento do cliente

Os clientes oficiais compartilham o seguinte comportamento (para a API detalhada por linguagem, veja Bibliotecas).

  • Mantêm uma conexão persistente por endereço, gerenciada em segundo plano. Se apenas um endereço for informado, internamente são mantidas duas conexões com o mesmo nó, de modo que, se uma delas cair momentaneamente, o serviço não é interrompido.
  • As conexões são escolhidas em round-robin. Um nó com conexão caída é automaticamente excluído, e a reconexão é tentada em segundo plano a cada 3 segundos.
  • As requisições são em pipeline. É possível enviar a próxima requisição sem esperar a resposta, e a ordem das respostas pode diferir da ordem das requisições — o cliente identifica a qual requisição cada resposta corresponde pela combinação ecoada (op, key). Se houver várias requisições com a mesma combinação (op, key), elas são casadas na ordem em que foram enviadas.
  • Se a conexão cair durante uma tentativa de aquisição, o cliente troca automaticamente para a próxima conexão. Só é reportado erro se, após tentar em todas as conexões registradas, nenhuma estiver disponível.
  • A liberação é reenviada por 5 segundos, a intervalos de 200ms — para que, mesmo que a conexão esteja momentaneamente caída no instante da liberação, o lock não fique preso no servidor por mais tempo do que o necessário (no fim das contas o lease o recuperaria, mas isso permite repassá-lo mais rápido ao próximo interessado).

Cluster — sem interrupção baseado em prioridade

  • A ordem da lista peers é a prioridade de promoção (o primeiro tem a maior prioridade). Entre os nós vivos, o de maior prioridade se torna ativo, e os demais se tornam standbys que replicam esse estado em tempo real.
  • Apenas o ativo processa requisições de clientes. Um cliente conectado a um standby é redirecionado (M) para o endereço do ativo e migra para lá.
  • Em caso de falha do ativo (ou reinicialização) → um standby é promovido e assume.
  • No encerramento gracioso (Ctrl+C), o ativo primeiro repassa a promoção ao sucessor (handoff) e só então redireciona os clientes, minimizando o período sem um ativo.
  • Rebaixamento automático: se, por uma partição de rede, dois nós se tornarem ativos ao mesmo tempo, o de menor prioridade detecta o outro e volta a ser standby por conta própria (evitando um split-brain permanente).

Esse esquema de promoção não é baseado em quórum (voto majoritário). Por isso, a quantidade de servidores ser par ou ímpar não importa, e o serviço se mantém enquanto ao menos um estiver vivo.

Nº de servidoresSem interrupçãoTolerância a falhas simultâneasObservação
1O mais rápido. Ocorre uma breve interrupção ao reiniciar
21Configuração mínima para operação sem interrupção. Suficiente na maioria dos casos
32Mantém redundância mesmo com um servidor em manutenção
4+N−1Só aumenta o custo de propagação — não recomendado

O que é importante saber sobre a consistência

Como a promoção baseada em prioridade não é consenso, no instante de uma partição de rede os dois lados podem ficar ativos ao mesmo tempo por um breve período, e, devido à natureza da replicação assíncrona, alguns grants podem ser perdidos no momento do failover. Ou seja, a exclusão mútua não é 100% garantida durante janelas de failover/partição. Se for necessária uma garantia forte, implemente no recurso protegido a validação do token de fencing descrito acima — recusando tokens antigos (menores), a segurança se mantém mesmo que o servidor de lock não seja perfeitamente consistente.