Blocchi distribuiti
1) Perché (e quando) sono necessari blocchi distribuiti
Il blocco distribuito è un meccanismo che garantisce l'interscambio di una sezione critica tra più nodi di cluster. Attività tipiche:- Leadership (leader election) per l'attività di fondo/sheduler.
- Limitazione dell'esecutore unico su una risorsa condivisa (spostamento dei file, migrazione dello schema, pagamento esclusivo).
- Elaborazione sequenziale di un aggregato (wallet/order) se non è possibile ottenere un'idempotazione o un'organizzazione diversa.
- Se è possibile creare un upsert idempotente, un CAS (compare-and-set) o una coda di chiave (per-key ordering).
- Se la risorsa consente operazioni di switch (CRDT, contatori).
- Se il problema viene risolto con una transazione in un unico archivio.
2) Modello di minacce e proprietà
Guasti e difficoltà:- Rete: ritardi, separazione, perdita di pacchetti.
- Processi: pausa GC, stop-the-world, crash dopo aver preso la serratura.
- Tempo: la deriva dell'orologio e lo spostamento spezzano gli approcci TTL.
- «Zombie» dopo la rete potrebbe pensare di possedere ancora la serratura.
- Protezione: non più di un proprietario valido (safety).
- Vivibilità: la serratura viene liberata in caso di guasto del proprietario (liveness).
- Giustizia, non c'è fame.
- Indipendenza dall'orologio: la correttezza non dipende da wall-clock (o compensato da fencing tokens).
3) Modelli di base
3. 1 Lease (castello d'affitto)
La serratura viene rilasciata con TTL. Il proprietario deve estenderlo fino alla scadenza (heartbeat/keepalive).
Il lato positivo è l'autocontrollo del crash.
Rischi: se il proprietario è bloccato e continua a lavorare, ma ha perso il rinnovo, potrebbe avere un doppio possesso.
3. 2 Fencing token (token della recinzione)
Ogni volta che viene catturato, viene dato un numero in crescita monotona. I consumatori della risorsa (database, coda, file storage) controllano il token e rifiutano le operazioni con il vecchio numero.
Ciò è fondamentale per TTL/lease e per le divise di rete: protegge dal proprietario «vecchio».
3. 3 serrature quorum (sistemi CC)
Utilizzano il consenso distribuito (Raft/Paxos; ), la voce è collegata a un punto di consenso. Non c'è uno split-breen nella maggior parte dei nodi.
Inoltre, una forte garanzia di sicurezza.
Meno: sensibilità al quorum (la sua perdita di vitalità zoppica).
3. 4 serrature PA (in-memory/cache + replica)
Ad esempio, un cluster Redis. Elevata disponibilità e velocità, ma senza garanzia di sicurezza per le divise di rete. Richiedono la fincing sul lato sink.
4) Piattaforme e pattern
4. 1 etcd/ ZooKeeper/Consul (raccomandato per strong locks)
Nodi effimeri (ZK) o sessioni/leases (etcd) - La chiave esiste finché la sessione è viva.
Sessione keepalive; La perdita del quorum è scaduta. Il castello è libero.
Nodi ordinali (ZK'EPHEMERAL _ SEQUENTIAL ') per la coda di attesa viene eseguita equità.
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 (con attenzione)
Classico: 'SET key value NX PX ttl'.
Problemi:- La replica/feelover può consentire ai proprietari simultanei.
- Redlock da più istanze riduce il rischio ma non elimina; È controverso in ambienti con una rete non affidabile.
È più sicuro utilizzare Redis come uno strato di coordinatore veloce, ma sempre integrare il fencing token nella risorsa di destinazione.
Esempio (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 serrature BD
PostgreSQL advisory locks: lock all'interno del cluster Postges (processo/sessione).
È bello avere tutte le sezioni critiche in un unico database.
sql
SELECT pg_try_advisory_lock(42); -- take
SELECT pg_advisory_unlock(42); -- let go
4. 4 Serrature di file/cloud
S3/GCS + metadato oggetto del vassoio con condizioni «If-Match» (ETag) è essenzialmente CAS.
Adatto per Becap/Migrazioni.
5) Design di una serratura sicura
5. 1 Identità del proprietario
Memorizza «owner _ id» (nodo # processo # pid # start _ time) + tocco casuale da comprimere con unlock.
Un nuovo unlock non deve togliere la serratura di qualcun altro.
5. 2 TTL e estensione
TTL <T _ fail _ detect (tempo di rilevamento del guasto) e ≥ p99 per la sezione critica x riserva.
L'estensione è periodica (ad esempio, ogni «TTL/3»), con deadline.
5. 3 Fencing token sul nastro
La sezione che modifica la risorsa esterna deve trasferire «fencing _ token».
Sink (database/cache/archivio) memorizza last _ token e rifiuta meno:sql
UPDATE wallet
SET balance = balance +:delta, last_token =:token
WHERE id =:id AND:token > last_token;
5. 4 Attesa e equità
In ZK - 'EPHEMERAL _ SEQUENTIAL', e osservatori: il cliente attende il rilascio del predecessore più vicino.
In etcd - chiavi con revisione/versioning; La priorità è «med _ revision».
5. 5 Comportamento split-brain
Approccio CAP: senza quorum non si può prendere una serratura - meglio rimanere in piedi che rompere la safety.
Approccio AP: è possibile fare progressi nelle isole divise.
6) Leadership (leader election)
In etcd/ZK - «leader» è una chiave epemica esclusiva; gli altri sono firmati per le modifiche.
Il leader scrive heartbeats; La perdita è la rielezione.
Tutte le operazioni del leader accompagnare il fencing token (numero di epoca/revisione).
7) Errori e loro elaborazione
Il cliente ha preso la serratura, ma il crash prima del lavoro delle norme non si farà male. TTL/sessione sarà rilasciato.
La serratura è scaduta:- È obbligatorio watchdog: se l'estensione non è corretta, interrompere la sezione critica e ritrattare/compensare.
- Niente «finisci dopo», non puoi continuare con la sezione critica senza la serratura.
Una lunga pausa (GC/stop-the-world) il prolungamento non è avvenuto, un altro ha preso la serratura. Il flusso di lavoro deve rilevare la perdita di proprietà (canale keepalive) e interrompere.
8) Deadlock, priorità e inversione
I dedlocchi nel mondo distribuito sono rari (la serratura è solitamente una), ma se ci sono più serrature, attenersi a un unico ordine di prelievo (lock ordering).
Inversione di priorità: il proprietario a bassa priorità mantiene la risorsa mentre i proprietari ad alta priorità attendono. Soluzioni: limiti TTL, preemption (se l'azienda lo permette), risorse sharding.
Digestione: utilizzare le code di attesa (nodi d'ordine ZK) per equità.
9) Osservabilità
Metriche:- `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» (numero di tentativi negati a causa della mancanza del quorum)
- `preemptions_total`, `wait_queue_len`
- `lock_name`, `owner_id`, `token`, `ttl`, `attempt`, `wait_time_ms`, `path` (для ZK), `mod_revision` (etcd).
- Span'aquire ', sezione critica «release», con il risultato.
- Altezza di lease _ renew _ fail _ total.
- `lock_hold_seconds{p99}` > SLO.
- Serrature orfane (senza heartbeat).
- Code d'attesa gonfie.
10) Esempi pratici
10. 1 Sicuro Retis-serratura con fencing (pseudo)
1. Memorizziamo il contatore di token in uno store sicuro (ad esempio Postges/etcd).
2. Con il successo di SET NX PX, leggiamo/incrementiamo il token e tutte le modifiche apportate alla risorsa vengono eseguite con la verifica del token nel database/servizio.
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 Leadership in ZK (Java, Curator)
java
LeaderSelector selector = new LeaderSelector(client, "/leaders/cron", listener);
selector. autoRequeue();
selector. start(); // listener. enterLeadership() с try-finally и heartbeat
10. 4 Postges advisory lock con deadline (SQL + app)
sql
SELECT pg_try_advisory_lock(128765); -- attempt without blocking
-- if false --> return via backoff + jitter
11) Test playbook (Game Days)
Perdita del quorum: disattiva 1-2 nodi etcd. Il tentativo di togliere la serratura deve non passare.
GC-pausa/stop-the-world - Ritardare artificialmente il flusso del proprietario di controllare che watchdog interrompe il lavoro.
Split-brain: emulazione della divisione in rete tra il proprietario e lo store della serratura, il nuovo proprietario ottiene una fencing token più alta, il vecchio è rifiutato dal sink.
Clock skew/draft - Togliere l'orologio dal proprietario (per Redis/lease) per assicurarsi che i token/controlli forniscano correttezza.
Crash before release - Il crollo del processo viene rilasciato in TTL/sessione.
12) Anti-pattern
Serratura netta TTL senza fincing quando si accede a una risorsa esterna.
Affidarsi al tempo locale per la correttezza (senza HLC/fencing).
Distribuzione di serrature attraverso una procedura guidata Redis in un ambiente con feelover e senza conferma delle repliche.
Sezione critica infinita (TTL «secoli»).
Rimuove il lucchetto «estraneo» senza comprimere «owner _ id »/token.
L'assenza di backoff + jitter è una tempesta di tentativi.
Un unico castello globale è un sacco di conflitti; Lo sharding è meglio della chiave.
13) Assegno-foglio di implementazione
- È stato definito il tipo di risorsa e se è possibile fare clic su CAS/coda/idimpotenza.
- Il meccanismo selezionato è etcd/ZK/Consul; Redis/cache - solo con fencing.
- Implementato: 'owner _ id', TTL + estensione, watchdog, unlock corretto.
- La risorsa esterna controlla il fencing token (monotonia).
- C'è una strategia di leadership e failover.
- Le metriche, gli alert, la logica dei token e le revisioni sono configurati.
- Sono previsti backoff + jitter e timeout su acquire.
- Sono stati eseguiti game days: quorum, split-brain, pause GC, clock skew.
- Documentazione relativa al prelievo di più serrature (se necessario).
- Piano di degrado (brownout): cosa fare quando la serratura non è disponibile.
14) FAQ
Q: Il lucchetto Redis «SET NX PX» è sufficiente?
A: Solo se la risorsa verifica il fencing token. In caso contrario, possono essere condivisi due proprietari.
Q: Che scelta è predefinita?
A: Per le severe garanzie, etcd/ZooKeeper/Consul. Per operazioni facili all'interno di un unico database - advisory locks Postges. Redis è solo con la fincing.
Q: Quale TTL mettere?
A: «TTL p99 durata sezione critica x 2» e abbastanza breve per una rapida pulizia «zombie». Estensione: ogni «TTL/3».
Come evitare la fame?
A: Coda di attesa ordine (ZK sequential) o fairness; un limite di tentativi e una pianificazione equa.
C'è bisogno di sincronizzare il tempo?
A: Non per correttezza (usa fencing). Per la prevedibilità operativa sì (NTP/PTP), ma non affidatevi al wall-clock nella logica della serratura.
15) Riepilogo
Blocchi distribuiti affidabili sono costruiti su store quorum (etcd/ZK/Consul) con lease + keepalive, e sono necessariamente completati da fencing token a livello di risorsa modificabile. Qualsiasi approccio TTL/Redis senza recinzione è il rischio di split-breen. Pensate prima alla causalità e all'idampotenza, usate i blocchi dove non è possibile, misurate, testate le modalità di guasto - e le vostre «sezioni critiche» rimarranno critiche solo in termini di senso e non in termini di incidenti.