Blokady rozproszone
1) Dlaczego (i kiedy) rozproszone zamki są potrzebne
Blokowanie rozproszone jest mechanizmem gwarantującym wzajemne wykluczenie dla odcinka krytycznego między kilkoma węzłami klastrowymi. Typowe zadania:- Wybory przywódców do zadania tła/cień.
- Ograniczenie pojedynczego wykonawcy nad udostępnionym zasobem (ruch plików, migracja schematów, ekskluzywny krok płatniczy).
- Sekwencyjne przetwarzanie kruszywa (portfel/zamówienie), jeśli nie jest możliwe osiągnięcie idempotencji/zamawianie w inny sposób.
- Jeśli możesz zrobić idempotent upsert, CAS (porównaj-i-zestaw) lub zamówienie per-key.
- Jeśli zasób pozwala na operacje komutacyjne (CRDT, liczniki).
- Jeśli problem zostanie rozwiązany przez transakcję w jednym sklepie.
2) Model zagrożenia i właściwości
Niepowodzenia i powikłania:- Sieć: opóźnienia, partycje, utrata pakietów.
- Procesy: pauza GC, stop-the-world, crash after lock capture.
- Czas: Dryfowanie zegara i przesuwanie TTL zbliża się.
- Repossession: Proces „zombie” po sieci może myśleć, że nadal jest właścicielem zamku.
- Bezpieczeństwo: nie więcej niż jeden ważny właściciel (bezpieczeństwo).
- Możliwość przeżycia: blokada jest uwalniana, gdy właściciel zawiódł (los).
- Sprawiedliwość: Nie ma postu.
- Niezależność zegara: prawidłowość nie zależy od zegara ściennego (lub kompensowana przez żetony ogrodzeniowe).
3) Główne modele
3. 1 Dzierżawa (blokada wynajmu)
Zamek jest wydany z TTL. Właściciel jest zobowiązany do odnowienia go przed wygaśnięciem (bicie serca/utrzymanie przy życiu).
Plusy: Rozpadające się wchłanianie siebie.
Ryzyko: jeśli właściciel „utknął” i nadal pracuje, ale stracił przedłużenie, może powstać podwójna własność.
3. 2 Token ogrodzeniowy
Przy każdym udanym przechwyceniu wydaje się monotonnie rosnącą liczbę. Konsumenci zasobów (baza danych, kolejka, przechowywanie plików) sprawdzają token i odrzucają operacje ze starym numerem.
Jest to niezwykle ważne dla TTL/dzierżawy i przegród sieciowych - chroni przed „starym” właścicielem.
3. 3 Zamki kworum (systemy CP)
Używany jest konsensus rozproszony (Raft/Paxos; etcd/ZooKeeper/Consul), rekord jest związany z dziennikiem konsensusu → nie ma rozdzielonego mózgu z większością węzłów.
Plus: mocne gwarancje bezpieczeństwa.
Minus: wrażliwość na kworum (gdy jest utracone, przeżywalność jest kulawa).
3. 4 zamki AP (pamięć/pamięć podręczna + replikacja)
Na przykład klaster Redis. Wysoka dostępność i szybkość, ale bez silnych gwarancji bezpieczeństwa dla przegród sieciowych. Wymagają ogrodzenia po stronie siniaka.
4) Platformy i wzory
4. 1 etcd/ZooKeeper/Consul (zalecany do mocnych zamków)
Węzły efemeryczne (ZK) lub sesje/dzierżawy (etcd): klucz istnieje, gdy sesja jest żywa.
Sesja utrzymuje się przy życiu; quorum loss → sesja wygasa → blokada jest zwolniona.
Węzły sekwencji (ZK 'EFEMERAL _ SEQUENTIAL') dla kolejki oczekiwania → fair.
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 (schludny)
Classic - 'SET key value NX PX ttl'.
Problemy:- Replikacja/feilover może umożliwić jednoczesnym właścicielom.
- Redlock z wielu instancji zmniejsza ryzyko, ale nie eliminuje; jest kontrowersyjny w środowiskach o nierzetelnej sieci.
Bezpieczniej jest używać Redis jako szybkiej warstwy koordynacyjnej, ale zawsze uzupełniaj token ogrodzenia w zasobie docelowym.
Przykład (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. Zamki 3 DB
Blokady doradcze PostgreSQL: blokada w klastrze Postgres (proces/sesja).
Dobrze, że wszystkie krytyczne sekcje są już w tej samej bazie danych.
sql
SELECT pg_try_advisory_lock(42); -- take
SELECT pg_advisory_unlock(42); -- let go
4. 4 blokady pliku/chmury
S3/GCS + obiekt blokada metadanych z warunkami 'If-Match' (ETag) → zasadniczo CAS.
Nadaje się do kopii zapasowych/migracji.
5) Projekt blokady bezpieczeństwa
5. 1 Tożsamość właściciela
Store 'owner _ id' (# proces # pid # start _ time node) + losowy token do weryfikacji odblokowania.
Wielokrotne odblokowanie nie powinno usunąć czyjegoś zamka.
5. 2 TTL i przedłużenie
TTL <T_fail_detect (czas wykrywania usterek) i p99 ≥ operacji odcinka krytycznego × zapasowego.
Odnowienie - okresowo (na przykład każdy 'TTL/3'), z terminem.
5. 3 Ogrodzenie żeton na siniaku
Sekcja modyfikująca zasób zewnętrzny musi przejść przez „ogrodzenie _ token”.
Zlewozmywak (DB/cache/storage) przechowuje „last _ token” i odrzuca mniejsze:sql
UPDATE wallet
SET balance = balance +:delta, last_token =:token
WHERE id =:id AND:token > last_token;
5. 4 Czekająca kolejka i sprawiedliwość
W ZK - „EFEMERAL _ SEQUENTIAL” i obserwatorzy: klient czeka na uwolnienie najbliższego poprzednika.
W etcd - klucze z wersją/wersją; zamówienie przez 'mod _ revision'.
5. 5 Zachowanie dzielonego mózgu
Podejście CP: bez kworum nie można wziąć zamka - lepiej stać niż złamać bezpieczeństwo.
Podejście AP: postęp jest dozwolony na wyspach podzielonych → ogrodzenie jest wymagane.
6) Wybory do lidera
W etcd/ZK „lider” jest wyłącznym kluczem epemerycznym; reszta jest zapisana do zmian.
Lider pisze bicie serca; strata - reelekcja.
Towarzyszyć wszystkim operacjom lidera z tokenem ogrodzenia (numer ery/wersji).
7) Błędy i ich przetwarzanie
Klient wziął zamek, ale crash do pracy → norma, nikt nie będzie cierpieć; TTL/sesja zostanie wydana.
Zamek wygasł w trakcie pracy:- Obowiązkowy watchdog: jeśli rozszerzenie nie powiodło się, przerwać sekcję krytyczną i cofnąć/zrekompensować.
- Nie „zakończyć później”: bez zamka, sekcja krytyczna nie może być kontynuowana.
Długa przerwa (GC/stop-the-world) → rozszerzenie nie nastąpiło, drugi wziął zamek. Przepływ pracy musi wykryć utratę własności (utrzymujący się kanał) i przerwać.
8) Dedloki, priorytety i inwersja
Dedloki w rozproszonym świecie są rzadkie (zwykle jest jeden zamek), ale jeśli istnieje kilka zamków, trzymać się jednego zamówienia (zamek zamawiania).
Inwersja priorytetowa: właściciel o niskim priorytecie posiada zasoby, podczas gdy te o wysokim priorytecie czekają. Rozwiązania: limity TTL, preempcja (jeśli pozwala na to biznes), odłamywanie zasobów.
Post: Użyj kolejek oczekujących (węzłów ZK-sub-order) dla uczciwości.
9) Obserwowalność
Metryka:- 'lock _ acquire _ total {status = ok' timeout 'error}'
- „lock _ hold _ seconds {p50, p95, p99}”
- „ogrodzenie _ token _ value” (monotonia)
- „lease _ renew _ fail _ total”
- „split _ brain _ prevented _ total” (liczba odmownych prób z powodu braku kworum)
- „preemptions _ total”, „wait _ queue _ len”
- 'lock _ name', 'owner _ id',' token ',' ttl', 'attempt', 'wait _ time _ ms', 'path' (дла ZK), 'mod _ revision' (etcd).
- „acquire → critical section → release” przęsła z wynikiem.
- Wzrost „leasing _ renew _ fail _ total”.
- 'lock _ hold _ seconds {p99}'> SLO.
- Zamki „sierocych” (bez bicia serca).
- Wzdęte listy oczekujących.
10) Studia przypadku
10. 1 Bezpieczny zamek Redis z ogrodzeniem (pseudo)
1. Licznik tokenów przechowujemy w niezawodnym sklepie (na przykład Postgres/etcd).
2. Jeśli 'SET NX PX' zakończy się sukcesem, odczytujemy/zwiększamy token i wprowadzamy wszystkie zmiany do zasobu za pomocą tokenu zaznaczonego w bazie/usłudze danych.
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 Przywództwo w ZK (Java, Kurator)
java
LeaderSelector selector = new LeaderSelector(client, "/leaders/cron", listener);
selector. autoRequeue();
selector. start(); // listener. enterLeadership() с try-finally и heartbeat
10. 4 Postgres blokada doradcza z terminem (SQL + aplikacja)
sql
SELECT pg_try_advisory_lock(128765); -- attempt without blocking
-- if false --> return via backoff + jitter
11) Test playbooks (Dni gry)
Utrata kworum: wyłączyć 1-2 węzły etcd → próba zabrania blokady nie powinna przejść.
GC-pauza/stop-the-world: sztucznie opóźnić przepływ właściciela → sprawdź, czy obserwator przerywa pracę.
Split-brain: emulacja separacji sieci między właścicielem a bokiem zamku → nowy właściciel dostaje wyższy symbol ogrodzenia, stary jest odrzucany przez niebieski.
Zegar skew/drift: zabrać zegar od właściciela (dla Redis/dzierżawy) → upewnij się, że poprawność jest zapewniona przez żetony/kontrole.
Crash przed zwolnieniem: proces awarii → blokada jest uwalniana poprzez TTL/sesję.
12) Anty-wzory
Czyścić blokadę TTL bez ogrodzenia podczas dostępu do zewnętrznego zasobu.
Polegać na czasie lokalnym dla poprawności (bez HLC/ogrodzenia).
Dystrybucja zamków przez jednego mistrza Redis w środowisku z feilover i bez potwierdzenia replik.
Nieskończona część krytyczna (TTL „dla wieków”).
Usuwanie czyjegoś zamka bez sprawdzania 'owner _ id'/token.
Brak backoff + jitter → burza prób.
Pojedynczy globalny zamek „za wszystko” - torba konfliktów; klucz jest lepszy.
13) Lista kontrolna wdrażania
- Typ zasobów zdefiniowany i można dozować CAS/kolejkę/idempotencję.
- Wybrany mechanizm: etcd/ZK/Consul for CP; Redis/cache - tylko z ogrodzeniem.
- Wdrożone: 'owner _ id', rozszerzenie TTL +, watchdog, poprawne odblokowanie.
- Zewnętrzne zasoby kontrolują token ogrodzenia (monotonia).
- Istnieje strategia przywództwa i porażka.
- Skonfigurowane mierniki, wpisy, żetony do rejestrowania i wersje.
- Zapewniono backoff + jitter i nabycie terminów.
- Dni gry: kworum, podział mózgu, pauzy GC, skew zegara.
- Dokumentacja procedury zabrania kilku zamków (jeżeli jest to wymagane).
- plan brownout - co zrobić, gdy blokada jest niedostępna.
14) FAQ
P: Czy blokada 'SET NX PX' jest wystarczająca?
Odp.: Tylko jeśli zasób sprawdza token ogrodzenia. W przeciwnym razie, z podziału sieci, dwóch właścicieli są możliwe.
P: Co wybrać „domyślnie”?
Odp.: Dla ścisłych gwarancji - etcd/ZooKeeper/Consul (CP). Dla łatwych zadań wewnątrz jednej bazy danych - blokady doradcze Postgres. Redis - tylko z ogrodzeniem.
P: Który TTL umieścić?
Odp.: 'TTL ≥ p99 krytyczny czas trwania odcinka × 2' i wystarczająco krótki, aby szybko oczyścić 'zombie. "Odnowienie - każdy 'TTL/3'.
P: Jak uniknąć postu?
Odp.: Kolejka oczekująca w kolejności (sekwencyjna ZK) lub algorytm uczciwości; limit prób i sprawiedliwego planowania.
P: Czy potrzebuję synchronizacji czasu?
Odp.: Dla poprawności - nie (użyj ogrodzenia). Dla przewidywalności operacyjnej, tak (NTP/PTP), ale nie polegać na zegarze ściennym dla logiki blokady.
15) Kwoty całkowite
Niezawodne blokady rozproszone są zbudowane na poziomach kworum (etcd/ZK/Consul) z dzierżawą + utrzymywane, i są koniecznie uzupełniane przez żeton ogrodzenia na poziomie zasobów ulegających zmianie. Każde podejście TTL/Redis bez ogrodzenia stanowi ryzyko rozszczepienia mózgu. Pomyśl najpierw o przyczynowości i idempotencji, użyj zamków, gdzie jest to niemożliwe bez nich, pomiar, tryby awarii testów - a twoje „krytyczne sekcje” pozostaną krytyczne tylko w znaczeniu, a nie w liczbie incydentów.