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:
- Exclusão mútua
- Identificação do proprietário
- TTL (expiração automática)
- Liberação segura (compare-and-delete)
- 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:
- Cliente tenta adquirir lock em N nós
- Lock válido se maioria aceitar
- TTL aplicado em todos
- 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:
- Criar znode efêmero sequencial
- Se for o menor número → lock adquirido
- 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
| Tecnologia | Consistência | Latência | Complexidade |
|---|---|---|---|
| Banco Relacional | Forte | Média | Baixa |
| Redis | Eventual | Baixa | Baixa |
| Redlock | Média-Alta | Baixa | Média |
| ZooKeeper | Forte | Baixa | Alta |
| etcd | Forte | Baixa | Média |
| DynamoDB | Forte | Baixa | Baixa |
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