Skip to main content

Lock Distribuído | Arquitetura

  • Lock Distribuído | Arquitetura

Documentação relacionada a Lock Distribuído: 🔗 Clique aqui


Definição

Lock distribuído é um mecanismo de exclusão mútua aplicado em ambientes onde múltiplos processos, instâncias ou nós competem por um recurso compartilhado sem compartilhar memória.

Ele é utilizado para:

  • Garantir execução única de jobs
  • Prevenir race conditions entre instâncias
  • Implementar eleição de líder
  • Controlar acesso concorrente a recursos externos
  • Coordenar workers distribuídos

Em ambientes escaláveis horizontalmente (Kubernetes, ECS, múltiplas VMs), locks locais não são suficientes.


Propriedades Necessárias

Um lock distribuído robusto deve garantir:

  1. Exclusão mútua
  2. Identificação do proprietário
  3. TTL (expiração automática)
  4. Liberação segura (compare-and-delete)
  5. Tolerância a falhas parciais

Sem TTL, há risco de deadlock permanente. Sem identificação do dono, há risco de liberar lock indevidamente.


Problemas que o Lock Resolve

Execução duplicada de job


Instância A → Executa
Instância B → Também executa

Com lock:


Instância A → Adquire lock → Executa
Instância B → Falha ao adquirir → Não executa


Implementações

1. Banco de Dados Relacional

Estratégia: constraint única + transação.

Tabela:

CREATE TABLE distributed_locks (
name TEXT PRIMARY KEY,
locked_at TIMESTAMP NOT NULL,
locked_by TEXT NOT NULL
);

Aquisição:

INSERT INTO distributed_locks(name, locked_at, locked_by)
VALUES ('job-daily', NOW(), 'instance-1');

Se falhar → lock já existe.

Liberação segura:

DELETE FROM distributed_locks
WHERE name = 'job-daily'
AND locked_by = 'instance-1';

Limitações:

  • Alto lock contention
  • Sem TTL automático
  • Escala limitada

2. Redis (Padrão de Mercado)

Usando Redis com comando atômico:

SET lock:job-daily instance-1 NX PX 30000
  • NX → somente se não existir
  • PX → TTL em milissegundos

Se retornar OK → lock adquirido.


Liberação Segura (Lua Script)

if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end

Isso impede que uma instância libere lock de outra.


Exemplo Node.js

import Redis from "ioredis";
import { v4 as uuidv4 } from "uuid";

const redis = new Redis();
const lockId = uuidv4();

async function acquireLock(key, ttl = 30000) {
const result = await redis.set(key, lockId, "NX", "PX", ttl);
return result === "OK";
}

async function releaseLock(key) {
const script = `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
`;
return redis.eval(script, 1, key, lockId);
}

Renovação de Lock (Heartbeat)

Se a operação pode demorar mais que o TTL:

setInterval(async () => {
await redis.pexpire("lock:job-daily", 30000);
}, 10000);

⚠ Deve-se validar se ainda é o proprietário antes de renovar.


Algoritmo Redlock

Implementação distribuída usando múltiplos nós Redis independentes.

Funcionamento:

  1. Cliente tenta adquirir lock em N nós
  2. Lock válido se maioria aceitar
  3. TTL aplicado em todos
  4. Validade calculada considerando latência

Utilizado em:

  • Node: node-redlock
  • Java: redisson
  • .NET: RedLock.net

Observação: há debates sobre garantias sob partição de rede.


ZooKeeper

Usa:

  • Znodes efêmeros
  • Ordenação sequencial
  • Watchers

Fluxo:

  1. Criar znode efêmero sequencial
  2. Se for o menor número → lock adquirido
  3. Caso contrário → aguarda notificação

Alta consistência. Complexidade operacional elevada.


etcd

Baseado em Raft.

Lock via:

  • Lease
  • Compare-and-swap
  • Transações atômicas

Exemplo conceitual:

etcdctl lease grant 30
etcdctl put lock/job-daily instance-1 --lease=<lease-id> --prev-kv

Usado internamente pelo Kubernetes.


DynamoDB (Ambiente AWS)

Estratégia: conditional write.

ConditionExpression: "attribute_not_exists(lockKey)"

Vantagens:

  • Alta disponibilidade
  • Sem gestão de infraestrutura

Comparação Técnica

TecnologiaConsistênciaLatênciaComplexidade
Banco RelacionalForteMédiaBaixa
RedisEventualBaixaBaixa
RedlockMédia-AltaBaixaMédia
ZooKeeperForteBaixaAlta
etcdForteBaixaMédia
DynamoDBForteBaixaBaixa

Padrões Arquiteturais Relacionados

  • Idempotência de operações
  • Fila com processamento exclusivo
  • Leader election
  • Sharding por chave
  • Optimistic concurrency control

Casos de Uso

Job Scheduler Distribuído

if (acquireLock("job-daily")) {
executeJob();
}

Eleição de Líder

Primeira instância que adquire lock torna-se líder. Se expirar → nova eleição ocorre automaticamente.


Rate Limiting Distribuído

Chave compartilhada controla limite global.


Problemas Comuns

Split Brain

Partição de rede permite múltiplos locks.

Mitigação:

  • Usar quorum (Redlock)
  • Usar sistemas baseados em consenso (Raft)

Expiração Prematura

Processo perde lock antes de finalizar.

Mitigação:

  • Renovação periódica
  • TTL ajustado dinamicamente

Boas Práticas

  • Sempre usar TTL
  • Sempre identificar proprietário
  • Liberar com compare-and-delete
  • Garantir idempotência na operação protegida
  • Evitar locks longos
  • Monitorar tempo médio de retenção

Quando Não Usar

  • Quando constraint única resolve
  • Quando fila garante exclusividade
  • Quando operação é naturalmente idempotente
  • Quando pode usar optimistic concurrency