Ein Leitfaden für Transaktionen über Microservices hinweg

1. Einleitung

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 mit vielen beweglichen Teilen, die 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 langwierig und unzuverlässig werden.

Dies kann die Benutzererfahrung und die Gesamtsystembandbreite ernsthaft beeinträchtigen. So ist eine der besten Möglichkeiten , um das Problem der verteilten Transaktionen zu lösen ist , sie vollständig zu vermeiden.

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

Normalerweise ist ein Microservice so konzipiert, dass er für sich genommen 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üssten.

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

Der Benutzer- Mikroservice würde sich mit dem Benutzerprofil (Erstellen eines neuen Benutzers, Bearbeiten von Profildaten usw.) mit der folgenden zugrunde liegenden Domänenklasse befassen:

@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 Nachrichtenmikroservice würde sich mit Rundfunk befassen. Sie kapselt die Entität Nachricht und alles drum 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 verfügt über eine eigene Datenbank. Beachten Sie, dass wir beziehen sich nicht auf die Entität Benutzer von der Einheit der Nachricht , da die Benutzerklassen aus dem nicht zugänglich sind Nachricht Micro. Wir verweisen auf den Benutzer nur mit ID.

Nun ist die User - Einheit enthält das lastMessageTime Feld , weil wir die Informationen über die letzte Benutzeraktivität Zeit in ihrem Profil zeigen.

Um dem Benutzer jedoch eine neue Nachricht hinzuzufügen und seine lastMessageTime zu aktualisieren, müssten wir jetzt eine Transaktion über Microservices hinweg implementieren.

2.2. Alternativer Ansatz ohne Transaktionen

Wir können unsere Microservice-Architektur ändern und das Feld lastMessageTime aus der User- Entität entfernen .

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

Wenn der Nachrichten- Microservice unter 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-Festschreibungsprotokoll

Das Zwei-Phasen-Festschreibungsprotokoll (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 - Während dieser Phase gibt der Transaktionskoordinator allen Teilnehmern entweder einen Commit- oder einen Rollback-Befehl

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

Das Koordinieren der Transaktion zwischen Mikrodiensten, selbst wenn sie sich im selben Netzwerk befinden, kann das System erheblich verlangsamen , sodass dieser Ansatz in einem Szenario mit hoher Auslastung normalerweise nicht verwendet wird.

3.2. XA Standard

Der 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 Ressourcen, die an verteilten Transaktionen teilnehmen, können beispielsweise zwei Datenbanken mit 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 ist REST-AT, das von RedHat weiterentwickelt wurde, aber noch nicht aus der Entwurfsphase herausgekommen ist. Es wird jedoch vom sofort einsatzbereiten 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 zweiphasigen Transaktion teilnehmen möchten, müssen auch eine bestimmte REST-API unterstützen.

Um eine verteilte Transaktion mit lokalen Ressourcen des Mikroservices zu verbinden, müssten 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 Entschädigung

By far, one of the most feasible models of handling consistency across microservices is eventual consistency.

This model doesn't enforce distributed ACID transactions across microservices. Instead, it proposes to use some mechanisms of ensuring that the system would be eventually consistent at some point in the future.

4.1. A Case for Eventual Consistency

For example, suppose we need to solve the following task:

  • register a user profile
  • do some automated background check that the user can actually access the system

The second task is to ensure, for example, that this user wasn't banned from our servers for some reason.

But it could take time, and we'd like to extract it to a separate microservice. It wouldn't be reasonable to keep the user waiting for so long just to know that she was registered successfully.

One way to solve it would be with a message-driven approach including compensation. Let's consider the following architecture:

  • the user microservice tasked with registering a user profile
  • the validation microservice tasked with doing a background check
  • the messaging platform that supports persistent queues

The messaging platform could ensure that the messages sent by the microservices are persisted. Then they would be delivered at a later time if the receiver weren't currently available

4.2. Happy Scenario

In this architecture, a happy scenario would be:

  • the user microservice registers a user, saving information about her in its local database
  • the user microservice marks this user with a flag. It could signify that this user hasn't yet been validated and doesn't have access to full system functionality
  • a confirmation of registration is sent to the user with a warning that not all functionality of the system is accessible right away
  • the user microservice sends a message to the validation microservice to do the background check of a user
  • the validation microservice runs the background check and sends a message to the user microservice with the results of the check
    • if the results are positive, the user microservice unblocks the user
    • if the results are negative, the user microservice deletes the user account

After we've gone through all these steps, the system should be in a consistent state. However, for some period of time, the user entity appeared to be in an incomplete state.

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

4.3. Failure Scenarios

Now let's consider some failure scenarios:

  • if the validation microservice is not accessible, then the messaging platform with its persistent queue functionality ensures that the validation microservice would receive this message at some later time
  • suppose the messaging platform fails, then the user microservice tries to send the message again at some later time, for example, by scheduled batch-processing of all users that were not yet validated
  • if the validation microservice receives the message, validates the user but can't send the answer back due to the messaging platform failure, the validation microservice also retries sending the message at some later time
  • if one of the messages got lost, or some other failure happened, the user microservice finds all non-validated users by scheduled batch-processing and sends requests for validation again

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 bewusst sein, dass die Gewährleistung einer eventuellen Konsistenz eine komplexe Aufgabe ist. Es gibt nicht für alle Fälle eine einzige Lösung.

5. Schlussfolgerung

In diesem Artikel haben wir einige der Mechanismen zum Implementieren von Transaktionen über Microservices hinweg erörtert.

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