Hvorfor kan du have brug for semi-synkron replikering?

Hej alle. Vladislav Rodin er i kontakt. Jeg underviser i øjeblikket i kurser om Software Architecture og High-Stress Software Architecture på OTUS. I forventning om starten på et nyt kursusflow "Højbelastningsarkitekt" Jeg besluttede at skrive et kort stykke originalt materiale, som jeg vil dele med dig.

Hvorfor kan du have brug for semi-synkron replikering?

Indledning

På grund af det faktum, at HDD'en kun kan udføre omkring 400-700 operationer i sekundet (hvilket er uforlignelig med typiske rps på et højbelastningssystem), er den klassiske diskdatabase flaskehalsen i arkitekturen. Derfor er det nødvendigt at være særlig opmærksom på skaleringsmønstrene for denne opbevaring.

I øjeblikket er der 2 databaseskaleringsmønstre: replikering og sharding. Sharding giver dig mulighed for at skalere skriveoperationen og som et resultat reducere rps pr. skrivning pr. server i din klynge. Replikering giver dig mulighed for at gøre det samme, men med læseoperationer. Det er dette mønster, denne artikel er viet til.

Replikation

Hvis du ser på replikering på et meget højt niveau, er det en simpel ting: du havde én server, der var data på den, og så kunne denne server ikke længere klare belastningen af ​​at læse disse data. Du tilføjer et par servere mere, synkroniserer data på tværs af alle servere, og brugeren kan læse fra enhver server i din klynge.

På trods af dens tilsyneladende enkelhed er der flere muligheder for at klassificere forskellige implementeringer af denne ordning:

  • Efter roller i klyngen (master-master eller master-slave)
  • Efter sendte objekter (rækkebaseret, erklæringsbaseret eller blandet)
  • Ifølge nodesynkroniseringsmekanismen

I dag skal vi behandle punkt 3.

Hvordan opstår en transaktionsforpligtelse?

Dette emne er ikke direkte relateret til replikering; der kan skrives en separat artikel om det, men da yderligere læsning er ubrugelig uden at forstå transaktionsforpligtelsesmekanismen, lad mig minde dig om de mest grundlæggende ting. En transaktionsforpligtelse sker i 3 faser:

  1. Logning af en transaktion til databaseloggen.
  2. Brug af en transaktion i en databasemotor.
  3. Returnerer bekræftelse til kunden på, at transaktionen blev gennemført.

I forskellige databaser kan denne algoritme have nuancer: for eksempel er der i InnoDB-motoren i MySQL-databasen 2 logfiler: en til replikering (binær log) og den anden til at vedligeholde ACID (fortryd/gendan log), mens den er i PostgreSQL der er én log, der udfører begge funktioner (skriv frem log = WAL). Men det, der præsenteres ovenfor, er netop det generelle koncept, som tillader sådanne nuancer ikke at blive taget i betragtning.

Synkron (synkron) replikering

Lad os tilføje logik for at replikere de modtagne ændringer til transaktionsbekræftelsesalgoritmen:

  1. Logning af en transaktion til databaseloggen.
  2. Brug af en transaktion i en databasemotor.
  3. Sender data til alle replikaer.
  4. Modtagelse af bekræftelse fra alle replikaer på, at en transaktion er gennemført på dem.
  5. Returnerer bekræftelse til kunden på, at transaktionen blev gennemført.

Med denne tilgang får vi en række ulemper:

  • klienten venter på, at ændringerne anvendes på alle replikaer.
  • efterhånden som antallet af noder i klyngen stiger, mindsker vi sandsynligheden for, at skriveoperationen vil lykkes.

Hvis alt er mere eller mindre klart med 1. pkt., så er årsagerne til 2. pkt. værd at forklare. Hvis vi under synkron replikering ikke modtager et svar fra mindst én node, ruller vi transaktionen tilbage. Ved at øge antallet af noder i klyngen øger du således sandsynligheden for, at en skriveoperation mislykkes.

Kan vi vente på bekræftelse fra kun en vis procentdel af noder, for eksempel fra 51% (quorum)? Ja, det kan vi, men i den klassiske version kræves bekræftelse fra alle noder, fordi det er sådan, vi kan sikre fuldstændig datakonsistens i klyngen, hvilket er en utvivlsom fordel ved denne type replikering.

Asynkron (asynkron) replikation

Lad os ændre den tidligere algoritme. Vi sender data til replikaerne "en gang senere", og "en gang senere" vil ændringerne blive anvendt på replikaerne:

  1. Logning af en transaktion til databaseloggen.
  2. Brug af en transaktion i en databasemotor.
  3. Returnerer bekræftelse til kunden på, at transaktionen blev gennemført.
  4. Sende data til replikaer og anvende ændringer på dem.

Denne tilgang fører til, at klyngen fungerer hurtigt, fordi vi ikke lader klienten vente på, at dataene når replikaerne og endda bliver forpligtet.

Men betingelsen om at dumpe data på replikaer "en gang senere" kan føre til tab af en transaktion og til tab af en transaktion bekræftet af brugeren, for hvis dataene ikke havde tid til at blive replikeret, en bekræftelse til klienten om succesen af ​​operationen blev sendt, og den node, som ændringerne ankom til, styrtede ned HDD, vi mister transaktionen, hvilket kan føre til meget ubehagelige konsekvenser.

Semisync replikering

Endelig kommer vi til semi-synkron replikering. Denne type replikering er ikke særlig kendt eller meget almindelig, men den er af stor interesse, fordi den kan kombinere fordelene ved både synkron og asynkron replikation.

Lad os prøve at kombinere de 2 tidligere tilgange. Vi beholder ikke klienten længe, ​​men vi vil kræve, at dataene replikeres:

  1. Logning af en transaktion til databaseloggen.
  2. Brug af en transaktion i en databasemotor.
  3. Sender data til replikaer.
  4. Modtagelse af bekræftelse fra replikaen på, at ændringerne er blevet modtaget (de vil blive anvendt "en gang senere").
  5. Returnerer bekræftelse til kunden på, at transaktionen blev gennemført.

Bemærk venligst, at med denne algoritme opstår transaktionstab kun, hvis både den node, der modtager ændringerne, og replika-noden fejler. Sandsynligheden for en sådan fejl anses for lav, og disse risici accepteres.

Men med denne tilgang er der en mulig risiko for fantomlæsninger. Lad os forestille os følgende scenarie: i trin 4 modtog vi ikke bekræftelse fra nogen replika. Vi skal rulle denne transaktion tilbage og ikke returnere en bekræftelse til kunden. Da dataene blev anvendt i trin 2, er der et tidsrum mellem slutningen af ​​trin 2 og tilbagerulningen af ​​transaktionen, hvor parallelle transaktioner kan se ændringer, der ikke bør være i databasen.

Tabsfri semisync replikering

Hvis du tænker lidt, kan du bare vende algoritmens trin og løse problemet med fantomlæsninger i dette scenarie:

  1. Logning af en transaktion til databaseloggen.
  2. Sender replikadata.
  3. Modtagelse af bekræftelse fra replikaen på, at ændringerne er blevet modtaget (de vil blive anvendt "en gang senere").
  4. Brug af en transaktion i en databasemotor.
  5. Returnerer bekræftelse til kunden på, at transaktionen blev gennemført.

Nu foretager vi kun ændringer, hvis de er blevet replikeret.

Output

Som altid er der ingen ideelle løsninger; der er et sæt løsninger, som hver har sine egne fordele og ulemper og er velegnede til at løse forskellige problemklasser. Dette er absolut sandt for at vælge en mekanisme til synkronisering af data i en replikeret database. Sættet af fordele, som semi-synkron replikation har, er tilstrækkelig solidt og interessant til, at det kan anses for at være værd at være opmærksom på, på trods af dets lave udbredelse.

Det er alt. Vi ses kl Rute!

Kilde: www.habr.com

Tilføj en kommentar