En guide til transaksjoner på tvers av mikrotjenester

1. Introduksjon

I denne artikkelen vil vi diskutere alternativer for å implementere en transaksjon på tvers av mikrotjenester.

Vi vil også sjekke ut noen alternativer til transaksjoner i et distribuert mikroservice-scenario.

2. Unngå transaksjoner på tvers av mikrotjenester

En distribuert transaksjon er en veldig kompleks prosess med mange bevegelige deler som kan mislykkes. Også, hvis disse delene kjører på forskjellige maskiner eller til og med i forskjellige datasentre, kan prosessen med å utføre en transaksjon bli veldig lang og upålitelig.

Dette kan alvorlig påvirke brukeropplevelsen og den samlede systembåndbredden. Så en av de beste måtene å løse problemet med distribuerte transaksjoner er å unngå dem helt.

2.1. Eksempel på arkitektur som krever transaksjoner

Vanligvis er en mikrotjeneste utformet på en slik måte at den er uavhengig og nyttig alene. Det skal være i stand til å løse noen atomoppgaver.

Hvis vi kunne dele systemet vårt i slike mikrotjenester, er det en god sjanse for at vi ikke trenger å implementere transaksjoner mellom dem i det hele tatt.

La oss for eksempel vurdere et system for kringkasting av meldinger mellom brukere.

De bruker microservice vil være opptatt av brukerprofilen (opprette en ny bruker, redigere profildata etc.) med følgende underliggende domeneklasse:

@Entity offentlig klasse Bruker implementerer Serializable {@Id @GeneratedValue (strategi = GenerationType.AUTO) privat lang id; @Grunnleggende privat strengnavn; @Basic private String etternavn; @Basic private Instant lastMessageTime; }

De beskjed mikroservice ville være opptatt av kringkasting. Den innkapsler enheten Beskjed og alt rundt det:

@Entity public class Melding implementerer Serializable {@Id @GeneratedValue (strategi = GenerationType.AUTO) privat lang id; @Basic privat lang userId; @Grunnleggende privat strenginnhold; @Basic private DirektemeldingTidsstempel; }

Hver mikroservice har sin egen database. Legg merke til at vi ikke refererer til enheten Bruker fra enheten Beskjed, ettersom brukerklassene ikke er tilgjengelige fra beskjed mikroservice. Vi refererer til brukeren bare etter id.

Nå er det Bruker enhet inneholder lastMessageTime felt fordi vi vil vise informasjonen om siste brukeraktivitetstid i profilen hennes.

Imidlertid for å legge til en ny melding til brukeren og oppdatere henne lastMessageTime, må vi nå implementere en transaksjon på tvers av mikrotjenester.

2.2. Alternativ tilnærming uten transaksjoner

Vi kan endre vår mikrotjenestearkitektur og fjerne feltet lastMessageTime fra Bruker enhet.

Da kunne vi vise denne gangen i brukerprofilen ved å utstede en egen forespørsel til meldingsmikrotjenesten og finne det maksimale meldingTidsstempel verdi for alle meldinger fra denne brukeren.

Sannsynligvis hvis beskjed microservice er under høy belastning eller til og med nede, kan vi ikke vise tidspunktet for den siste meldingen til brukeren i profilen hennes.

Men det kan være mer akseptabelt enn å unnlate å distribuere en distribuert transaksjon for å lagre en melding bare fordi brukerens mikrotjeneste ikke svarte i tide.

Det er selvfølgelig mer komplekse scenarier når vi må implementere en forretningsprosess på tvers av flere mikrotjenester, og vi ønsker ikke å tillate inkonsistens mellom disse mikrotjenestene.

3. To-fase forpliktelsesprotokoll

To-fase forpliktelsesprotokoll (eller 2PC) er en mekanisme for å implementere en transaksjon på tvers av forskjellige programvarekomponenter (flere databaser, meldingskøer etc.)

3.1. Arkitekturen til 2PC

En av de viktige deltakerne i en distribuert transaksjon er transaksjonskoordinatoren. Den distribuerte transaksjonen består av to trinn:

  • Forbered fase - I løpet av denne fasen forbereder alle deltakerne i transaksjonen seg for å forplikte seg og varsle koordinatoren om at de er klare til å fullføre transaksjonen
  • Forpliktelses- eller tilbakeføringsfase - i løpet av denne fasen utstedes enten en kommisjon eller en tilbakeføringskommando av transaksjonskoordinatoren til alle deltakerne

Problemet med 2PC er at det er ganske sakte sammenlignet med tiden for drift av en enkelt mikroservice.

Koordinering av transaksjonen mellom mikrotjenester, selv om de er i samme nettverk, kan virkelig redusere systemet, så denne tilnærmingen brukes vanligvis ikke i et scenario med høy belastning.

3.2. XA Standard

XA-standarden er en spesifikasjon for å gjennomføre 2PC-distribuerte transaksjoner på tvers av støttende ressurser. Enhver JTA-kompatibel applikasjonsserver (JBoss, GlassFish osv.) Støtter den out of the-box.

Ressursene som deltar i distribuerte transaksjoner kan for eksempel være to databaser med to forskjellige mikrotjenester.

For å dra nytte av denne mekanismen må ressursene imidlertid distribueres til en enkelt JTA-plattform. Dette er ikke alltid mulig for en mikrotjenestearkitektur.

3.3. REST-AT Standardutkast

En annen foreslått standard er REST-AT som hadde gjennomgått en del utvikling av RedHat, men likevel ikke kom seg ut av utkaststadiet. Den støttes imidlertid av WildFly-applikasjonsserveren utenom boksen.

Denne standarden tillater bruk av applikasjonsserveren som en transaksjonskoordinator med et spesifikt REST API for å opprette og bli med på de distribuerte transaksjonene.

De RESTful-webtjenestene som ønsker å delta i to-fase transaksjonen, må også støtte et spesifikt REST API.

Dessverre, for å bygge bro over en distribuert transaksjon til lokale ressurser fra mikrotjenesten, må vi fortsatt enten distribuere disse ressursene til en enkelt JTA-plattform eller løse en ikke-triviell oppgave med å skrive denne broen selv.

4. Eventuell konsistens og kompensasjon

Langt, en av de mest gjennomførbare modellene for håndtering av konsistens på tvers av mikrotjenester er eventuell konsistens.

Denne modellen håndhever ikke distribuerte ACID-transaksjoner på tvers av mikrotjenester. I stedet foreslår den å bruke noen mekanismer for å sikre at systemet til slutt vil være konsistent på et tidspunkt i fremtiden.

4.1. En sak for eventuell konsistens

Anta for eksempel at vi må løse følgende oppgave:

  • registrere en brukerprofil
  • gjøre noen automatiserte bakgrunnssjekker at brukeren faktisk kan få tilgang til systemet

Den andre oppgaven er å sikre, for eksempel at denne brukeren ikke ble utestengt fra serverne våre av en eller annen grunn.

Men det kan ta tid, og vi vil gjerne trekke det ut til en egen mikroservice. Det ville ikke være rimelig å la brukeren vente så lenge bare for å vite at hun ble registrert.

En måte å løse det på ville være med en meldingsdrevet tilnærming inkludert kompensasjon. La oss vurdere følgende arkitektur:

  • de bruker mikroservice som har til oppgave å registrere en brukerprofil
  • de validering mikroservice som har til oppgave å gjøre en bakgrunnssjekk
  • meldingsplattformen som støtter vedvarende køer

Meldingsplattformen kan sikre at meldingene som sendes av mikrotjenestene vedvarer. Så ville de bli levert på et senere tidspunkt hvis mottakeren ikke var tilgjengelig for øyeblikket

4.2. Lykkelig scenario

I denne arkitekturen vil et lykkelig scenario være:

  • de bruker microservice registrerer en bruker og lagrer informasjon om henne i sin lokale database
  • de bruker mikroservice markerer denne brukeren med et flagg. Det kan bety at denne brukeren ennå ikke er validert og ikke har tilgang til full systemfunksjonalitet
  • en bekreftelse på registrering blir sendt til brukeren med en advarsel om at ikke all funksjonalitet i systemet er tilgjengelig med en gang
  • de bruker microservice sender en melding til validering mikroservice for å gjøre bakgrunnssjekken til en bruker
  • de validering microservice kjører bakgrunnssjekken og sender en melding til bruker mikroservice med resultatene av sjekken
    • hvis resultatene er positive, bruker mikroservice blokkerer brukeren
    • hvis resultatene er negative, vil bruker microservice sletter brukerkontoen

Etter at vi har gått gjennom alle disse trinnene, bør systemet være i en konsistent tilstand. I en periode syntes brukerenheten å være i en ufullstendig tilstand.

Det siste trinnet, når brukerens mikrotjeneste fjerner den ugyldige kontoen, er en kompensasjonsfase.

4.3. Feilscenarier

La oss nå vurdere noen feilscenarier:

  • hvis validering microservice ikke er tilgjengelig, så sørger meldingsplattformen med sin vedvarende køfunksjonalitet for at validering microservice vil motta denne meldingen på et senere tidspunkt
  • antar at meldingsplattformen mislykkes, så bruker microservice prøver å sende meldingen igjen på et senere tidspunkt, for eksempel ved planlagt batchbehandling av alle brukere som ennå ikke var validert
  • hvis validering microservice mottar meldingen, validerer brukeren, men kan ikke sende svaret tilbake på grunn av feil på meldingsplattformen validering microservice prøver også å sende meldingen på et senere tidspunkt
  • hvis en av meldingene gikk tapt, eller det oppstod en annen feil, bruker microservice finner alle ikke-validerte brukere ved planlagt batch-behandling og sender forespørsler om validering igjen

Selv om noen av meldingene ble utstedt flere ganger, ville dette ikke påvirke konsistensen av dataene i mikrotjenestens databaser.

Ved nøye å vurdere alle mulige feilscenarier, kan vi sikre at systemet vårt tilfredsstiller vilkårene for eventuell konsistens. Samtidig trenger vi ikke håndtere de kostbare distribuerte transaksjonene.

Men vi må være klar over at det å sikre en eventuell konsistens er en kompleks oppgave. Det har ikke en eneste løsning for alle tilfeller.

5. Konklusjon

I denne artikkelen har vi diskutert noen av mekanismene for å implementere transaksjoner på tvers av mikrotjenester.

Og vi har også utforsket noen alternativer for å gjøre denne stilen av transaksjoner i utgangspunktet.


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