Hvorfor kan halv-synkron replikation være nødvendig?

Hej alle sammen. Det er Владислав Родин, der taler. I øjeblikket underviser jeg på OTUS-portalen i kurser om softwarearkitektur og arkitektur til højbelastede systemer. Forud for starten af det nye hold af kurset «Arkitekt for høj belastning» har jeg besluttet at skrive lidt originalt materiale, som jeg gerne vil dele med jer.

Hvorfor kan halv-synkron replikation være nødvendig?

Introduktion

Da HDD kun kan udføre cirka 400-700 operationer pr. sekund (hvilket ikke kan sammenlignes med de typiske rps'er, der er relateret til et højbelastet system), er den klassiske diskdatabase et flaskehals i arkitekturen. Derfor er det nødvendigt at lægge særlig vægt på skaleringsmønstre for dette lager.

I øjeblikket findes der to skaleringsmønstre for databasen: replikering og sharding. Sharding gør det muligt at skalere skriveoperationer og dermed reducere rps for skrivning til én server i dit cluster. Replikering gør det samme for læseoperationer. Denne artikel vil specielt fokusere på dette mønster.

Replikering

Hvis man ser på replikering på et meget overfladisk niveau, er det simpelt: du havde én server, hvor dataene lå, og så kunne denne server ikke længere håndtere belastningen af at læse disse data. Du tilføjer et par servere, synkroniserer dataene på alle servere, og brugeren kan læse fra hvilken som helst server i dit cluster.

På trods af den tilsyneladende enkelhed findes der flere måder at klassificere forskellige implementeringer af denne ordning:

  • Efter roller i clusteret (master-master eller master-slave)
  • Efter de sendte objekter (row-based, statement-based eller mixed)
  • Efter mekanismen til synkronisering af noder

I dag vil vi specifikt gennemgå det tredje punkt.

Hvordan transaktionen kommitteres

Dette emne relaterer sig ikke direkte til replikering, og der kan skrives en separat artikel om det, men da det er nytteløst at læse videre uden at forstå mekanismen for kommittering af transaktionen, tillader jeg mig at minde om de mest grundlæggende ting. Kommittering af transaktionen sker i tre trin:

  1. Skrivning af transaktionen i databasens log.
  2. Anvendelse af transaktionen i database-motoren.
  3. Tilbagelevering af bekræftelse til klienten om den vellykkede anvendelse af transaktionen.

I forskellige databaser i denne algoritme kan der opstå nuancer: For eksempel har InnoDB-motoren i MySQL to logfiler: en til replikering (binary log) og en anden til at opretholde ACID (undo/redo log), mens PostgreSQL har én logfil, der udfører begge funktioner (write ahead log = WAL). Men ovenfor er netop den generelle koncept, der gør det muligt at tilsidesætte sådanne nuancer.

Synchron replikering (sync)

Lad os tilføje logik til replikering af de modtagne ændringer i transaktionscommitalgoritmen:

  1. Skrivning af transaktionen i databasens log.
  2. Anvendelse af transaktionen i database-motoren.
  3. Afsendelse af data til alle replikaer.
  4. Modtagelse af bekræftelse fra alle replikaer om, at transaktionen er udført på dem.
  5. Tilbagelevering af bekræftelse til klienten om den vellykkede anvendelse af transaktionen.

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

  • Klienten venter på at anvende ændringer på alle replikaer.
  • Med et stigende antal noder i klyngen mindsker vi sandsynligheden for, at skriveoperationen lykkes.

Hvis det første punkt er relativt klart, kræver årsagerne til det andet punkt en forklaring. Hvis vi ved synkron replikering ikke får svar fra mindst én node, tilbagefører vi transaktionen. Dermed, ved at øge antallet af noder i klyngen, øger du sandsynligheden for, at skriveoperationen fejler.

Kan vi vente på bekræftelse kun fra en vis andel af noderne, for eksempel 51% (kvorum)? Ja, det kan vi, men i den klassiske version kræves bekræftelse fra alle noder, da det er sådan, vi kan sikre fuld konsistens af data i klyngen, hvilket er en uomtvistelig fordel ved denne form for replikering.

Asynkron replikering (async)

Lad os ændre den tidligere algoritme. Data til replikaer vil blive sendt "en gang senere", og "en gang senere" vil ændringerne blive anvendt på replikaerne:

  1. Skrivning af transaktionen i databasens log.
  2. Anvendelse af transaktionen i database-motoren.
  3. Tilbagelevering af bekræftelse til klienten om den vellykkede anvendelse af transaktionen.
  4. Afsendelse af data til replikaer og anvendelse af ændringer af dem.

Denne tilgang fører til, at klyngen kører hurtigt, da vi ikke holder klienten ventende, mens data når frem til replikaerne og bliver committet.

Men betingelsen for at sende data til replikaer "en gang senere" kan føre til tab af transaktioner, specifikt tab af bekræftede brugertransaktioner, for hvis data ikke når at blive replikeret, mens bekræftelsen til kunden om, at operationen er gennemført, er blevet sendt, og den node, der modtog ændringerne, er gået ned, mister vi transaktionen, hvilket kan føre til meget ubehagelige konsekvenser.

Halv-synkron replikering (semisync)

Endelig har vi nå nået til den halv-synkrone replikation. Denne type replikation er ikke særlig kendt og ikke særlig udbredt, men den er interessant, da den kan kombinere fordelene ved både synkron og asynkron replikation.

Lad os forsøge at sammenflette de to tidligere tilgange. Vi vil ikke holde kunden for længe, men kræve, at dataene replikeres:

  1. Skrivning af transaktionen i databasens log.
  2. Anvendelse af transaktionen i database-motoren.
  3. Send data til replikaer.
  4. Modtag bekræftelse fra replikaen om modtagelse af ændringerne (de vil blive anvendt "på et senere tidspunkt").
  5. Tilbagelevering af bekræftelse til klienten om den vellykkede anvendelse af transaktionen.

Bemærk, at ved denne algoritme vil tab af transaktioner kun ske i tilfælde af, at både noden, der modtager ændringerne, og replika-noden falder. Sandsynligheden for en sådan fejl anses for lav, og disse risici accepteres.

Men med denne tilgang er der risiko for phantom- læsninger. Forestil dig følgende scenarie: i trin 4 får vi ikke bekræftelse fra nogen replika. Vi skal tilbageføre denne transaktion og ikke returnere en bekræftelse til kunden. Da dataene blev anvendt i trin 2, opstår der et tidsinterval mellem afslutningen af trin 2 og tilbageførselen af transaktionen, hvor parallelle transaktioner kan se de ændringer, som ikke burde være i databasen.

Lose-less semisync replikation

Hvis man tænker lidt, kan man blot bytte om på trinnene i algoritmen for at løse problemet med phantom-læsninger i dette scenarie:

  1. Skrivning af transaktionen i databasens log.
  2. Send data til replikaen.
  3. Modtag bekræftelse fra replikaen om modtagelse af ændringerne (de vil blive anvendt "på et senere tidspunkt").
  4. Anvendelse af transaktionen i database-motoren.
  5. Tilbagelevering af bekræftelse til klienten om den vellykkede anvendelse af transaktionen.

Nu begynder vi kun at bekræfte ændringer, hvis de er blevet replikeret.

Output

Som altid findes der ikke perfekte løsninger, der findes kun et sæt løsninger, som hver især har deres fordele og ulemper og passer til forskellige klasser af opgaver. Dette gælder også for valget af synkroniseringsmekanismen for en replikeret database. Sættet af fordele, som halv-synkron replikation har, er ganske solidt og interessant nok til, at det kan betragtes som værdifuldt, på trods af dets lave udbredelse.

Det var alt. Vi ses på kursus!

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster