8.1 Transaksjons-ID-er

Det er utpekt som XID eller TxID (hvis det er en forskjell, fortell meg). Tidsstempler kan brukes som TxID, som kan spille i hendene hvis vi ønsker å gjenopprette alle handlinger til et tidspunkt. Problemet kan oppstå hvis tidsstemplet ikke er granulært nok – da kan transaksjoner få samme ID.

Derfor er det mest pålitelige alternativet å generere unike UUID-produkt-IDer. I Python er dette veldig enkelt:

>>> import uuid 
>>> str(uuid.uuid4()) 
'f50ec0b7-f960-400d-91f0-c42a6d44e3d0' 
>>> str(uuid.uuid4()) 
'd15bed89-c0a5-4a72-98d9-5507ea7bc0ba' 

Det er også et alternativ å hash et sett med transaksjonsdefinerende data og bruke denne hashen som TxID.

8.2 Forsøk på nytt

Hvis vi vet at en bestemt funksjon eller et bestemt program er idempotent, betyr dette at vi kan og bør prøve å gjenta kallet i tilfelle feil. Og vi må bare være forberedt på at noen operasjoner vil gi en feil - gitt at moderne applikasjoner er distribuert over nettverket og maskinvaren, bør feilen ikke betraktes som et unntak, men som normen. Feilen kan oppstå på grunn av serverkrasj, nettverksfeil, overbelastning av ekstern applikasjon. Hvordan skal søknaden vår oppføre seg? Det stemmer, prøv å gjenta operasjonen.

Siden ett stykke kode kan si mer enn en hel side med ord, la oss bruke ett eksempel for å forstå hvordan den naive gjenforsøksmekanismen ideelt sett bør fungere. Jeg skal demonstrere dette ved å bruke Tenacity-biblioteket (det er så godt designet at selv om du ikke planlegger å bruke det, bør eksemplet vise deg hvordan du kan designe gjentakelsesmekanismen):

import logging
import random
import sys
from tenacity import retry, stop_after_attempt, stop_after_delay, wait_exponential, retry_if_exception_type, before_log

logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
logger = logging.getLogger(__name__)

@retry(
	stop=(stop_after_delay(10) | stop_after_attempt(5)),
	wait=wait_exponential(multiplier=1, min=4, max=10),
	retry=retry_if_exception_type(IOError),
	before=before_log(logger, logging.DEBUG)
)
def do_something_unreliable():
	if random.randint(0, 10) > 1:
    	raise IOError("Broken sauce, everything is hosed!!!111one")
	else:
    	return "Awesome sauce!"

print(do_something_unreliable.retry.statistics)

> For sikkerhets skyld vil jeg si: \@retry(...) er en spesiell Python-syntaks som kalles en "dekorator". Det er bare en funksjon på nytt (...) som omslutter en annen funksjon og gjør noe før eller etter at den er utført.

Som vi kan se, kan gjenforsøk utformes kreativt:

  • Du kan begrense forsøk etter tid (10 sekunder) eller antall forsøk (5).
  • Kan være eksponentiell (det vil si 2 ** et økende antall n ). eller på en annen måte (for eksempel fikset) for å øke tiden mellom separate forsøk. Den eksponentielle varianten kalles «congestion collapse».
  • Du kan bare prøve på nytt for visse typer feil (IOError).
  • Forsøk på nytt kan innledes eller fullføres av noen spesielle oppføringer i loggen.

Nå som vi har fullført young fighter-kurset og kjenner de grunnleggende byggesteinene som vi trenger for å jobbe med transaksjoner på applikasjonssiden, la oss bli kjent med to metoder som lar oss implementere transaksjoner i distribuerte systemer.

8.3 Avanserte verktøy for transaksjonselskere

Jeg vil bare gi ganske generelle definisjoner, siden dette emnet er verdig en egen stor artikkel.

To-fase commit (2 stk) . 2pc har to faser: en forberedelsesfase og en forpliktelsesfase. Under forberedelsesfasen vil alle mikrotjenester bli bedt om å forberede seg på noen dataendringer som kan gjøres atomært. Når de alle er klare, vil commit-fasen gjøre de faktiske endringene. For å koordinere prosessen trengs en global koordinator, som låser de nødvendige objektene – det vil si at de blir utilgjengelige for endringer inntil koordinatoren låser dem opp. Hvis en bestemt mikrotjeneste ikke er klar for endringer (for eksempel ikke svarer), vil koordinatoren avbryte transaksjonen og starte tilbakeføringsprosessen.

Hvorfor er denne protokollen god? Det gir atomitet. I tillegg garanterer det isolasjon ved skriving og lesing. Dette betyr at endringer i en transaksjon ikke er synlige for andre før koordinatoren forplikter endringene. Men disse egenskapene har også en ulempe: siden denne protokollen er synkron (blokkering), bremser den systemet (til tross for at selve RPC-anropet er ganske tregt). Og igjen er det fare for gjensidig blokkering.

Saga . I dette mønsteret blir en distribuert transaksjon utført av asynkrone lokale transaksjoner på tvers av alle tilknyttede mikrotjenester. Mikrotjenester kommuniserer med hverandre via en eventbuss. Hvis en mikrotjeneste ikke klarer å fullføre sin lokale transaksjon, vil andre mikrotjenester utføre kompenserende transaksjoner for å rulle tilbake endringene.

Fordelen med Saga er at ingen objekter er blokkert. Men det er selvfølgelig ulemper.

Saga er vanskelig å feilsøke, spesielt når det er mange mikrotjenester involvert. En annen ulempe med Saga-mønsteret er at det mangler leseisolasjon. Det vil si at hvis egenskapene som er angitt i ACID er viktige for oss, så er ikke Saga særlig egnet for oss.

Hva ser vi av beskrivelsen av disse to teknikkene? Det faktum at i distribuerte systemer ligger ansvaret for atomitet og isolasjon hos applikasjonen. Det samme skjer ved bruk av databaser som ikke gir ACID-garantier. Det vil si at ting som konfliktløsning, tilbakeføringer, forpliktelser og frigjøring av plass faller på utviklerens skuldre.

8.4 Hvordan vet jeg når jeg trenger SYRE-garantier?

Når det er stor sannsynlighet for at et visst sett med brukere eller prosesser samtidig vil arbeide på samme data .

Beklager banaliteten, men et typisk eksempel er økonomiske transaksjoner.

Når rekkefølgen transaksjoner utføres i betyr noe.

Tenk deg at bedriften din er i ferd med å bytte fra FunnyYellowChat messenger til FunnyRedChat messenger, fordi FunnyRedChat lar deg sende gifs, men FunnyYellowChat kan ikke. Men du endrer ikke bare messenger - du migrerer bedriftens korrespondanse fra en messenger til en annen. Du gjør dette fordi programmererne dine var for late til å dokumentere programmer og prosesser et sted sentralt, og i stedet publiserte de alt i forskjellige kanaler i messenger. Ja, og selgerne dine publiserte detaljene om forhandlinger og avtaler på samme sted. Kort sagt, hele livet til bedriften din er der, og siden ingen har tid til å overføre det hele til en tjeneste for dokumentasjon, og søket etter direktemeldinger fungerer bra, bestemte du deg i stedet for å rydde ruinene for å kopiere alle meldinger til et nytt sted. Rekkefølgen på meldingene er viktig

For korrespondanse i en messenger er forresten rekkefølgen generelt viktig, men når to personer skriver noe i samme chat samtidig, er det generelt sett ikke så viktig hvem sin melding som vises først. Så, for dette spesielle scenariet, ville ACID ikke være nødvendig.

Et annet mulig eksempel er bioinformatikk. Jeg forstår ikke dette i det hele tatt, men jeg antar at rekkefølgen er viktig når man skal tyde det menneskelige genomet. Men jeg hørte at bioinformatikere generelt bruker noen av verktøyene deres til alt – kanskje de har egne databaser.

Når du ikke kan gi en bruker eller behandle foreldede data.

Og igjen - økonomiske transaksjoner. For å være ærlig, kunne jeg ikke tenke meg noe annet eksempel.

Når ventende transaksjoner er forbundet med betydelige kostnader. Se for deg problemene som kan oppstå når en lege og en sykepleier både oppdaterer en pasientjournal og sletter hverandres endringer samtidig, fordi databasen ikke kan isolere transaksjoner. Helsevesenet er et annet område, foruten finans, hvor ACID-garantier har en tendens til å være kritiske.

8.5 Når trenger jeg ikke ACID?

Når brukere bare oppdaterer noen av sine private data.

For eksempel legger en bruker kommentarer eller klistrelapper på en nettside. Eller redigerer personlige data på en personlig konto hos en leverandør av tjenester.

Når brukere ikke oppdaterer data i det hele tatt, men kun supplerer med nye (legg ved).

For eksempel en løpende applikasjon som lagrer data på løpeturene dine: hvor mye du løp, til hvilken tid, rute osv. Hver ny kjøring er nye data, og de gamle blir ikke redigert i det hele tatt. Kanskje, basert på dataene, får du analyser - og bare NoSQL-databaser er bra for dette scenariet.

Når forretningslogikk ikke bestemmer behovet for en bestemt rekkefølge som transaksjoner utføres i.

Sannsynligvis, for en Youtube-blogger som samler inn donasjoner til produksjon av nytt materiale under neste direktesending, er det ikke så viktig hvem, når og i hvilken rekkefølge som kastet penger til ham.

Når brukere blir værende på samme nettside eller programvindu i flere sekunder eller til og med minutter, og derfor vil de på en eller annen måte se foreldede data.

Teoretisk sett er dette alle nyhetsmedier på nett, eller samme Youtube. Eller "Habr". Når det ikke spiller noen rolle for deg at ufullstendige transaksjoner kan lagres midlertidig i systemet, kan du ignorere dem uten skade.

Hvis du samler data fra mange kilder, og data som oppdateres med høy frekvens - for eksempel data om belegget av parkeringsplasser i en by som endres minst hvert 5. minutt, så vil det i teorien ikke være et stort problem for deg hvis transaksjonen for en av parkeringsplassene på et tidspunkt ikke vil gå gjennom. Selv om det selvfølgelig avhenger av hva du vil gjøre med disse dataene.