En guide til transaktioner på tværs af mikrotjenester

1. Introduktion

I denne artikel vil vi diskutere muligheder for at implementere en transaktion på tværs af mikrotjenester.

Vi tjekker også nogle alternativer til transaktioner i et distribueret mikroservicescenarie.

2. Undgå transaktioner på tværs af mikrotjenester

En distribueret transaktion er en meget kompleks proces med mange bevægelige dele, der kan mislykkes. Også, hvis disse dele kører på forskellige maskiner eller endda i forskellige datacentre, kan processen med at begå en transaktion blive meget lang og upålidelig.

Dette kan alvorligt påvirke brugeroplevelsen og den samlede systembåndbredde. Så en af ​​de bedste måder at løse problemet med distribuerede transaktioner på er at undgå dem fuldstændigt.

2.1. Eksempel på arkitektur, der kræver transaktioner

Normalt er en mikroservice designet på en sådan måde, at den er uafhængig og nyttig alene. Det skal være i stand til at løse nogle atomforretningsopgaver.

Hvis vi kunne opdele vores system i sådanne mikroservices, er der en god chance for, at vi slet ikke har brug for at gennemføre transaktioner mellem dem.

Lad os for eksempel overveje et system med udsendelsesmeddelelser mellem brugere.

Det bruger mikroservice ville være bekymret for brugerprofilen (oprettelse af en ny bruger, redigering af profildata osv.) med følgende underliggende domæneklasse:

@Entity offentlig klasse Bruger implementerer Serializable {@Id @GeneratedValue (strategi = GenerationType.AUTO) privat lang id; @ Grundlæggende privat strengnavn; @Basic privat streng efternavn; @ Grundlæggende privat Øjeblikkelig LastMessageTime; }

Det besked mikroservice ville være optaget af udsendelse. Det indkapsler enheden Besked og alt omkring det:

@Entity public class Meddelelse implementerer Serializable {@Id @GeneratedValue (strategi = GenerationType.AUTO) privat lang id; @Basic privat lang userId; @ Grundlæggende privat strengindhold; @ Grundlæggende privat Øjeblikkelig beskedTidsstempel; }

Hver mikroservice har sin egen database. Bemærk, at vi ikke henviser til enheden Bruger fra enheden Besked, da brugerklasserne ikke er tilgængelige fra besked mikroservice. Vi henviser kun til brugeren ved id.

Nu er det Bruger enhed indeholder lastMessageTime felt, fordi vi vil vise oplysningerne om den sidste brugeraktivitetstid i hendes profil.

Dog for at tilføje en ny besked til brugeren og opdatere hende lastMessageTime, bliver vi nu nødt til at gennemføre en transaktion på tværs af mikrotjenester.

2.2. Alternativ tilgang uden transaktioner

Vi kan ændre vores mikroservicearkitektur og fjerne feltet lastMessageTime fra Bruger enhed.

Derefter kunne vi vise denne gang i brugerprofilen ved at udstede en separat anmodning til meddelelsens mikroservice og finde det maksimale beskedTidsstempel værdi for alle meddelelser fra denne bruger.

Sandsynligvis, hvis den besked mikroservice er under høj belastning eller endda nede, kan vi ikke vise tidspunktet for brugerens sidste besked i hendes profil.

Men det kunne være mere acceptabelt end ikke at begå en distribueret transaktion for at gemme en besked, bare fordi brugerens mikroservice ikke reagerede i tide.

Der er selvfølgelig mere komplekse scenarier, når vi skal implementere en forretningsproces på tværs af flere mikrotjenester, og vi ønsker ikke at tillade inkonsistens mellem disse mikrotjenester.

3. To-faset forpligtelsesprotokol

To-fasetildelingsprotokol (eller 2PC) er en mekanisme til implementering af en transaktion på tværs af forskellige softwarekomponenter (flere databaser, meddelelseskøer osv.)

3.1. Arkitekturen i 2PC

En af de vigtige deltagere i en distribueret transaktion er transaktionskoordinatoren. Den distribuerede transaktion består af to trin:

  • Forbered fase - I løbet af denne fase forbereder alle deltagere i transaktionen sig til forpligtelse og underretter koordinatoren om, at de er klar til at gennemføre transaktionen
  • Forpligtelses- eller tilbageførselsfase - i løbet af denne fase udstedes enten en kommission eller en tilbagevendende kommando af transaktionskoordinatoren til alle deltagere

Problemet med 2PC er, at det er ret langsomt i forhold til tiden for drift af en enkelt mikroservice.

Koordinering af transaktionen mellem mikrotjenester, selvom de er på det samme netværk, kan virkelig bremse systemet, så denne tilgang bruges normalt ikke i et scenarie med høj belastning.

3.2. XA Standard

XA-standarden er en specifikation til at udføre 2PC-distribuerede transaktioner på tværs af de understøttende ressourcer. Enhver JTA-kompatibel applikationsserver (JBoss, GlassFish osv.) Understøtter den out-of-the-box.

Ressourcerne, der deltager i distribuerede transaktioner, kan for eksempel være to databaser med to forskellige mikrotjenester.

For at udnytte denne mekanisme skal ressourcerne imidlertid distribueres til en enkelt JTA-platform. Dette er ikke altid muligt for en mikroservicearkitektur.

3.3. REST-AT Standardudkast

En anden foreslået standard er REST-AT, som havde gennemgået en vis udvikling af RedHat, men stadig ikke kom ud af udkastfasen. Det understøttes dog af WildFly-applikationsserveren out-of-the-box.

Denne standard tillader anvendelse af applikationsserveren som en transaktionskoordinator med en specifik REST API til oprettelse og sammenføjning af de distribuerede transaktioner.

De RESTfulde webtjenester, der ønsker at deltage i tofasetransaktionen, skal også understøtte en bestemt REST API.

Desværre er vi stadig nødt til enten at distribuere disse ressourcer til en enkelt JTA-platform eller løse en ikke-triviel opgave med at skrive denne bro selv for at bygge en distribueret transaktion til lokale ressourcer inden for mikroservicen.

4. Eventuel konsistens og kompensation

En af de mest gennemførlige modeller for håndtering af konsistens på tværs af mikrotjenester er langtfra den endelige konsistens.

Denne model håndhæver ikke distribuerede ACID-transaktioner på tværs af mikrotjenester. I stedet foreslås det at bruge nogle mekanismer til at sikre, at systemet i sidste ende vil være konsistent på et eller andet tidspunkt i fremtiden.

4.1. En sag for eventuel sammenhæng

Antag for eksempel, at vi skal løse følgende opgave:

  • registrer en brugerprofil
  • foretage nogle automatiserede baggrundskontroller, at brugeren faktisk kan få adgang til systemet

Den anden opgave er f.eks. At sikre, at denne bruger af en eller anden grund ikke blev forbudt fra vores servere.

Men det kan tage tid, og vi vil gerne udtrække det til en separat mikroservice. Det ville ikke være rimeligt at lade brugeren vente så længe bare for at vide, at hun blev registreret med succes.

En måde at løse det på ville være med en meddelelsesdrevet tilgang inklusive kompensation. Lad os overveje følgende arkitektur:

  • det bruger mikroservice, der har til opgave at registrere en brugerprofil
  • det validering mikroservice, der har til opgave at foretage en baggrundskontrol
  • meddelelsesplatformen, der understøtter vedvarende køer

Meddelelsesplatformen kunne sikre, at de meddelelser, der sendes af mikrotjenesterne, er vedvarende. Derefter ville de blive leveret på et senere tidspunkt, hvis modtageren ikke var tilgængelig i øjeblikket

4.2. Glad scenarie

I denne arkitektur ville et lykkeligt scenario være:

  • det bruger mikroservice registrerer en bruger og gemmer oplysninger om hende i sin lokale database
  • det bruger mikroservice markerer denne bruger med et flag. Det kan betyde, at denne bruger endnu ikke er valideret og ikke har adgang til fuld systemfunktionalitet
  • en bekræftelse på registrering sendes til brugeren med en advarsel om, at ikke alle funktionaliteter i systemet er tilgængelige med det samme
  • det bruger microservice sender en besked til validering mikroservice for at udføre en brugers baggrundskontrol
  • det validering microservice kører baggrundskontrollen og sender en besked til bruger mikroservice med resultaterne af kontrollen
    • hvis resultaterne er positive, bruger mikroservice fjerner blokeringen af ​​brugeren
    • hvis resultaterne er negative, bruger mikroservice sletter brugerkontoen

Når vi har gennemgået alle disse trin, skal systemet være i en konsistent tilstand. I et stykke tid syntes brugerenheden imidlertid at være i en ufuldstændig tilstand.

Det sidste trin, når brugerens mikroservice fjerner den ugyldige konto, er en kompensationsfase.

4.3. Fejlscenarier

Lad os nu overveje nogle fejlscenarier:

  • hvis den validering microservice ikke er tilgængelig, så meddelelsesplatformen med dens vedvarende køfunktionalitet sikrer, at validering microservice ville modtage denne besked på et senere tidspunkt
  • Antag, at meddelelsesplatformen mislykkes, så bruger mikroservice forsøger at sende beskeden igen på et senere tidspunkt, for eksempel ved planlagt batchbehandling af alle brugere, der endnu ikke er valideret
  • hvis den validering microservice modtager beskeden, validerer brugeren, men kan ikke sende svaret tilbage på grund af fejlmeddelelsen på messaging platformen validering microservice prøver også at sende beskeden på et senere tidspunkt
  • hvis en af ​​meddelelserne gik tabt, eller der opstod en anden fejl, blev bruger microservice finder alle ikke-validerede brugere ved planlagt batchbehandling og sender anmodninger om validering igen

Selvom nogle af meddelelserne blev udstedt flere gange, ville det ikke påvirke konsistensen af ​​dataene i mikroservices databaser.

Ved nøje at overveje alle mulige fejlscenarier kan vi sikre, at vores system opfylder betingelserne for eventuel konsistens. Samtidig behøver vi ikke håndtere de dyre distribuerede transaktioner.

Men vi skal være opmærksomme på, at det at sikre en eventuel konsistens er en kompleks opgave. Det har ikke en enkelt løsning til alle sager.

5. Konklusion

I denne artikel har vi diskuteret nogle af mekanismerne til implementering af transaktioner på tværs af mikrotjenester.

Og vi har også undersøgt nogle alternativer til at gøre denne type transaktioner i første omgang.


$config[zx-auto] not found$config[zx-overlay] not found