Ein Leitfaden für Transaktionen mit Microservices

Ein Leitfaden für Transaktionen über Microservices hinweg

1. Einführung

In diesem Artikel werden Optionen zum Implementieren einer Transaktion über Microservices hinweg erläutert.

Wir werden auch einige Alternativen zu Transaktionen in einem verteilten Microservice-Szenario prüfen.

2. Vermeiden von Transaktionen über Microservices hinweg

Eine verteilte Transaktion ist ein sehr komplexer Prozess, bei dem viele bewegliche Teile ausfallen können. Wenn diese Teile auf verschiedenen Computern oder sogar in verschiedenen Rechenzentren ausgeführt werden, kann der Vorgang des Festschreibens einer Transaktion sehr lang und unzuverlässig werden.

Dies kann die Benutzererfahrung und die gesamte Systembandbreite erheblich beeinträchtigen. Alsoone of the best ways to solve the problem of distributed transactions is to avoid them completely.

2.1. Beispiel für eine Architektur, die Transaktionen erfordert

In der Regel ist ein Mikrodienst so konzipiert, dass er für sich unabhängig und nützlich ist. Es sollte in der Lage sein, eine atomare Geschäftsaufgabe zu lösen.

Wenn wir unser System in solche Mikrodienste aufteilen könnten, besteht eine gute Chance, dass wir überhaupt keine Transaktionen zwischen ihnen implementieren müssen.

Betrachten wir beispielsweise ein System für Broadcast-Nachrichten zwischen Benutzern.

Der Microservice vonuserbefasst sich mit dem Benutzerprofil (Erstellen eines neuen Benutzers, Bearbeiten von Profildaten usw.) mit der folgenden zugrunde liegenden Domänenklasse:

@Entity
public class User implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Basic
    private String name;

    @Basic
    private String surname;

    @Basic
    private Instant lastMessageTime;
}

Der Microservice vonmessagewürde sich mit Rundfunk befassen. Es kapselt die EntitätMessage und alles um sie herum:

@Entity
public class Message implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Basic
    private long userId;

    @Basic
    private String contents;

    @Basic
    private Instant messageTimestamp;

}

Jeder Microservice hat eine eigene Datenbank. Beachten Sie, dass wir nicht auf die EntitätUser aus der EntitätMessage verweisen, da auf die Benutzerklassen nicht über den Microservicemessagezugegriffen werden kann. Wir verweisen auf den Benutzer nur durch ID.

Jetzt enthält die EntitätUser das FeldlastMessageTime, da wir die Informationen zur letzten Benutzeraktivitätszeit in ihrem Profil anzeigen möchten.

Um dem Benutzer jedoch eine neue Nachricht hinzuzufügen und seinelastMessageTimezu aktualisieren, müssen wir jetzt eine Transaktion über Microservices hinweg implementieren.

2.2. Alternativer Ansatz ohne Transaktionen

Wir können unsere Microservice-Architektur ändern und das FeldlastMessageTime aus der EntitätUserentfernen.

Dann könnten wir diese Zeit im Benutzerprofil anzeigen, indem wir eine separate Anforderung an den Nachrichtenmikroservice senden und den maximalen Wert vonmessageTimestampfür alle Nachrichten dieses Benutzers ermitteln.

Wenn der Microservice vonmessageunter hoher Last oder sogar inaktiv ist, können wir wahrscheinlich nicht die Zeit der letzten Nachricht des Benutzers in seinem Profil anzeigen.

Dies könnte jedoch akzeptabler sein, als eine verteilte Transaktion zum Speichern einer Nachricht nicht festzuschreiben, nur weil der Benutzer-Microservice nicht rechtzeitig geantwortet hat.

Es gibt natürlich komplexere Szenarien, in denen wir einen Geschäftsprozess über mehrere Mikrodienste hinweg implementieren müssen, und wir möchten keine Inkonsistenz zwischen diesen Mikrodiensten zulassen.

3. Zwei-Phasen-Commit-Protokoll

Two-phase commit protocol (oder 2PC) ist ein Mechanismus zum Implementieren einer Transaktion über verschiedene Softwarekomponenten (mehrere Datenbanken, Nachrichtenwarteschlangen usw.).

3.1. Die Architektur von 2PC

Einer der wichtigen Teilnehmer an einer verteilten Transaktion ist der Transaktionskoordinator. Die verteilte Transaktion besteht aus zwei Schritten:

  • Vorbereitungsphase - Während dieser Phase bereiten sich alle Teilnehmer der Transaktion auf das Festschreiben vor und benachrichtigen den Koordinator, dass sie bereit sind, die Transaktion abzuschließen

  • Commit- oder Rollback-Phase - In dieser Phase wird vom Transaktionskoordinator ein Commit- oder ein Rollback-Befehl an alle Teilnehmer ausgegeben

Das Problem mit 2PC ist, dass es im Vergleich zur Betriebszeit eines einzelnen Mikrodienstes ziemlich langsam ist.

Coordinating the transaction between microservices, even if they are on the same network, can really slow the system down, daher wird dieser Ansatz in einem Szenario mit hoher Last normalerweise nicht verwendet.

3.2. XA Standard

XA standard ist eine Spezifikation für die Durchführung der verteilten 2PC-Transaktionen über die unterstützenden Ressourcen. Jeder JTA-kompatible Anwendungsserver (JBoss, GlassFish usw.) unterstützt ihn sofort.

Die an verteilten Transaktionen beteiligten Ressourcen können beispielsweise zwei Datenbanken von zwei verschiedenen Mikrodiensten sein.

Um diesen Mechanismus nutzen zu können, müssen die Ressourcen jedoch auf einer einzelnen JTA-Plattform bereitgestellt werden. Dies ist für eine Microservice-Architektur nicht immer möglich.

3.3. REST-AT Standardentwurf

Ein weiterer vorgeschlagener Standard istREST-AT, der von RedHat weiterentwickelt wurde, aber noch nicht aus der Entwurfsphase herausgekommen ist. Es wird jedoch sofort vom WildFly-Anwendungsserver unterstützt.

Dieser Standard ermöglicht die Verwendung des Anwendungsservers als Transaktionskoordinator mit einer bestimmten REST-API zum Erstellen und Verbinden der verteilten Transaktionen.

Die RESTful-Webdienste, die an der Zwei-Phasen-Transaktion teilnehmen möchten, müssen auch eine bestimmte REST-API unterstützen.

Um eine verteilte Transaktion mit lokalen Ressourcen des Mikrodienstes zu verbinden, müssen wir diese Ressourcen leider entweder auf einer einzelnen JTA-Plattform bereitstellen oder eine nicht triviale Aufgabe lösen, diese Brücke selbst zu schreiben.

4. Eventuelle Konsistenz und Kompensation

Bei weitem isteventual consistency eines der praktikabelsten Modelle für die Handhabung der Konsistenz über Mikrodienste hinweg.

Dieses Modell erzwingt keine verteilten ACID-Transaktionen über Microservices hinweg. Stattdessen werden einige Mechanismen vorgeschlagen, um sicherzustellen, dass das System irgendwann in der Zukunft konsistent sein wird.

4.1. Ein Argument für eventuelle Konsistenz

Angenommen, wir müssen die folgende Aufgabe lösen:

  • Registrieren Sie ein Benutzerprofil

  • Führen Sie eine automatische Hintergrundüberprüfung durch, um sicherzustellen, dass der Benutzer tatsächlich auf das System zugreifen kann

Die zweite Aufgabe besteht beispielsweise darin, sicherzustellen, dass dieser Benutzer aus irgendeinem Grund nicht von unseren Servern verbannt wurde.

Es kann jedoch einige Zeit dauern, und wir möchten es in einen separaten Microservice extrahieren. Es wäre nicht sinnvoll, die Benutzerin so lange warten zu lassen, nur um zu wissen, dass sie erfolgreich registriert wurde.

One way to solve it would be with a message-driven approach including compensation. Betrachten wir die folgende Architektur:

  • Der Mikrodienstuser, der mit der Registrierung eines Benutzerprofils beauftragt ist

  • Der Mikroservice vonvalidationhat die Aufgabe, eine Hintergrundprüfung durchzuführen

  • Die Messaging-Plattform, die permanente Warteschlangen unterstützt

Die Messaging-Plattform kann sicherstellen, dass die von den Microservices gesendeten Nachrichten bestehen bleiben. Dann würden sie zu einem späteren Zeitpunkt geliefert, wenn der Empfänger derzeit nicht verfügbar wäre

4.2. Glückliches Szenario

In dieser Architektur wäre ein glückliches Szenario:

  • Der Microserviceuserregistriert einen Benutzer und speichert Informationen über ihn in seiner lokalen Datenbank

  • Der Microserviceusermarkiert diesen Benutzer mit einem Flag. Dies könnte bedeuten, dass dieser Benutzer noch nicht validiert wurde und keinen Zugriff auf die gesamte Systemfunktionalität hat

  • Der Benutzer erhält eine Registrierungsbestätigung mit der Warnung, dass nicht alle Funktionen des Systems sofort verfügbar sind

  • Der Microserviceuserendet eine Nachricht an den Microservicevalidation, um die Hintergrundprüfung eines Benutzers durchzuführen

  • Der Microservicevalidationführt die Hintergrundprüfung durch und sendet eine Nachricht mit den Ergebnissen der Prüfung an den Microserviceuser

    • Wenn die Ergebnisse positiv sind, entsperrt der Mikroserviceuserden Benutzer

    • Wenn die Ergebnisse negativ sind, löscht der Microserviceuserdas Benutzerkonto

Nachdem wir alle diese Schritte ausgeführt haben, sollte sich das System in einem konsistenten Zustand befinden. Für einige Zeit schien sich die Benutzerentität jedoch in einem unvollständigen Zustand zu befinden.

The last step, when the user microservice removes the invalid account, is a compensation phase.

4.3. Fehlerszenarien

Betrachten wir nun einige Fehlerszenarien:

  • Wenn auf den Microservice vonvalidationnicht zugegriffen werden kann, stellt die Messaging-Plattform mit ihrer dauerhaften Warteschlangenfunktion sicher, dass der Microservice vonvalidationdiese Nachricht zu einem späteren Zeitpunkt empfängt

  • Angenommen, die Messaging-Plattform schlägt fehl, und der Microserviceuserversucht, die Nachricht zu einem späteren Zeitpunkt erneut zu senden, z. B. durch geplante Stapelverarbeitung aller Benutzer, die noch nicht validiert wurden

  • Wenn der Mikrodienst vonvalidationdie Nachricht empfängt, den Benutzer validiert, die Antwort jedoch aufgrund eines Ausfalls der Messaging-Plattform nicht zurücksenden kann, versucht der Mikrodienst vonvalidationauch, die Nachricht zu einem späteren Zeitpunkt erneut zu senden

  • Wenn eine der Nachrichten verloren gegangen ist oder ein anderer Fehler aufgetreten ist, findet der Microserviceuseralle nicht validierten Benutzer durch geplante Stapelverarbeitung und sendet erneut Validierungsanforderungen

Selbst wenn einige der Nachrichten mehrmals ausgegeben würden, würde dies die Konsistenz der Daten in den Datenbanken der Microservices nicht beeinträchtigen.

Durch sorgfältige Prüfung aller möglichen Fehlerszenarien können wir sicherstellen, dass unser System die Bedingungen für eine eventuelle Konsistenz erfüllt. Gleichzeitig müssten wir uns nicht um die kostspieligen verteilten Transaktionen kümmern.

Wir müssen uns jedoch darüber im Klaren sein, dass die Sicherstellung einer eventuellen Konsistenz eine komplexe Aufgabe ist. Es gibt nicht für alle Fälle eine einzige Lösung.

5. Fazit

In diesem Artikel haben wir einige der Mechanismen für die Implementierung von Transaktionen über Microservices hinweg erörtert.

Außerdem haben wir zunächst einige Alternativen zu dieser Art von Transaktionen untersucht.