Varför kan du behöva semi-synkron replikering?

Hej alla. Vladislav Rodin är i kontakt. Jag undervisar för närvarande i kurser om mjukvaruarkitektur och högstressprogramvaruarkitektur på OTUS. I väntan på start av ett nytt kursflöde "Högbelastningsarkitekt" Jag bestämde mig för att skriva ett kort stycke originalmaterial som jag vill dela med dig.

Varför kan du behöva semi-synkron replikering?

Inledning

På grund av det faktum att hårddisken bara kan utföra cirka 400-700 operationer per sekund (vilket är ojämförligt med typiska rps på ett högbelastningssystem) är den klassiska diskdatabasen arkitekturens flaskhals. Därför är det nödvändigt att ägna särskild uppmärksamhet åt skalningsmönstren för denna lagring.

För närvarande finns det två skalningsmönster för databaser: replikering och skärning. Sharding låter dig skala skrivoperationen och, som ett resultat, minska rps per skriv per server i ditt kluster. Replikering låter dig göra samma sak, men med läsoperationer. Det är detta mönster som den här artikeln ägnas åt.

Replikering

Om du tittar på replikering på en mycket hög nivå är det en enkel sak: du hade en server, det fanns data på den, och sedan kunde den här servern inte längre klara av belastningen av att läsa dessa data. Du lägger till ytterligare ett par servrar, synkroniserar data över alla servrar och användaren kan läsa från vilken server som helst i ditt kluster.

Trots sin uppenbara enkelhet finns det flera alternativ för att klassificera olika implementeringar av detta schema:

  • Efter roller i klustret (master-master eller master-slave)
  • Av skickade objekt (radbaserad, uttalandebaserad eller blandad)
  • Enligt nodsynkroniseringsmekanismen

Idag ska vi ta itu med punkt 3.

Hur uppstår en transaktionsåtagande?

Det här ämnet är inte direkt relaterat till replikering; en separat artikel kan skrivas om det, men eftersom ytterligare läsning är värdelös utan att förstå transaktionsförpliktelsens mekanism, låt mig påminna dig om de mest grundläggande sakerna. En transaktionsåtagande sker i 3 steg:

  1. Logga en transaktion till databasloggen.
  2. Använda en transaktion i en databasmotor.
  3. Returnerar bekräftelse till kunden att transaktionen genomfördes framgångsrikt.

I olika databaser kan denna algoritm ha nyanser: till exempel i InnoDB-motorn i MySQL-databasen finns det 2 loggar: en för replikering (binär logg) och den andra för att underhålla ACID (ångra/gör om logg), medan den är i PostgreSQL det finns en logg som utför båda funktionerna (skriv framåt logg = WAL). Men vad som presenteras ovan är just det allmänna konceptet, som gör att sådana nyanser inte kan beaktas.

Synkron (synk) replikering

Låt oss lägga till logik för att replikera de mottagna ändringarna i transaktionsbekräftelsealgoritmen:

  1. Logga en transaktion till databasloggen.
  2. Använda en transaktion i en databasmotor.
  3. Skickar data till alla repliker.
  4. Får bekräftelse från alla repliker att en transaktion har slutförts på dem.
  5. Returnerar bekräftelse till kunden att transaktionen genomfördes framgångsrikt.

Med detta tillvägagångssätt får vi ett antal nackdelar:

  • klienten väntar på att ändringarna ska tillämpas på alla repliker.
  • när antalet noder i klustret ökar minskar vi sannolikheten för att skrivoperationen kommer att lyckas.

Om allt är mer eller mindre klart med 1:a punkten, så är anledningarna till 2:a punkten värda att förklara. Om vi ​​under synkron replikering inte får något svar från minst en nod, återställer vi transaktionen. Genom att öka antalet noder i klustret ökar du således sannolikheten för att en skrivoperation misslyckas.

Kan vi vänta på bekräftelse från endast en viss procentandel av noder, till exempel från 51% (quorum)? Ja, det kan vi, men i den klassiska versionen krävs bekräftelse från alla noder, eftersom det är så vi kan säkerställa fullständig datakonsistens i klustret, vilket är en otvivelaktig fördel med denna typ av replikering.

Asynkron (asynkron) replikering

Låt oss ändra den tidigare algoritmen. Vi kommer att skicka data till replikerna "någon gång senare", och "någon gång senare" kommer ändringarna att tillämpas på replikerna:

  1. Logga en transaktion till databasloggen.
  2. Använda en transaktion i en databasmotor.
  3. Returnerar bekräftelse till kunden att transaktionen genomfördes framgångsrikt.
  4. Skickar data till repliker och tillämpar ändringar på dem.

Detta tillvägagångssätt leder till att klustret fungerar snabbt, eftersom vi inte låter klienten vänta på att data ska nå replikerna och till och med bli engagerad.

Men villkoret att dumpa data på repliker "någon gång senare" kan leda till förlust av en transaktion och till förlust av en transaktion som bekräftats av användaren, för om data inte hann replikeras, en bekräftelse till kunden om framgången för operationen skickades, och noden till vilken ändringarna kom kraschade HDD, vi förlorar transaktionen, vilket kan leda till mycket obehagliga konsekvenser.

Semisynk replikering

Slutligen kommer vi till semi-synkron replikering. Denna typ av replikering är inte särskilt välkänd eller mycket vanlig, men den är av stort intresse eftersom den kan kombinera fördelarna med både synkron och asynkron replikering.

Låt oss försöka kombinera de två tidigare tillvägagångssätten. Vi kommer inte att behålla klienten länge, men vi kommer att kräva att uppgifterna replikeras:

  1. Logga en transaktion till databasloggen.
  2. Använda en transaktion i en databasmotor.
  3. Skickar data till repliker.
  4. Får bekräftelse från repliken att ändringarna har tagits emot (de kommer att tillämpas "någon gång senare").
  5. Returnerar bekräftelse till kunden att transaktionen genomfördes framgångsrikt.

Observera att med denna algoritm inträffar transaktionsförlust endast om både noden som tar emot ändringarna och repliknoden misslyckas. Sannolikheten för ett sådant misslyckande anses låg, och dessa risker accepteras.

Men med detta tillvägagångssätt finns det en möjlig risk för fantomläsningar. Låt oss föreställa oss följande scenario: i steg 4 fick vi ingen bekräftelse från någon replik. Vi måste återställa denna transaktion och inte returnera en bekräftelse till kunden. Eftersom data tillämpades i steg 2 finns det ett tidsintervall mellan slutet av steg 2 och återställningen av transaktionen, under vilken parallella transaktioner kan se ändringar som inte ska finnas i databasen.

Förlustfri halvsynk replikering

Om du tänker lite kan du bara vända stegen i algoritmen och åtgärda problemet med fantomläsningar i det här scenariot:

  1. Logga en transaktion till databasloggen.
  2. Skickar replikdata.
  3. Får bekräftelse från repliken att ändringarna har tagits emot (de kommer att tillämpas "någon gång senare").
  4. Använda en transaktion i en databasmotor.
  5. Returnerar bekräftelse till kunden att transaktionen genomfördes framgångsrikt.

Nu gör vi ändringar endast om de har replikerats.

Utgång

Som alltid finns det inga idealiska lösningar, det finns en uppsättning lösningar, som var och en har sina egna fördelar och nackdelar och är lämpliga för att lösa olika klasser av problem. Detta är absolut sant för att välja en mekanism för att synkronisera data i en replikerad databas. Uppsättningen fördelar som semi-synkron replikering har är tillräckligt solid och intressant för att den kan anses värd att uppmärksammas, trots sin låga prevalens.

Det är allt. Ses på kurs!

Källa: will.com

Lägg en kommentar