Een gids voor transacties in microservices

1. Inleiding

In dit artikel bespreken we opties om een ​​transactie tussen microservices te implementeren.

We zullen ook enkele alternatieven voor transacties bekijken in een gedistribueerd microservicescenario.

2. Transacties tussen microservices vermijden

Een gedistribueerde transactie is een zeer complex proces met veel bewegende onderdelen die kunnen mislukken. Als deze onderdelen op verschillende machines of zelfs in verschillende datacenters draaien, kan het proces van het plegen van een transactie erg lang en onbetrouwbaar worden.

Dit kan de gebruikerservaring en de algehele systeembandbreedte ernstig beïnvloeden. Zo een van de beste manieren om het probleem van gedistribueerde transacties op te lossen, is door ze volledig te vermijden.

2.1. Voorbeeld van een architectuur die transacties vereist

Gewoonlijk is een microservice zo ontworpen dat deze zelfstandig en bruikbaar is. Het zou een atomaire zakelijke taak moeten kunnen oplossen.

Als we ons systeem in dergelijke microservices zouden kunnen splitsen, is de kans groot dat we helemaal geen transacties tussen hen hoeven te implementeren.

Laten we bijvoorbeeld eens kijken naar een systeem voor het verzenden van berichten tussen gebruikers.

De gebruiker microservice zou zich bezighouden met het gebruikersprofiel (een nieuwe gebruiker aanmaken, profielgegevens bewerken enz.) met de volgende onderliggende domeinklasse:

@Entity public class Gebruiker implementeert Serializable {@Id @GeneratedValue (strategy = GenerationType.AUTO) privé lang ID; @Basic private String naam; @Basic private String achternaam; @Basic privé Instant lastMessageTime; }

De bericht microservice zou zich bezighouden met omroep. Het kapselt de entiteit in Bericht en alles eromheen:

@Entity public class Bericht implementeert Serializable {@Id @GeneratedValue (strategy = GenerationType.AUTO) privé lang ID; @Basic privé lange gebruikers-ID; @Basic private String-inhoud; @Basic privé Instant messageTimestamp; }

Elke microservice heeft zijn eigen database. Merk op dat we niet naar de entiteit verwijzen Gebruiker van de entiteit Bericht, aangezien de gebruikersklassen niet toegankelijk zijn vanaf de bericht microservice. We verwijzen alleen naar de gebruiker met id.

Nu de Gebruiker entiteit bevat de lastMessageTime veld omdat we de informatie over de tijd van de laatste gebruikersactiviteit in haar profiel willen tonen.

Om echter een nieuw bericht aan de gebruiker toe te voegen en haar bij te werken lastMessageTime, zouden we nu een transactie tussen microservices moeten implementeren.

2.2. Alternatieve aanpak zonder transacties

We kunnen onze microservice-architectuur aanpassen en het veld verwijderen lastMessageTime van de Gebruiker entiteit.

Vervolgens kunnen we deze tijd weergeven in het gebruikersprofiel door een afzonderlijk verzoek te sturen naar de berichtenmicroservice en het maximum te vinden berichtTimestamp waarde voor alle berichten van deze gebruiker.

Waarschijnlijk, als het bericht microservice staat onder hoge belasting of zelfs niet, we kunnen de tijd van het laatste bericht van de gebruiker in haar profiel niet weergeven.

Maar dat zou acceptabeler kunnen zijn dan het niet plegen van een gedistribueerde transactie om een ​​bericht op te slaan alleen omdat de gebruikersmicroservice niet op tijd reageerde.

Er zijn natuurlijk complexere scenario's wanneer we een bedrijfsproces over meerdere microservices moeten implementeren, en we willen geen inconsistentie tussen die microservices toestaan.

3. Tweefasig vastleggingsprotocol

Tweefasig commit-protocol (of 2PC) is een mechanisme voor het implementeren van een transactie tussen verschillende softwarecomponenten (meerdere databases, berichtenwachtrijen enz.)

3.1. De architectuur van 2PC

Een van de belangrijke deelnemers aan een gedistribueerde transactie is de transactiecoördinator. De gedistribueerde transactie bestaat uit twee stappen:

  • Voorbereidingsfase - tijdens deze fase bereiden alle deelnemers aan de transactie zich voor op vastlegging en stellen de coördinator op de hoogte dat ze klaar zijn om de transactie te voltooien
  • Commit- of Rollback-fase - tijdens deze fase wordt door de transactiecoördinator een commando voor vastleggen of terugdraaien aan alle deelnemers gegeven

Het probleem met 2PC is dat het vrij traag is in vergelijking met de tijd die nodig is voor een enkele microservice.

Het coördineren van de transactie tussen microservices, zelfs als ze zich op hetzelfde netwerk bevinden, kan het systeem echt vertragen, dus deze benadering wordt meestal niet gebruikt in een scenario met hoge belasting.

3.2. XA-standaard

De XA-standaard is een specificatie voor het uitvoeren van de 2PC-gedistribueerde transacties over de ondersteunende bronnen. Elke JTA-compatibele applicatieserver (JBoss, GlassFish etc.) ondersteunt het out-of-the-box.

De bronnen die deelnemen aan gedistribueerde transacties zouden bijvoorbeeld twee databases van twee verschillende microservices kunnen zijn.

Om echter van dit mechanisme te profiteren, moeten de middelen worden ingezet op één JTA-platform. Dit is niet altijd haalbaar voor een microservice-architectuur.

3.3. REST-AT Standard Draft

Een andere voorgestelde standaard is REST-AT die enige ontwikkeling had ondergaan door RedHat maar nog steeds niet uit de conceptfase kwam. Het wordt echter standaard ondersteund door de WildFly-applicatieserver.

Deze standaard maakt het mogelijk om de applicatieserver te gebruiken als een transactiecoördinator met een specifieke REST API voor het maken van en deelnemen aan de gedistribueerde transacties.

De RESTful-webservices die willen deelnemen aan de transactie in twee fasen, moeten ook een specifieke REST-API ondersteunen.

Om een ​​gedistribueerde transactie naar lokale bronnen van de microservice te overbruggen, moeten we helaas deze bronnen op één JTA-platform inzetten of een niet-triviale taak oplossen door deze brug zelf te schrijven.

4. Eventuele consistentie en compensatie

Een van de meest haalbare modellen voor het omgaan met consistentie tussen microservices is de uiteindelijke consistentie.

Dit model dwingt geen gedistribueerde ACID-transacties af over microservices. In plaats daarvan stelt het voor om een ​​aantal mechanismen te gebruiken om ervoor te zorgen dat het systeem op een bepaald moment in de toekomst uiteindelijk consistent zal zijn.

4.1. Een zaak voor uiteindelijke consistentie

Stel dat we de volgende taak moeten oplossen:

  • registreer een gebruikersprofiel
  • voer een geautomatiseerde achtergrondcontrole uit om te zien of de gebruiker daadwerkelijk toegang heeft tot het systeem

De tweede taak is er bijvoorbeeld voor te zorgen dat deze gebruiker om de een of andere reden niet van onze servers is verbannen.

Maar het kan even duren, en we willen het extraheren naar een aparte microservice. Het zou niet redelijk zijn om de gebruiker zo lang te laten wachten om te weten dat ze met succes is geregistreerd.

Een manier om het op te lossen is een berichtgestuurde aanpak inclusief compensatie. Laten we eens kijken naar de volgende architectuur:

  • de gebruiker microservice belast met het registreren van een gebruikersprofiel
  • de validatie microservice belast met het uitvoeren van een achtergrondcontrole
  • het berichtenplatform dat permanente wachtrijen ondersteunt

Het berichtenplatform zou ervoor kunnen zorgen dat de berichten die door de microservices worden verzonden, behouden blijven. Dan zouden ze op een later tijdstip worden afgeleverd als de ontvanger op dat moment niet beschikbaar was

4.2. Gelukkig scenario

In deze architectuur zou een gelukkig scenario zijn:

  • de gebruiker microservice registreert een gebruiker en slaat informatie over haar op in zijn lokale database
  • de gebruiker microservice markeert deze gebruiker met een vlag. Het kan betekenen dat deze gebruiker nog niet is gevalideerd en geen toegang heeft tot de volledige systeemfunctionaliteit
  • een bevestiging van registratie wordt naar de gebruiker gestuurd met de waarschuwing dat niet alle functionaliteit van het systeem direct toegankelijk is
  • de gebruiker microservice stuurt een bericht naar de validatie microservice om de achtergrondcontrole van een gebruiker uit te voeren
  • de validatie microservice voert de achtergrondcontrole uit en stuurt een bericht naar het gebruiker microservice met de resultaten van de controle
    • als de resultaten positief zijn, de gebruiker microservice deblokkeert de gebruiker
    • als de resultaten negatief zijn, de gebruiker microservice verwijdert het gebruikersaccount

Nadat we al deze stappen hebben doorlopen, zou het systeem in een consistente staat moeten zijn. De gebruikersorganisatie leek echter enige tijd in een onvolledige staat te verkeren.

De laatste stap, wanneer de gebruikersmicroservice het ongeldige account verwijdert, is een compensatiefase.

4.3. Storingsscenario's

Laten we nu eens kijken naar enkele foutscenario's:

  • als het validatie microservice niet toegankelijk is, dan zorgt het berichtenplatform met zijn persistente wachtrijfunctionaliteit ervoor dat het validatie microservice zou dit bericht op een later tijdstip ontvangen
  • stel dat het berichtenplatform faalt, dan is het gebruiker microservice probeert het bericht op een later tijdstip opnieuw te verzenden, bijvoorbeeld door geplande batchverwerking van alle gebruikers die nog niet zijn gevalideerd
  • als het validatie microservice ontvangt het bericht, valideert de gebruiker, maar kan het antwoord niet terugsturen vanwege een storing in het berichtenplatform, de validatie microservice probeert het bericht ook op een later tijdstip opnieuw te verzenden
  • als een van de berichten verloren is gegaan of als er een andere fout is opgetreden, wordt het gebruiker microservice vindt alle niet-gevalideerde gebruikers door geplande batchverwerking en verzendt verzoeken om validatie opnieuw

Zelfs als sommige berichten meerdere keren zijn verzonden, heeft dit geen invloed op de consistentie van de gegevens in de databases van de microservices.

Door alle mogelijke storingsscenario's zorgvuldig te overwegen, kunnen we ervoor zorgen dat ons systeem voldoet aan de voorwaarden van uiteindelijke consistentie. Tegelijkertijd hoeven we niet om te gaan met de dure gedistribueerde transacties.

Maar we moeten ons ervan bewust zijn dat het zorgen voor uiteindelijke consistentie een complexe taak is. Het heeft niet voor alle gevallen een enkele oplossing.

5. Conclusie

In dit artikel hebben we enkele mechanismen besproken voor het implementeren van transacties tussen microservices.

En we hebben ook enkele alternatieven onderzocht om in de eerste plaats deze stijl van transacties uit te voeren.


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