Eventarchitektur für Microservices mit dem Saga Pattern

Table of Contents

Was sind Microservices?

Microservices sind heutzutage eine sehr populäre Architektur für Software, bei der die Anwendung in kleinere Komponenten (Services) unterteilt wird. Jeder Service soll dabei eine bestimmte Aufgabe erledigen und kommuniziert mit den anderen Services über Schnittstellen. Microservices bringen viele Vorteile. Aufgrund des Designs der Architektur (leicht gekoppelte Komponenten), können die einzelnen Services separat gewartet, getestet und deployed werden. Das bedeutet, dass wir Services ersetzen können, ohne die Prozesse der Anwendung zu beeinträchtigen. Der Preis dafür ist eine hohe Komplexität in der Kommunikation zwischen den Services und der Definition ihrer Schnittstellen. In diesem Artikel werden wir zwei Patterns vorstellen, die in der Microservice Architektur oft eingesetzt werden, um diese Komplexität zu reduzieren und eine konsistente funktionsfähige Infrastruktur zu erhalten.

Motivation für SAGA

ACID

Die „ACID-ity“ einer Transaktion beschreibt vier Eigenschaften der Transaktion, die die Validität und Konsistenz der Daten in einer Datenbank garantieren:

  • Atomicity: Eine Transaktion kann entweder komplett fehlschlagen oder komplett erfolgen
  • Consistency: Alle Integritätsprüfungen sind vor dem Abschluss der Transaktion überprüft
  • Isolation:  Transaktion die nebenläufig ausgeführt werden dürfen sich nicht gegenseitig beeinflussen
  • Durability: Die Auswirkungen einer erfolgreich ausgeführten Transaktion persistieren.

Local Transaction

locale Transaktion

Das obige Bild beschreibt eine Transaktion in einem monolithischen System. Wenn ein beliebiger Schritt fehlschlägt, wird die ganze Transaktion rückgängig gemacht. Alle Änderungen bisheriger Schritte werden abgebrochen und damit die ACID-ity der Transaktion garantiert.

Distributed Transaction

In einer Microservices Architektur soll jeder Service seine eigene Datenbank haben und darf nur mit dieser Datenbank kommunizieren. Was passiert wenn Transaktionen mehrere Services überbrücken?

Das obige Bild stellt einen PlaceOrder Transaktion bzw. Prozess dar, die den CustomerService und den OrderService einbezieht.

  1. Der Benutzer gibt eine neue Bestellung auf
  2. Der Conductor weiß, dass sowohl die CustomerTable als auch die OrderTable aktualisiert werden müssen und beauftragt dafür die Customer- und Order-Service.
  3. Wenn beide Services mit der Transaktion durch sind, sendet der Conductor eine OK Antwort zurück.
 

Soweit so gut. Allerdings müssen wir das Szenario betrachten, bei dem der CustomerService seine Datenbank aktualisiert hat und aus irgendeinem Grund der OrderService fehlschlägt. Wir kommen in einen invaliden Zustand, da die Transaktion nicht atomar ist. Welchen Zustand soll zurückgegeben werden, wenn während eine Transaktion, die ein Objekt ändert, eine Anfrage zum Lesen des Objekts ankommt? Im nächsten Abschnitt stellen wir zwei Alternativen dar, um dieses Problem zu bewältigen. 

Lösungen

2pc

2pc - Transaktion

2pc, Two Phase Commit besteht aus zwei Phasen:

  1. Prepare: Alle beteiligten Microservices werden in dieser Phase ein Signal vom Coordinator erhalten, sich für eine lokale Datenänderung vorzubereiten.
  2. Commit: Diese Phase beginnt, wenn alle Services bereit sind. Die Änderungen werden in jedem Service angewendet (committed), bevor der Coordinator die Transaktion beendet. 
 
Dieses Pattern garantiert uns, dass die Transaktion atomar ist. Das Protokoll sperrt das Objekt, das die Transaktion ändern wird und ist erst verfügbar wenn die Transaktion beendet ist, mit oder ohne Erfolg. Das Problem ist allerdings die Synchronität. Andere Transaktionen, die dieses Objekt brauchen müssen warten bis die aktuelle Transaktion fertig ist. Dies könnte ein Bottleneck für unser System sein. Es ist auch möglich, dass zwei Transaktionen sich gegenseitig blockieren, wenn jede Transaktion ein Objekt braucht, das der andere sperrt. Dies nennt man einen Deadlock. Dies gilt es jedoch zu vermeiden.

Was bedeutet Saga?

Der Saga Pattern ist eins der prominentesten Patterns, die in einer Microservice Architektur eingesetzt werden. Ein Saga ist eine Sequenz von lokalen Transaktionen, bei der jede Transaktion die Daten eines einzigen Service ändern kann. Ein externer Trigger (z.B. Aufruf eines API Endpunktes) startet die erste Transaktion, dessen Ende die nächste Transaktion bis die letzte Transaktion zum Ende kommt.

Man das Saga Pattern auf verschiedene Arten implementieren

  • Choreography: jede lokale Transaktion publiziert ein Event welches andere lokale Transaktionen in einem anderen Services triggert.
  • Orchestration: Ein Orchestrator kontrolliert, wann die Transaktion in welchem Service ausgeführt werden muss.
 

Die folgende Bilder beschreiben eine Choreography zwischen dem Order- und dem Customer-Service:

Saga - erfolgreiche Transaktion
  1. CreateOrder gestartet: Eine Bestellung wurde aufgegeben.
  2. Daten im OrderService werden aktualisiert.
  3.  Das Order Created Event wird gesendet.
  4. CustomerService reagiert auf dieses Event und aktualisiert entsprechend seine Daten.
  5. Das Ende des Sagas ist mit dem Customer Updated Event signalisiert.
Saga - fehlerhafte Transaktion
  1. CreateOrder gestartet: Eine Bestellung wurde aufgegeben.
  2. Daten im OrderService werden aktualisiert.
  3. Das Order Created Event wird gesendet.
  4. CustomerService reagiert auf dieses Event und aktualisiert entsprechend seine Daten.
  5. Die Transaktion ist fehlgeschlagen. Der CustomerService sendet ein Customer Update Failed Event, damit Änderungen im OrderService zurückgesetzt werden
  6. Das Ende des Sagas ist mit dem Order Creation Compensated Event signalisiert.

Schreibe einen Kommentar