Bloqueios distribuídos
1) Para quê (e quando) bloqueios distribuídos
O bloqueio distribuído é um mecanismo que garante a interconexão de uma seção crítica entre vários nós do cluster. Tarefas típicas:- Liderança para a tarefa de fundo/shedooler.
- Limite o único executor sobre o recurso compartilhado (mover arquivos, migrar o esquema, fazer pagamentos exclusivos).
- Processamento em sequência de uma unidade (wallet/order) se não for possível obter idempotação/ordenamento de outra forma.
- Se você puder fazer upsert idempotental, CAS (compare-and-set) ou fila de chave (per-key ordering).
- Se o recurso permitir operações de comutação (CRDT, contadores).
- Se o problema for resolvido por uma transação em um único armazém.
2) Modelo de ameaças e propriedades
Rejeitos e dificuldades:- Rede: atrasos, separação (partition), perda de pacotes.
- Processos: pausa GC, stop-the-world, crash após a tomada do castelo.
- Hora: A deriva do relógio e o deslocamento quebram as abordagens TTL.
- «Zombies», depois da rede, pode pensar que ainda possui o castelo.
- Segurança: no máximo um dono válido (safety).
- Vitalidade: O castelo é liberado quando o proprietário falhar (liveness).
- Justiça, falta fome.
- Independente do relógio: a correção não depende de wall-clock (ou compensado por fencing tocens).
3) Modelos básicos
3. 1 Lease (castelo de aluguel)
O castelo é emitido com a TTL. O proprietário é obrigado a prolongá-lo até o vencimento (heartbeat/keepalive).
Os benefícios são a auto-criatura do crash.
Riscos: Se o proprietário continua a trabalhar, mas perdeu a extensão, pode haver dupla posse.
3. 2 Fencing tocen (tornozelo de vedação)
A cada captura bem sucedida, o número cresce monotonicamente. Os consumidores do recurso (BB, fila, armazenamento de arquivos) verificam o token e rejeitam as transações com o número antigo.
Isso é essencial no TTL/lease e nas divisões de rede - protege contra o «antigo» dono.
3. 3 cadeados Quorum (Sistemas de CP)
Utilizam o consenso distribuído (Raft/Paxos; etcd/ZooKeeper/Consul), a gravação está associada a um tópico de consenso → não há split brain na maioria dos nós.
Além disso, garantias fortes de segurança.
Menos: sensibilidade ao quórum (quando perde a vitalidade).
3. 4 cadeados AP (in-memory/dinheiro + replicação)
Por exemplo, um cluster Redis. Alta disponibilidade e velocidade, mas sem segurança rígida nas divisões de rede. Exigem fencing do lado sink.
4) Plataformas e pattern
4. 1 etcd/ ZooKeeper/Consul (recomendado para strong locks)
Nós efêmeros (ZK) ou sessões/leases (etcd): a chave existe enquanto a sessão estiver viva.
Keepalive de sessão; perda de quórum → a sessão expira → o castelo é liberado.
Nós de ordem (ZK 'EPHEMERAL _ SEQUENTIAL') para a fila de espera → justiça.
go cli, _:= clientv3. New(...)
lease, _:= cli. Grant(ctx, 10) // 10s lease sess, _:= concurrency. NewSession(cli, concurrency. WithLease(lease. ID))
m:= concurrency. NewMutex(sess, "/locks/orders/42")
if err:= m. Lock(ctx); err!= nil { / handle / }
defer m. Unlock(ctx)
4. 2 Redis (com cuidado)
Clássico - 'SET key value NX PX ttl'.
Problemas:- A replicação/feelover pode permitir proprietários simultâneos.
- Redlock de várias instâncias reduz o risco, mas não elimina; disputado em ambientes com rede precária.
É mais seguro usar o Redis como uma camada rápida de coordenação, mas sempre complementar o fencing tocen no recurso de destino.
Exemplo (Lua-unlock):lua
-- release only if value matches if redis. call("GET", KEYS[1]) == ARGV[1] then return redis. call("DEL", KEYS[1])
else return 0 end
4. 3 cadeados BD
PostgreSQL advisory locks: lock dentro do cluster Postgres (processo/sessão).
Ainda bem que todas as secções críticas estão na mesma base.
sql
SELECT pg_try_advisory_lock(42); -- take
SELECT pg_advisory_unlock(42); -- let go
4. 4 Cadeados de arquivo/nuvem
S3/GCS + cabelo de metadado de objeto com condições de 'If-Match' (ETAG) → essencialmente CAS.
Adequado para bacapes/migração.
5) Design de um castelo seguro
5. 1 Identidade do proprietário
Guarde 'owner _ id' (nó # processo # pid # start _ time) + token aleatório para combinação em unlock.
O unlock não deve ser removido.
5. 2 TTL e extensão
O TTL <T _ fail _ detect (hora da detecção da falha) e ≥ p99 para a seção crítica x reserva.
A extensão é intermitente (por exemplo, a cada 'TTL/3'), com deadline.
5. 3 Fencing tocen em sinca
A secção que altera o recurso externo deve transferir 'fencing _ tocen'.
Sink (BD/dinheiro/armazenamento) armazena 'last _ tocen' e rejeita os menores:sql
UPDATE wallet
SET balance = balance +:delta, last_token =:token
WHERE id =:id AND:token > last_token;
5. 4 Fila de espera e justiça
ZK - 'EPHEMERAL _ SEQUENTIAL' e observadores: O cliente aguarda a libertação do antecessor mais próximo.
Em etcd - chaves com revisão/versionização; A prioridade é 'moon _ revision'.
5. 5 Comportamento de split-brain
Abordagem COP: sem quórum, não se pode pegar o cadeado - é melhor ficar parado do que quebrar o safety.
Abordagem AP: é permitido progresso em ilhas divididas → precisa de fencing.
6) Liderança (líder elation)
Em etcd/ZK - «líder» é uma chave epérmica exclusiva; o resto está assinado para alterações.
Líder escreve heartbeats; A perda é a reeleição.
Todas as operações do líder acompanhe fencing tocen (número de época/revisão).
7) Erros e processamento
O cliente pegou o cadeado, mas o crash antes de trabalhar, ninguém se magoa; TTL/sessão será liberado.
O castelo expirou no meio do trabalho:- É obrigatório watchdog: se a extensão não for possível, interromper a secção crítica e reverter/compensar.
- Nada de «acabar depois», a secção crítica não pode continuar sem o castelo.
Longa pausa (GC/stop-the-world) → prolongamento não aconteceu, outro levou o castelo. O fluxo de trabalho deve detectar a perda de posse (canal keepalive) e interromper.
8) Dedlocks, prioridades e inversão
Os dedlocks no mundo distribuído são raros (o castelo é normalmente um), mas se os castelos forem múltiplos, mantenha uma ordem de tomada unificada (lock ordering).
Inversão de prioridades: o proprietário de baixa prioridade mantém o recurso enquanto os de alta prioridade esperam. Soluções: limites TTL, preempção (se o negócio permitir), recursos sharding.
Fome: use as filas de espera (nódulos de ordem ZK) para fazer justiça.
9) Observabilidade
Métricas:- `lock_acquire_total{status=ok|timeout|error}`
- `lock_hold_seconds{p50,p95,p99}`
- 'fencing _ token _ value' (monotonia)
- `lease_renew_fail_total`
- 'split _ brain _ prevented _ total' (quantas tentativas foram recusadas por falta de quórum)
- `preemptions_total`, `wait_queue_len`
- `lock_name`, `owner_id`, `token`, `ttl`, `attempt`, `wait_time_ms`, `path` (для ZK), `mod_revision` (etcd).
- Spans "aquire critical seção" release "com o resultado.
- Crescimento de 'lease _ renew _ fail _ total'.
- `lock_hold_seconds{p99}` > SLO.
- Cadeados «órfãos» (sem heartbeat).
- Filas de espera inchadas.
10) Exemplos práticos
10. 1 Castelo Redis seguro com fencing (pseudo)
1. Armazenando o contador de tokens em um estoque seguro (por exemplo, Postgres/etcd).
2. Com o sucesso do 'SET NX PX', lemos/incorporamos o token e fazemos todas as alterações no recurso com a verificação do token no serviço/base de dados.
python acquire token = db. next_token ("locks/orders/42") # monotone ok = redis. set("locks:orders:42", owner, nx=True, px=ttl_ms)
if not ok:
raise Busy()
critical op guarded by token db. exec("UPDATE orders SET... WHERE id=:id AND:token > last_token",...)
release (compare owner)
10. 2 etcd Mutex + watchdog (Go)
go ctx, cancel:= context. WithCancel(context. Background())
sess, _:= concurrency. NewSession(cli, concurrency. WithTTL(10))
m:= concurrency. NewMutex(sess, "/locks/job/cleanup")
if err:= m. Lock(ctx); err!= nil { /... / }
// Watchdog go func() {
<-sess. Done ()//loss of session/quorum cancel ()//stop working
}()
doCritical (ctx )//must respond to ctx. Done()
_ = m. Unlock(context. Background())
_ = sess. Close()
10. 3 Liderança em ZK (Java, Curator)
java
LeaderSelector selector = new LeaderSelector(client, "/leaders/cron", listener);
selector. autoRequeue();
selector. start(); // listener. enterLeadership() с try-finally и heartbeat
10. 4 Postgres advisory lock com dedline (SQL + app)
sql
SELECT pg_try_advisory_lock(128765); -- attempt without blocking
-- if false --> return via backoff + jitter
11) Playbooks de teste (Game Days)
Perda de quórum: desativar 1-2 nó etCD → tentar pegar o cadeado deve não passar.
GC-pausa/stop-the-world: reter artificialmente o fluxo do proprietário → verificar que watchdog interrompe o trabalho.
Split-brain: emulação da divisão de rede entre o proprietário e o estoro do castelo → o novo proprietário recebe uma fencing tocen mais alta, o antigo é rejeitado pelo sinuoso.
Clock skew/draft: Tirar o relógio do proprietário (para Redis/lease) → certificar-se de que a correção é fornecida por tokens/verificações.
Crash before release: queda do processo → fechadura é liberada por TTL/sessão.
12) Anti-pattern
Castelo TTL limpo sem fencing no acesso a recursos externos.
Dependa do tempo local para ser correto (sem HLC/fencing).
Distribuir cadeados através de um assistente Redis em um ambiente com feedback e sem confirmação de réplicas.
Secção crítica infinita (TTL «séculos»).
Retire o cadeado «alheio» sem «owner _ id »/tocen.
Falta de backoff + jitter → «tempestade» de tentativas.
Um único castelo global «para tudo» - um saco de conflitos; O charding é melhor com a chave.
13) Folha de cheque de implementação
- O tipo de recurso está definido e se o CAS/fila/idimpotência pode ser pago.
- Mecanismo selecionado: etcd/ZK/Consul para a COP; Redis/dinheiro - apenas com fencing.
- Implementados: 'owner _ id', TTL + extensão, watchdog, unlock correto.
- O recurso externo verifica fencing tocen (monotonia).
- Há uma estratégia de liderança e failover.
- As métricas, as alertas, a logação de tokens e as revisões são configuradas.
- Estão previstos backoff + jitter e temporizações no aquire.
- Realizado game days: quórum, split-brain, pausas GC, clock skew.
- Documentação da ordem da tomada de vários cadeados (se necessário).
- Plano de degradação (brownout): o que fazer quando a fechadura não estiver disponível.
14) FAQ
Q: O cadeado Redis 'SET NX PX' é suficiente?
A: Somente se o recurso verificar fencing tocen. Caso contrário, dois proprietários podem ser divididos em rede.
Q: O que escolher por padrão?
A: Para garantias rigorosas - (COP). Para tarefas fáceis dentro de uma base de dados - advisory locks Postgres. O Redis é apenas com fencing.
Q: Que tipo de TTL colocar?
A: 'TTL p99 duração da secção crítica x 2' e curta o suficiente para limpar rapidamente os 'zombies'. Extensão a cada 'TTL/3'.
Como evitar a fome?
A: Fila de espera por ordem (ZK sequential) ou algoritmo fairness; limite de tentativas e planejamento justo.
O tempo precisa de sincronização?
A: Para ser correto, não (use fencing). Para previsibilidade operacional - sim (NTP/PTP), mas não se baseie em wall-clock na lógica do castelo.
15) Resultado
Bloqueios distribuídos confiáveis são construídos em estoques quórum (etcd/ZK/Consul) com lease + keepalive, e necessariamente completados com fencing tocen ao nível do recurso a ser alterado. Qualquer abordagem TTL/Redis sem vedação - risco de split brain. Pense primeiro em causosidade e idempotidade, use bloqueios onde não é possível, mede, teste os modos de falha - e suas «seções críticas» permanecerão críticas apenas no sentido e não no número de incidentes.