Î: Un server de servicii, o bază de date, operațiune: interoghează soldul curent al utilizatorului, deduci 3% din soldul curent ca taxă de gestionare
sincronizat lacăt Blocare DB
Î: Două servere de servicii, o bază de date, operațiune: interoghează soldul curent al utilizatorului, deduci 3% din soldul curent ca taxă de gestionare Blocaje distribuite
Ce fel de lacăt distribuit avem nevoie? Poate asigura că, într-un cluster de aplicații distribuite, aceeași metodă poate fi executată doar de un fir de execuție pe o singură mașină în același timp.
Dacă această încuietoare este una reentrantă (evitați blocajele)
Această lacăt este cel mai bine ca blocaj (gândește-te dacă îl dorești în funcție de nevoile afacerii tale).
Această încuietoare este cea mai bună pentru a fi corectă (gândește-te dacă vrei sau nu această încuietoare în funcție de nevoile de business)
Există funcții de achiziție și eliberare a blocajelor foarte disponibile
Performanța încuietorilor de achiziție și eliberare este mai bună
1. Încuietori distribuite bazate pe baze de date
Blocaje distribuite bazate pe implementări bazate pe tabele
Când vrem să blocăm o metodă, executăm următorul SQL: inserați în metoda Lock(method_name,desc) valori ('method_name','desc')
Deoarece am impus o constrângere de unicitate pe method_name, dacă mai multe cereri sunt trimise simultan către baza de date, baza de date va asigura că doar o singură operație poate reuși, atunci putem presupune că firul care a obținut cu succes metoda se blochează și poate executa conținutul corpului metodei.
Când metoda este executată, dacă vrei să eliberezi blocarea, trebuie să execuți următorul Sql: șterge din methodLock unde method_name ='method_name'
Această implementare simplă de mai sus are următoarele probleme:
- Această blocare depinde de disponibilitatea bazei de date, care este un singur punct și va face ca sistemul de afaceri să fie indisponibil odată ce baza de date este blocată.
- Această blocare nu are timp de expirare, iar odată ce operațiunea de deblocare eșuează, înregistrarea blocării va rămâne în baza de date, iar celelalte fire nu vor mai putea obține blocarea.
- Această blocare poate fi neblocantă doar pentru că operația de inserare a datelor va raporta direct o eroare după eșecul inserției. Firele care nu dobândesc blocaje nu vor intra în coadă și vor trebui să declanșeze din nou operația de achiziție a blocajului pentru a obține din nou blocarea.
- Încuietoarea nu este reentrantă, iar același fir nu poate obține din nou lacătul până când acesta nu este eliberat. Pentru că datele din date există deja.
- Această lacăt este incorect, iar toate firele care așteaptă lacătul concurează pentru el din noroc.
Desigur, putem rezolva problemele de mai sus și în alte moduri.
- Este baza de date un singur punct? Construiește două baze de date și datele vor fi sincronizate în ambele direcții. După ce ai închis, treci rapid la biblioteca de rezervă.
- Nu există termen de expirare? Fă doar o sarcină programată pentru a curăța datele de timeout din baza de date la intervale regulate.
- Non-blocare? Fă o buclă de timp până când inserția reușește și apoi revine la succes.
- Non-reentrant? Adaugă un câmp în tabelul bazei de date pentru a înregistra informațiile gazdă și firele mașinii care primește blocajul în prezent, apoi, data viitoare când obții blocarea, interogează mai întâi baza de date, dacă informațiile gazdei și informațiile despre thread ale mașinii curente pot fi găsite în baza de date, poți atribui direct blocajul lui.
- Nedrept? Creează un alt tabel intermediar pentru a înregistra toate firele de execuție care așteaptă blocajul și sortează-le după timpul de creare, iar doar primul creat are voie să achiziționeze blocajul
Blocaje distribuite bazate pe blocaje exclusive
Pe lângă adăugarea și ștergerea înregistrărilor din tabelul de date, blocările distribuite pot fi implementate și cu ajutorul blocajelor care vin cu datele.
Folosim și tabelul bazei de date pe care tocmai l-am creat. Blocajele distribuite pot fi implementate prin blocaje exclusive pe baze de date. Motorul InnoDB bazat pe MySQL poate folosi următoarele metode pentru a implementa operațiuni de blocare:
Dacă adaugi pentru actualizare după instrucțiunea de interogare, baza de date va adăuga un blocaj exclusiv tabelului bazei de date în timpul procesului de interogare. Când se adaugă un blocaj exclusiv într-un registru, alte fire nu mai pot adăuga un blocaj exclusiv la înregistrare pe acea linie.
Putem crede că firul care obține blocajul exclusiv poate obține blocajul distribuit, iar când blocajul este obținut, logica de business a metodei poate fi executată și apoi deblocată prin următoarele metode:
Public Void Unlock(){ connection.commit(); }
prin connection.commit(); operație de eliberare a lacătului.
Această metodă poate rezolva eficient problemele menționate mai sus legate de incapacitatea de a elibera lacătul și de a bloca lacătul.
Blocarea încuietorilor? Instrucțiunea for update revine imediat după execuția cu succes și rămâne blocată până când reușește.
Serviciul a căzut după blocare, nu poate fi eliberat? Astfel, baza de date eliberează blocajul de la sine după ce serviciul cade.
Totuși, încă nu rezolvă direct problema bazei de date cu punct unic, reentranță și blocare corectă.
Pentru a rezuma modul în care se folosește baza de date pentru a implementa blocaje distribuite, ambele bazându-se pe un tabel din baza de date, una este să se determine dacă există o blocare prin existența înregistrărilor din tabel, iar cealaltă este implementarea blocajelor distribuite prin blocajul exclusiv al bazei de date.
Avantajele bazelor de date cu blocare distribuită
Direct cu ajutorul bazei de date, este ușor de înțeles.
Dezavantaje ale implementării blocajelor distribuite în baze de date
Vor exista diverse probleme, iar întreaga soluție va deveni din ce în ce mai complexă în procesul de rezolvare a problemei.
Operarea bazei de date necesită anumite sarcini suplimentare, iar problemele de performanță trebuie luate în considerare.
2. Blocaje distribuite bazate pe cache
Comparativ cu soluția de blocare distribuită bazată pe baze de date, implementarea bazată pe cache va performa mai bine în ceea ce privește performanța.
Există multe produse mature de cache în prezent, inclusiv Redis, memcache etc. Aici luăm Redis ca exemplu pentru a analiza schema folosirii cache-ului pentru implementarea blocajelor distribuite.
Există multe articole conexe pe Internet despre implementarea blocajelor distribuite bazate pe Redis, iar metoda principală de implementare este utilizarea metodei Jedis.setNX.
Există, de asemenea, mai multe probleme cu implementarea de mai sus:
1. Problema unui singur punct.
2. Această încuietoare nu are timp de expirare, iar odată ce operațiunea de deblocare eșuează, va face ca înregistrarea blocării să fie mereu în redis și alte fire de discuție să nu mai poată obține blocarea.
3. Această încuietoare poate fi doar neblocantă și va reveni direct indiferent dacă reușește sau eșuează.
4. Această încuietoare nu este reintrantă, iar după ce un fir obține lacătul, nu poate obține lacătul din nou înainte de a o elibera, deoarece cheia folosită există deja în redis. operațiunile setNX nu mai pot fi executate.
5. Acest blocaj este nedrept, toate firele care așteaptă încep operațiunile setNX în același timp, iar firele norocoase pot obține blocarea.
Desigur, există și modalități de a o rezolva.
- În prezent, serviciile principale de caching suportă implementarea clusterelor pentru a rezolva probleme punctuale unice prin clustering.
- Nu există termen de expirare? Metoda setExpire din redis suportă timpul de expirare primit, iar datele sunt șterse automat după ce acest timp este atins.
- Non-blocare? în timp ce este executat în mod repetat.
- Nu este posibil să reintri? După ce un fir de discuție dobândește blocatul, salvează informațiile gazdei curente și informațiile despre fir și verifică dacă ești proprietarul blocajului curent înainte de a-l obține data viitoare.
- Nedrept? Pune toate firele în așteptare într-o coadă înainte ca un fir să obțină un blocaj, apoi obține blocajul pe baza principiului primul intrat, primul ieșit.
Politica de sincronizare a clusterului redis necesită timp, iar thread-ul A primește un blocaj după setarea cu succes a NX, dar această valoare nu a fost actualizată pe serverul unde thread-ul B execută setNX, ceea ce va cauza probleme de concurență.
Salvatore Sanfilippo, autorul cărții redis, a propus algoritmul Redlock, care implementează management distribuit al blocajelor (DLM) mai sigur și mai fiabil decât un singur nod.
Algoritmul Redlock presupune că există N noduri redis independente unele de altele, în general setate la N=5, iar aceste N noduri rulează pe mașini diferite pentru a menține independența fizică.
Pașii algoritmului sunt următorii:
1. Clientul obține timpul curent în milisecunde. 2. Clientul încearcă să obțină blocajul N noduri (fiecare nod primește blocajul în același mod ca blocajul cache menționat anterior), iar N noduri primesc blocajul cu aceeași cheie și valoare. Clientul trebuie să seteze timeout-ul de acces la interfață, iar timeout-ul interfeței trebuie să fie mult mai mic decât timeout-ul blocajului, de exemplu, timpul eliberării automate a blocajului este de 10s, apoi timeout-ul interfeței este setat la aproximativ 5-50ms. Acest lucru îți permite să folosești un timeout cât mai curând posibil când accesezi un nod redis după ce acesta se oprește și reduce utilizarea normală a blocării. 3. Clientul calculează cât timp durează să obțină blocarea, scăzând timpul obținut la pasul 1 cu timpul curent, doar când clientul obține mai mult de 3 noduri ale blocajului, iar timpul pentru a obține blocajul este mai mic decât timpul de timeout al blocajului, clientul obține blocajul distribuit. 4. Timpul clientului pentru a obține blocajul este timpul de timeout setat minus timpul petrecut pentru obținerea blocajului calculat la pasul 3. 5. Dacă clientul nu reușește să obțină blocajul, acesta va șterge toate blocajele pe rând. Folosind algoritmul Redlock, se poate garanta că serviciul de blocare distribuită poate funcționa în continuare atunci când se blochează până la 2 noduri, ceea ce îmbunătățește mult disponibilitatea comparativ cu blocarea anterioară a bazei de date și a cache-ului.
Totuși, un expert distribuit a scris un articol intitulat "Cum să faci blocarea distribuită" în care punea sub semnul întrebării corectitudinea Redlock-ului.
Expertul a menționat că există două aspecte de luat în considerare atunci când se analizează lacătele distribuite: performanța și corectitudinea.
Dacă folosești un blocaj distribuit de înaltă performanță și corectitudinea nu este necesară, atunci utilizarea unui blocaj cache este suficientă.
Dacă folosești o încuietoare distribuită foarte fiabilă, atunci trebuie să iei în considerare probleme stricte de fiabilitate. Redlock, pe de altă parte, nu îndeplinește corectitudinea. De ce nu? Experții enumeră mai multe aspecte.
În prezent, multe limbaje de programare folosesc mașini virtuale cu funcții GC, în Full GC, programul se oprește din procesarea GC, uneori Full GC durează mult, iar chiar și programul are câteva minute de întârziere; articolul menționează exemplul HBase, HBase uneori GC pentru câteva minute, ceea ce determină expirarea contractului de închiriere. De exemplu, în figura de mai jos, clientul 1 primește un blocaj și este pe punctul de a procesa o resursă partajată, iar când este pe cale să proceseze o resursă comună, GC complet are loc până când blocajul expiră. Astfel, clientul 2 primește din nou blocarea și începe să lucreze la resursa partajată. Când clientul 2 este în procesare, clientul 1 finalizează GC complet și începe procesarea resurselor partajate, astfel încât ambii clienți procesează resurse comune.
Experții au oferit o soluție, așa cum se arată în figura de mai jos: MVCC, aduci un token la blocare, tokenul este conceptul de versiune, de fiecare dată când blocajul operațional este finalizat, tokenul va fi adăugat 1, aduci tokenul la procesarea resurselor partajate, doar versiunea specificată a tokenului poate gestiona resursa partajată.
Apoi expertul a mai spus că algoritmul se bazează pe ora locală și că Redis se bazează pe metoda getTimeOfDay pentru a obține ora în loc de ceasul monoton când gestionează expirarea cheilor, ceea ce duce și la inexactități de timp. De exemplu, într-un scenariu, doi clienți 1 și client 2 au 5 noduri redis (A, B, C, D și E).
1. Clientul 1 obține cu succes blocarea de la A, B și C și obține timeout-ul rețelei de blocare de la D și E. 2. Ceasul nodului C este inexact, cauzând timeout-ul blocării. 3. clientul 2 obține cu succes blocajul de la C, D și E și obține timeout-ul rețelei de blocare de la A și B. 4. Astfel, atât clientul 1, cât și clientul 2 primesc o blocare. Pentru a rezuma cele două puncte pe care experții le spun despre indisponibilitatea lui Redlock:
1. GC și alte scenarii pot apărea oricând, determinând clientul să obțină un blocaj, iar timeout-ul de procesare determină un alt client să obțină blocarea. Experții au oferit, de asemenea, o soluție pentru utilizarea tokenurilor auto-incrementante. 2. Algoritmul se bazează pe ora locală, iar ceasul va fi inexact, rezultând ca doi clienți să primească blocaje simultan. Prin urmare, concluzia dată de experți este că Redlock poate funcționa normal doar în intervalul de întârziere limitată a rețelei, întreruperea programului limitat și eroarea de ceas limitată, dar limitele acestor trei scenarii nu pot fi confirmate, așa că experții nu recomandă utilizarea Redlock. Pentru scenarii cu cerințe ridicate de corectitudine, experții recomandă Zookeeper, care va fi discutat mai târziu folosind Zookeeper ca lacăt distribuit.
Răspunsul autorului Redis
Autorul Redis a răspuns scriind un blog după ce a văzut articolul expertului. Autorul i-a mulțumit politicos expertului, apoi și-a exprimat dezacordul față de punctul de vedere al acestuia.
Discuția autorului REDIS despre utilizarea token-urilor pentru a rezolva problema timeout-ului blocajului poate fi rezumată în următoarele cinci puncte:
Punctul 1, utilizarea blocajelor distribuite este, în general, în sensul că nu există altă modalitate de a controla resursele partajate, experții folosesc tokenuri pentru a asigura procesarea resurselor comune, deci nu mai este nevoie de blocaje distribuite. Punctul 2: Pentru generarea de token-uri, pentru a asigura fiabilitatea token-urilor obținute de diferiți clienți, serviciul care generează token-uri are în continuare nevoie de blocaje distribuite pentru a asigura fiabilitatea serviciului. Punctul 3, pentru modul în care experții spun token-urile auto-incrementante, autorul redis consideră că este complet inutil, fiecare client poate genera un UUID unic ca token și poate seta resursa partajată într-o stare pe care doar clientul cu uuid-ul o poate gestiona, astfel încât ceilalți clienți să nu poată procesa resursa partajată până când clientul care obține blocajul nu eliberează blocarea. Așa cum se arată în figura de mai sus, dacă clientul tokenului 34 trimite GC în timpul procesului de scriere și determină expirarea blocării, un alt client poate obține blocajul tokenului 35 și poate începe să scrie din nou, rezultând un conflict de blocare. Prin urmare, ordinea jetoanelor nu poate fi combinată cu resursele comune. Punctul 5, autorul redis consideră că, în majoritatea scenariilor, blocajele distribuite sunt folosite pentru a gestiona problemele de actualizare în scenarii non-tranzacționale. Autorul ar trebui să spună că există unele scenarii în care este dificil să combini token-uri pentru a gestiona resurse comune, așa că trebuie să te bazezi pe blocaje pentru a bloca resursele și a le procesa. O altă problemă a ceasului despre care vorbesc experții, autorii Redis oferă și o explicație. Dacă timpul necesar pentru a obține lacătul este prea lung și depășește timpul implicit de timeout al lacătului, atunci clientul nu poate obține lacătul în acest moment și nu vor exista exemple propuse de experți.
Sentimente personale
Prima problemă pe care o rezum este că, după ce un client obține un blocaj distribuit, blocajul poate fi eliberat după un timeout în timpul procesării clientului. Anterior, când se discuta despre timeout-ul stabilit de blocarea bazei de date de 2 minute, dacă o sarcină ocupă blocarea ordinului mai mult de 2 minute, celălalt centru de tranzacționare poate obține acest blocaj de ordine, astfel încât cele două centre de tranzacționare să poată procesa același ordin în același timp. În condiții normale, sarcina este procesată în câteva secunde, dar uneori, timeout-ul stabilit prin unirea la o cerere RPC este prea lung, iar dacă există mai multe astfel de cereri de timeout într-o sarcină, este probabil ca timpul automat de deblocare să fie depășit. Dacă scriem în Java, poate exista Full GC la mijloc, astfel încât după ce blocarea este deblocată după timeout-ul blocajului, clientul nu o poate percepe, ceea ce este un lucru foarte serios. Nu cred că este o problemă legată de încuietoarea în sine, atâta timp cât orice blocaj distribuit menționat mai sus are caracteristicile de eliberare a timeout-ului, o astfel de problemă va apărea. Dacă folosești funcția de timeout de blocare, clientul trebuie să seteze timeout-ul blocat și să acționeze în consecință, în loc să continue procesarea resursei comune. Algoritmul Redlock-ului returnează timpul de blocare pe care clientul îl poate ocupa după ce clientul dobândește blocarea, iar clientul trebuie să proceseze acest timp pentru a opri sarcina după acel timp.
A doua problemă este că experții distribuiți nu înțeleg Redlock. O caracteristică cheie a Redlock este că timpul necesar pentru obținerea blocajului este timpul total în care blocajul trece implicit la timeout minus timpul necesar pentru a-l obține, astfel încât timpul necesar clientului pentru procesare este relativ, indiferent de ora locală.
Din acest punct de vedere, corectitudinea Redlock poate fi pe deplin garantată. Analiza atentă a Redlock-ului, comparativ cu redis-ul unui nod, principala caracteristică oferită de Redlock este o fiabilitate mai ridicată, care este o caracteristică importantă în unele scenarii. Dar cred că Redlock a cheltuit prea mulți bani pentru a obține fiabilitate.
- În primul rând, trebuie implementate 5 noduri pentru a face Redlock-ul mai fiabil.
- Apoi trebuie să ceri 5 noduri pentru a obține blocarea, iar prin metoda Future poți mai întâi să ceri simultan la 5 noduri și apoi să obții rezultatul răspunsului, ceea ce poate scurta timpul de răspuns, dar tot durează mai mult decât un blocaj redis cu un singur nod.
- Apoi, deoarece mai mult de 3 din cele 5 noduri trebuie obținute, poate apărea un conflict de blocare, adică toată lumea a obținut 1-2 blocaje și, ca rezultat, nimeni nu poate obține blocarea; această problemă, autorul Redis împrumută esența algoritmului raft, prin coliziune la un moment aleatoriu, timpul de conflict poate fi redus considerabil, dar această problemă nu poate fi evitată foarte bine, mai ales când blocajul este dobândit pentru prima dată, astfel că costul de timp pentru obținerea blocajului crește.
- Dacă 2 dintre cele 5 noduri sunt căzute, disponibilitatea blocajului va fi mult redusă; în primul rând, trebuie să aștepți ca rezultatele acestor două noduri doborâte să expire înainte de a reveni, iar sunt doar 3 noduri, iar clientul trebuie să obțină blocajele tuturor celor 3 noduri pentru a avea blocarea, ceea ce este de asemenea mai dificil.
- Dacă există o partiție de rețea, atunci poate exista o situație în care clientul nu va putea niciodată să obțină blocarea.
După ce am analizat atât de multe motive, cred că cel mai critic punct al problemei Redlock este că Redlock cere clienților să asigure consistența scrierilor, iar cele 5 noduri din backend sunt complet independente, iar toți clienții trebuie să opereze aceste 5 noduri. Dacă există un lider între 5 noduri, clientul poate sincroniza datele liderului atâta timp cât clientul obține blocarea de la lider, astfel încât să nu existe probleme precum partiționarea, timeout-urile sau conflictele. Prin urmare, pentru a asigura corectitudinea blocărilor distribuite, cred că folosirea unui serviciu de coordonare distribuită cu o consistență puternică poate rezolva mai bine problema.
Întrebarea apare din nou: cât timp ar trebui să setez timpul de expirare? Cum să setezi timpul de invalidare este prea scurt, iar blocajul este eliberat automat înainte ca metoda să fie executată, astfel vor apărea probleme de concurență. Dacă durează prea mult, alte thread-uri care primesc blocarea pot fi nevoite să aștepte mult timp.
Această problemă există și cu utilizarea bazelor de date pentru implementarea blocajelor distribuite.
Abordarea actuală principală a acestei probleme este să setezi un timp scurt de timeout pentru fiecare blocaj obținut și să începi un fir de discuție pentru a reîmprospăta timpul de timeout de fiecare dată când este pe cale să ajungă la acel timeout. Închei acest fir de discuție în același timp cu eliberarea lacătului. De exemplu, redisson, componenta oficială de blocare distribuită a redis, folosește această soluție.
Avantajele utilizării caching-ului pentru implementarea blocărilor distribuite Performanță bună.
Dezavantaje ale utilizării caching-ului pentru implementarea blocajelor distribuite Implementarea este prea responsabilă, sunt prea mulți factori de luat în considerare.
Blocaje distribuite bazate pe implementarea Zookeeper
Blocaje distribuite bazate pe noduri ordonate temporare din Zookeeper.
Ideea generală este că atunci când fiecare client blochează o metodă, un nod ordonat instantaneu unic este generat în directorul nodului specificat, corespunzător metodei de pe Zookeeper. Modul de a determina dacă să obții un blocaj este simplu, trebuie doar să determini pe cel cu cel mai mic număr de serie în nodul ordonat. Când blocajul este eliberat, pur și simplu ștergeți nodul instantaneu. În același timp, poate evita problema blocajelor cauzate de întreruperea serviciului care nu poate fi eliberată.
Să vedem dacă Zookeeper poate rezolva problemele menționate anterior.
- Încuietoarea nu se deschide? Utilizarea Zookeeper poate rezolva eficient problema blocajelor care nu sunt eliberate, deoarece la crearea blocării, clientul va crea un nod temporar în ZK, iar odată ce clientul obține blocajul și îl blochează brusc (conexiunea sesiunii este întreruptă), nodul temporar va fi eliminat automat. Alți clienți pot obține din nou lacătul.
- Încuietori care nu blochează? Odată ce nodul se schimbă, Zookeeper va notifica clientul, iar clientul poate verifica dacă nodul creat este cel mai mic număr ordinal dintre toate nodurile.
- Nu poți reintra? Când clientul creează un nod, scrie direct informațiile gazdei și informațiile despre thread ale clientului curent către nod, iar data viitoare când vrei să obții blocarea, poți compara datele din cel mai mic nod curent. Dacă informația este aceeași cu a ta, atunci poți obține direct blocarea, iar dacă este diferită, poți crea un nod secvențial temporar pentru a participa la coadă.
Întrebarea apare din nou: știm că Zookeeper trebuie implementat în clustere, vor apărea probleme de sincronizare a datelor precum clusterele Redis?
Zookeeper este o componentă distribuită care garantează consistența slabă, adică consistența finală.
Zookeeper folosește un protocol de sincronizare a datelor numit Quorum Based Protocol. Dacă există N servere Zookeeper în clusterul Zookeeper (N este de obicei impar, 3 pot respecta fiabilitatea datelor și au performanțe ridicate la citire și scriere, iar 5 au cel mai bun echilibru între fiabilitatea datelor și performanța de citire și scriere), atunci o operație de scriere a utilizatorului este mai întâi sincronizată cu N/2 + 1 servere, apoi returnată utilizatorului, solicitându-l să scrie cu succes. Protocolul de sincronizare a datelor bazat pe Protocolul Bazat pe Quorum determină consistența forței pe care Zookeeper o poate susține.
Într-un mediu distribuit, stocarea datelor care respectă o consistență puternică este practic inexistentă și necesită ca toate nodurile să fie actualizate sincron atunci când se actualizează datele unui singur nod. Această strategie de sincronizare apare în baza de date de replicare sincronă master-slave. Totuși, această strategie de sincronizare are un impact prea mare asupra performanței scrierii și este rar întâlnită în practică. Deoarece Zookeeper scrie N/2+1 noduri sincron, iar N/2 noduri nu sunt actualizate sincron, Zookeeper nu este puternic consistent.
Operația de actualizare a datelor de către utilizator nu garantează că citirile ulterioare vor citi valoarea actualizată, dar va arăta în cele din urmă consistență. Sacrificarea consistenței nu înseamnă ignorarea completă a consistenței datelor, altfel datele devin haotice, așa că, indiferent cât de mare este disponibilitatea sistemului, indiferent cât de bună este distribuția, acestea nu au nicio valoare. Sacrificarea consistenței înseamnă pur și simplu că o consistență puternică în bazele de date relaționale nu mai este necesară, ci atâta timp cât sistemul poate atinge consistența în cele din urmă.
O întrebare cu un singur punct? Utilizarea Zookeeper poate rezolva eficient o problemă punctuală, ZK este implementat în clustere, atâta timp cât mai mult de jumătate dintre mașinile din cluster supraviețuiesc, serviciul poate fi oferit lumii exterioare.
Probleme de echitate? Utilizarea Zookeeper poate rezolva problema blocărilor corecte, nodurile temporare create de client în ZK sunt ordonate, iar de fiecare dată când blocajul este eliberat, ZK poate notifica cel mai mic nod pentru a obține blocarea, asigurând echitatea.
Întrebarea apare din nou: știm că Zookeeper trebuie implementat în clustere, vor apărea probleme de sincronizare a datelor precum clusterele Redis?
Zookeeper este o componentă distribuită care garantează consistența slabă, adică consistența finală.
Zookeeper folosește un protocol de sincronizare a datelor numit Quorum Based Protocol. Dacă există N servere Zookeeper în clusterul Zookeeper (N este de obicei impar, 3 pot respecta fiabilitatea datelor și au performanțe ridicate la citire și scriere, iar 5 au cel mai bun echilibru între fiabilitatea datelor și performanța de citire și scriere), atunci o operație de scriere a utilizatorului este mai întâi sincronizată cu N/2 + 1 servere, apoi returnată utilizatorului, solicitându-l să scrie cu succes. Protocolul de sincronizare a datelor bazat pe Protocolul Bazat pe Quorum determină consistența forței pe care Zookeeper o poate susține.
Într-un mediu distribuit, stocarea datelor care respectă o consistență puternică este practic inexistentă și necesită ca toate nodurile să fie actualizate sincron atunci când se actualizează datele unui singur nod. Această strategie de sincronizare apare în baza de date de replicare sincronă master-slave. Totuși, această strategie de sincronizare are un impact prea mare asupra performanței scrierii și este rar întâlnită în practică. Deoarece Zookeeper scrie N/2+1 noduri sincron, iar N/2 noduri nu sunt actualizate sincron, Zookeeper nu este puternic consistent.
Operația de actualizare a datelor de către utilizator nu garantează că citirile ulterioare vor citi valoarea actualizată, dar va arăta în cele din urmă consistență. Sacrificarea consistenței nu înseamnă ignorarea completă a consistenței datelor, altfel datele devin haotice, așa că, indiferent cât de mare este disponibilitatea sistemului, indiferent cât de bună este distribuția, acestea nu au nicio valoare. Sacrificarea consistenței înseamnă pur și simplu că o consistență puternică în bazele de date relaționale nu mai este necesară, ci atâta timp cât sistemul poate atinge consistența în cele din urmă.
Dacă Zookeeper respectă consistența cauzală depinde de modul în care clientul este programat.
Practici care nu satisfac consistența cauzală
- Procesul A scrie o bucată de date în /z al Zookeeper și returnează cu succes
- Procesul A informează procesul B că A a modificat datele din /z
- B citește datele din /z din Zookeeper
- Deoarece serverul Zookeeper-ului conectat la B poate să nu fi fost actualizat cu datele scrise de A, atunci B nu va putea citi datele scrise de A
Practici care respectă consistența cauzală
- Procesul B ascultă modificările de date în /z pe Zookeeper
- Procesul A scrie o bucată de date pe /z al Zookeeper, iar înainte ca aceasta să se întoarcă cu succes, Zookeeper trebuie să sune ascultătorul înregistrat pe /z, iar liderul îl va notifica pe B despre schimbarea datelor
- După ce metoda de răspuns la evenimente a procesului B primește răspuns, aceasta ia datele modificate, astfel încât B va putea cu siguranță să obțină valoarea modificată
- Consistența cauzală aici se referă la consistența cauzală dintre Lider și B, adică liderul notifică datele despre o schimbare
Mecanismul al doilea de ascultare a evenimentelor este, de asemenea, metoda care ar trebui folosită pentru a programa corect Zookeeper, astfel încât Zookeeper să respecte consistența cauzală
Prin urmare, când implementăm blocaje distribuite bazate pe Zookeeper, ar trebui să folosim practica de a satisface consistența cauzală, adică firele care așteaptă blocarea ascultă modificările din blocarea Zookeeper, iar când blocarea este eliberată, Zookeeper va notifica firul de așteptare care îndeplinește condițiile de blocare corecte.
Poți folosi direct clientul terț de bibliotecă Zookeeper, care cuprinde un serviciu de încuietori reentrant.
Încuietorile distribuite implementate cu ZK par să se potrivească exact cu ceea ce ne așteptam de la un blocaj distribuit la începutul acestui articol. Totuși, nu este așa, iar blocajul distribuit implementat de Zookeeper are de fapt un dezavantaj, și anume că performanța poate să nu fie la fel de ridicată ca cea a serviciului de cache. Pentru că de fiecare dată în procesul de creare și eliberare a unui blocaj, nodurile instantanee trebuie create și distruse dinamic pentru a realiza funcția de blocare. Crearea și ștergerea nodurilor în ZK poate fi realizată doar prin serverul leader, iar apoi datele sunt partajate cu toate mașinile urmăritoare.
Avantaje ale utilizării Zookeeper pentru implementarea lacătorilor distribuite Rezolvarea eficientă a problemelor cu punct unic, problemelor de neintrare, problemelor de neblocare și eșecul blocării la eliberare. Este relativ simplu de implementat.
Dezavantaje ale utilizării Zookeeper pentru implementarea încuietorilor distribuite Performanța nu este la fel de bună ca folosirea cache-ului pentru implementarea blocajelor distribuite. Este necesară înțelegerea principiilor ZK.
Comparație între cele trei opțiuni
Din perspectiva ușurinței de a înțelege (de la jos la sus) Cache > bază de date > Zookeeper
Din perspectiva complexității implementării (de la scăzut la ridicat) Cache> Zookeeper > baze de date
Din perspectiva performanței (de la înalt la jos) Caching > Zookeeper >= bază de date
Din punct de vedere al fiabilității (de la cel mai înalt la cel scăzut) Cache> Zookeeper > baze de date
|