Varför kan halvsynkron replikation behövas?

Hej allihop. Det hÀr Àr Vladislav Rodin. För nÀrvarande undervisar jag pÄ plattformen OTUS kurser som handlar om programvaruarkitektur och programvaruarkitektur som utsÀtts för hög belastning. Inför starten av den nya kursomgÄngen »Arkitekt för hög belastning« har jag beslutat att skriva ett litet originalmaterial som jag vill dela med er.

Varför kan halvsynkron replikation behövas?

Introduktion

Eftersom HDD endast kan utföra cirka 400-700 operationer per sekund (vilket Àr ohÄllbart jÀmfört med de typiska rps:arna som förekommer i ett högbelastningssystem), utgör en klassisk databashantering en flaskhals i arkitekturen. DÀrför Àr det nödvÀndigt att sÀrskilt fokusera pÄ mönstren för skalning av detta lagringssystem.

För nÀrvarande finns det tvÄ mönster för att skala databasen: replikation och sharding. Sharding gör det möjligt att skala skrivoperationen och, som följd, minska den rps som skrivs till en server i din kluster. Replikation gör samma sak men med lÀsoperationer. Denna artikel Àgnas just detta mönster.

Replikation

Om man ser replikation pÄ en helt övergripande nivÄ, Àr det en enkel sak: du hade en server, dÀr data lagrades, och sedan klarade inte denna server av belastningen vid lÀsning av dessa data. Du lÀgger till nÄgra fler servrar, synkroniserar data pÄ alla servrar, och anvÀndare kan lÀsa frÄn vilken server som helst i ditt kluster.

Trots den till synes enkla strukturen finns det flera sÀtt att klassificera olika implementeringar av detta schema:

  • Efter roller i klustret (master-master eller master-slave)
  • Efter överförda objekt (row-based, statement-based eller mixed)
  • Efter mekanismen för nodsynkronisering

Idag ska vi fokusera pÄ just den tredje punkten.

Hur en transaktion kommittas

Detta Àmne Àr direkt relaterat till replikation, en separat artikel kan skrivas om det, men eftersom förstÄelsen för hur transaktioner kommittas Àr avgörande för att lÀsa vidare, lÄt mig pÄminna om de mest grundlÀggande delarna. Kommittén av transaktionen sker i tre steg:

  1. Skriva transaktionen till databasens logg.
  2. TillÀmpa transaktionen i databasens motor.
  3. Skicka bekrÀftelse till klienten om att transaktionen har tillÀmpats framgÄngsrikt.

I olika databaser kan det uppstÄ nyanser i denna algoritm: till exempel har MySQL:s InnoDB-motor 2 loggar: en för replikering (binary log) och en annan för att upprÀtthÄlla ACID (undo/redolog), medan PostgreSQL har en logg som utför bÄda funktionerna (write ahead log = WAL). Men ovan har den allmÀnna konceptet presenterats, vilket gör det möjligt att ignorera sÄdana nyanser.

Synchronous (sync) replikering

LÄt oss lÀgga till logik för replikering av de erhÄllna Àndringarna i algoritmen för transaktionscommit:

  1. Skriva transaktionen till databasens logg.
  2. TillÀmpa transaktionen i databasens motor.
  3. Skicka data till alla repliker.
  4. Ta emot bekrÀftelse frÄn alla repliker om att transaktionen har genomförts.
  5. Skicka bekrÀftelse till klienten om att transaktionen har tillÀmpats framgÄngsrikt.

Med denna metod fÄr vi ett antal nackdelar:

  • klienten vĂ€ntar pĂ„ att Ă€ndringarna ska tillĂ€mpas pĂ„ alla repliker.
  • med en ökning av antalet noder i klustret minskar vi sannolikheten för att skrivoperationen lyckas.

Om första punkten Àr mer eller mindre klar, sÄ Àr anledningarna till den andra punkten vÀrda att förklara. Om vi inte fÄr svar frÄn minst en nod vid synkroniserad replikering, rullar vi tillbaka transaktionen. DÀrmed, genom att öka antalet noder i klustret, ökar du sannolikheten för att skrivoperationen misslyckas.

Kan vi vÀnta pÄ bekrÀftelse endast frÄn en viss andel av noderna, till exempel frÄn 51% (kvorum)? Ja, det kan vi, men i det klassiska fallet krÀvs bekrÀftelse frÄn alla noder, eftersom det Àr sÄ vi kan sÀkerstÀlla full konsistens av data i klustret, vilket utan tvekan Àr en fördel med denna typ av replikering.

Asynkron (async) replikering

LÄt oss förÀndra den tidigare algoritmen. Vi kommer att skicka data till repliker "nÄgon gÄng senare", och "nÄgon gÄng senare" kommer Àndringarna att tillÀmpas pÄ replikerna:

  1. Skriva transaktionen till databasens logg.
  2. TillÀmpa transaktionen i databasens motor.
  3. Skicka bekrÀftelse till klienten om att transaktionen har tillÀmpats framgÄngsrikt.
  4. Skicka data till replikerna och tillÀmpa Àndringarna.

Denna metod resulterar i att klustret arbetar snabbt, eftersom vi inte hÄller klienten i vÀntan medan data nÄr repliker och Ànnu mÄste bli kommittade.

Men villkoret för att skicka data till repliker "nÄgon gÄng senare" kan leda till att transaktionen gÄr förlorad, inklusive en bekrÀftad transaktion för anvÀndaren, eftersom om data inte hinner replikeras och bekrÀftelse har skickats till klienten om att operationen har genomförts men noden, som fick Àndringarna, har en hÄrddisk som kraschar, förlorar vi transaktionen, vilket kan leda till mycket otrevliga konsekvenser.

Halvsynkron (semisync) replikering

Äntligen har vi kommit till den semisynkrona replikeringen. Denna typ av replikering Ă€r inte sĂ€rskilt kĂ€nd och spridd, men den Ă€r intressant eftersom den kan förena fördelarna med bĂ„de synkron och asynkron replikering.

LÄt oss försöka kombinera de tvÄ tidigare tillvÀgagÄngssÀtten. Vi ska inte hÄlla kunden för lÀnge, men krÀva att data replikeras:

  1. Skriva transaktionen till databasens logg.
  2. TillÀmpa transaktionen i databasens motor.
  3. Skickar data till repliker.
  4. Mottar bekrÀftelse frÄn repliken om att Àndringar har tagits emot (de kommer att tillÀmpas "nÄgon gÄng senare").
  5. Skicka bekrÀftelse till klienten om att transaktionen har tillÀmpats framgÄngsrikt.

Observera att med denna algoritm kan transaktionsförlust endast intrÀffa om bÄde noden som tar emot Àndringar och repliknodens fallerar. Sannolikheten för en sÄdan störning bedöms som lÄg, och dessa risker accepteras.

Men med detta tillvÀgagÄngssÀtt finns det en risk för spöklÀsningar. TÀnk er följande scenario: vid steg 4 fick vi ingen bekrÀftelse frÄn nÄgon replik. Vi mÄste Äterkalla 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 Äterkallandet av transaktionen, under vilket parallella transaktioner kan se de förÀndringar som inte borde finnas i databasen.

Förlustfri semisynkron replikering

Om man tÀnker lite kan man bara byta steg i algoritmen för att ÄtgÀrda problemet med spöklÀsningar i detta scenario:

  1. Skriva transaktionen till databasens logg.
  2. Skickar data till repliken.
  3. Mottar bekrÀftelse frÄn repliken om att Àndringar har tagits emot (de kommer att tillÀmpas "nÄgon gÄng senare").
  4. TillÀmpa transaktionen i databasens motor.
  5. Skicka bekrÀftelse till klienten om att transaktionen har tillÀmpats framgÄngsrikt.

Nu kommittar vi Àndringar endast om de har replikeras.

Utdata

Som alltid finns det inga perfekta lösningar, det finns en uppsÀttning lösningar, var och en med sina egna fördelar och nackdelar och som passar för olika klassers uppgifter. Detta gÀller Àven valet av mekanism för datasykronisering av den replikerade databasen. UppsÀttningen fördelar som semisynkron replikering har Àr tillrÀckligt solid och intressant för att den ska erkÀnnas som vÀrd uppmÀrksamhet, trots att den Àr lite spridd.

Det Àr allt för nu. Vi ses pÄ kurs!

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster