Heute machen wir Sie auf ein kleines Material zu Microservices und verteilter Architektur aufmerksam. Insbesondere wird die Idee von Martin Fowler angesprochen, dass ein neues System mit einem Monolithen beginnen sollte, und selbst in einer entwickelten Microservice-Architektur ist es ratsam, einen großen monolithischen Kern zu belassen.
Viel Spaß beim Lesen!
Heute denkt jeder über Microservices nach und schreibt sie - und ich bin keine Ausnahme. Basierend auf den Grundprinzipien von Microservices und ihrem wahren Kontext ist klar, dass Microservices ein verteiltes System sind.
Was ist eine verteilte Transaktion?
Transaktionen, die mehrere physische Systeme oder Computer in einem Netzwerk umfassen, werden einfach als verteilte Transaktionen bezeichnet. In der Welt der Mikrodienste wird eine Transaktion auf mehrere Dienste aufgeteilt, die nacheinander aufgerufen werden, um die gesamte Transaktion abzuschließen.
Hier ist ein monolithisches Online-Shop-System, das Transaktionen verwendet:
Abb. 1: Transaktion in einem Monolithen
Wenn im obigen System ein Benutzer eine Bestellanforderung (Checkout) an die Plattform sendet , erstellt die Plattform eine lokale Transaktion in der Datenbank, und diese Transaktion umfasst viele Datenbanktabellen, um die Bestellung zu verarbeiten ( zu verarbeiten ) und zu reservieren(Reserve-) Waren aus dem Lager. Wenn einer dieser Schritte fehlschlägt, kann die Transaktion zurückgesetzt werden , was bedeutet, dass sowohl die Bestellung selbst als auch die reservierte Ware abgelehnt werden. Diese Prinzipien werden als ACID (Atomicity, Consistency, Isolation, Durability) bezeichnet und sind auf Datenbanksystemebene garantiert.
Hier ist eine Zerlegung eines aus Microservices erstellten Online-Shop-Systems:
Abbildung 2: Transaktionen in einem Microservice
Nach der Zerlegung dieses Systems haben wir Microservices
OrderMicroserviceund erstelltInventoryMicroservicemit separaten Datenbanken. Wenn eine Checkout-Anfrage von einem Benutzer kommt, werden diese beiden Mikrodienste aufgerufen und jeder von ihnen nimmt Änderungen an seiner Datenbank vor. Da eine Transaktion jetzt über mehrere Datenbanken auf mehrere Systeme verteilt ist, wird sie als verteilt betrachtet .
Was ist das Problem beim Festschreiben verteilter Transaktionen in Microservices?
Mit der Einführung der Microservice-Architektur verlieren Datenbanken ihre ACID-Natur. Aufgrund der möglichen Verbreitung von Transaktionen zwischen vielen Microservices und damit Datenbanken muss man sich mit folgenden Schlüsselproblemen befassen:
Wie kann die Transaktionsatomarität aufrechterhalten werden?
Atomarität bedeutet, dass in jeder Transaktion entweder alle Schritte ausgeführt werden können oder keine. Wenn das obige Beispiel den Vorgang "Bestellpositionen" in der Methode nicht abschließt
InventoryMicroservice, wie können dann die vorgenommenen Änderungen in der "Auftragsabwicklung" rückgängig gemacht werden OrderMicroservice?
Wie gehe ich mit Wettbewerbsanfragen um?
Angenommen, ein Objekt von einem der Mikrodienste wird zur Langzeitspeicherung in die Datenbank eingegeben, und gleichzeitig liest eine andere Anforderung dasselbe Objekt. Welche Daten sollte der Dienst zurückgeben - alt oder neu? Müssen Sie im obigen Beispiel, wenn es
OrderMicroserviceseine Arbeit bereits abgeschlossen hat und InventoryMicroservicegerade aktualisiert wird, die aktuelle Bestellung in die Anzahl der Anfragen nach Bestellungen des Benutzers aufnehmen?
Moderne Systeme sind unter Berücksichtigung potenzieller Fehler konzipiert, und eines der Hauptprobleme bei der verteilten Transaktionsverarbeitung wird von Pat Helland gut artikuliert.
In der Regel erstellen Entwickler keine großen skalierbaren Anwendungen, bei denen mit verteilten Transaktionen gearbeitet werden muss.
Mögliche Lösungen
Die beiden oben genannten Punkte sind im Zusammenhang mit dem Entwurf und der Erstellung von Anwendungen auf der Basis von Mikrodiensten sehr kritisch. Um sie zu lösen, werden die folgenden zwei Ansätze verwendet:
- Zweiphasenfixierung
- Ultimative Konsistenz und Kompensation / SAGA
1. Zweiphasenfixierung
Wie der Name schon sagt, umfasst diese Methode zur Verarbeitung von Transaktionen zwei Phasen: eine Vorbereitungsphase und eine Festschreibungsphase. Eine wichtige Rolle spielt in diesem Fall der Transaktionskoordinator, der den Lebenszyklus der Transaktion organisiert.
Wie es funktioniert
In der Vorbereitungsphase bereiten sich alle an der Arbeit beteiligten Microservices auf das Commit vor und benachrichtigen den Koordinator, dass sie bereit sind, die Transaktion abzuschließen. Im nächsten Schritt erfolgt dann entweder ein Commit oder der Transaktionskoordinator weist alle Microservices an, ein Rollback durchzuführen.
Betrachten Sie noch einmal ein Online-Shop-System als Beispiel:
Abbildung 3: Erfolgreiches Zwei-Phasen-Commit in einem Microservice-System
Wenn ein Benutzer im obigen Beispiel (Abbildung 3) eine Bestellanforderung sendet,
TransactionCoordinatorstartet der Koordinator zunächst eine globale Transaktion mit vollständigen Kontextinformationen. Zunächst sendet es den Befehl prepare an den Microservice OrderMicroservice, um die Bestellung zu erstellen. Dann sendet es den Vorbereitungsbefehl anInventoryMicroserviceArtikel reservieren. Wenn beide Dienste bereit sind, Änderungen vorzunehmen, blockieren sie Objekte für weitere Änderungen und benachrichtigen sie darüber TransactionCoordinator. Sobald TransactionCoordinatorbestätigt wird, dass alle Microservices bereit sind, ihre Änderungen zu übernehmen, werden diese Microservices angewiesen, sie zu speichern, indem ein Commit der Transaktion angefordert wird. Zu diesem Zeitpunkt werden alle Objekte entsperrt.
Abbildung 4: Fehlgeschlagenes zweiphasiges Festschreiben bei der Arbeit mit Microservices
In einem Fehlerszenario (Abbildung 4): Wenn ein einzelner Microservice zu irgendeinem Zeitpunkt keine Zeit zur Vorbereitung hat,
TransactionCoordinatorbrechen Sie die Transaktion ab und starten Sie den Rollback-Prozess. In der Abbildung OrderMicroservicekonnte ich aus irgendeinem Grund keine Bestellung erstellen, InventoryMicroserviceantwortete jedoch , dass ich bereit war, eine Bestellung zu erstellen. Der Koordinator TransactionCoordinatorwird eine Stornierung unter beantragenInventoryMicroserviceDanach setzt der Dienst alle vorgenommenen Änderungen zurück und entsperrt die Datenbankobjekte.
Leistungen
- Dieser Ansatz garantiert die Atomizität der Transaktion. Die Transaktion wird entweder abgeschlossen, wenn beide Microservices erfolgreich sind oder wenn die Microservices keine Änderungen vornehmen.
- Zweitens können Sie mit diesem Ansatz das Lesen vom Schreiben isolieren, da Änderungen an Objekten erst sichtbar werden, wenn der Transaktionskoordinator diese Änderungen festschreibt.
- Dieser Ansatz ist ein synchroner Anruf, bei dem der Client über Erfolg oder Misserfolg benachrichtigt wird.
Nachteile
- Nichts ist perfekt; Zwei-Phasen-Commits sind im Vergleich zu einzelnen Microservice-Vorgängen eher langsam. Sie sind stark vom Koordinator abhängig. Transaktionen, die das System in Zeiten hoher Auslastung erheblich verlangsamen können.
- Ein weiterer großer Nachteil ist das Sperren von Datenbankzeilen. Das Sperren kann zu einem Leistungsengpass werden, und es können Deadlocks auftreten , bei denen sich zwei Transaktionen gegenseitig sperren.
2. Ultimative Konsistenz und Kompensation / SAGA
Eine der besten Definitionen für Konsistenz finden Sie letztendlich bei microservices.io: Jeder Dienst veröffentlicht ein Ereignis, wenn seine Daten aktualisiert werden. Andere Dienste abonnieren Ereignisse. Wenn ein Ereignis empfangen wird, aktualisiert der Dienst seine Daten .
Bei diesem Ansatz wird eine verteilte Transaktion als Sammlung asynchroner lokaler Transaktionen auf den entsprechenden Mikrodiensten ausgeführt. Microservices tauschen Informationen über den Eventbus aus.
Wie es funktioniert
Nehmen wir noch einmal ein Beispiel für ein System, das in einem Online-Shop ausgeführt wird:
Abbildung 5: Ultimative Konsistenz / SAGA, Erfolg
Im obigen Beispiel (Abbildung 5) fordert der Kunde das System auf, die Bestellung zu verarbeiten. Diese Anforderung
Choreographerlöst das Ereignis " Bestellung erstellen" aus, mit dem die Transaktion gestartet wird. Microservice OrderMicroservicewartet auf dieses Ereignis und erstellt eine Bestellung. Wenn dieser Vorgang erfolgreich ist, wird das Ereignis "Bestellung erstellt" ausgelöst. Der Koordinator Choreographerhört auf dieses Ereignis und bestellt Artikel, wodurch das Ereignis "Reservegegenstände" ausgelöst wird. MicroserviceInventoryMicroservicehört auf dieses Ereignis und bestellt Waren; Wenn dieses Ereignis erfolgreich ist, wird das Ereignis "Artikel reserviert" ausgelöst. In diesem Beispiel bedeutet dies, dass die Transaktion beendet wurde.
Die gesamte ereignisbasierte Kommunikation zwischen Mikrodiensten erfolgt über den Ereignisbus, und ein anderes System ist für dessen Organisation verantwortlich (Choreografie). Auf diese Weise wird das Problem mit unnötiger Komplexität gelöst.
Abbildung 6: Ultimative Konsistenz / SAGA, fehlgeschlagenes Ergebnis
Wenn die
InventoryMicroserviceElemente aus irgendeinem Grund nicht reserviert wurden (Abbildung 6), wird das Ereignis Fehler beim Reservieren von Elementen ausgelöst. Der Koordinator Choreographerwartet auf dieses Ereignis und startet die Gegentransaktion, wodurch das Ereignis "Auftrag löschen" ausgelöst wird. MicroserviceOrderMicroservice Hört auf dieses Ereignis und entfernt die zuvor erstellte Reihenfolge.
Leistungen
Ein Hauptvorteil dieses Ansatzes besteht darin, dass sich jeder Mikrodienst nur auf seine eigene atomare Transaktion konzentriert. Microservices werden nicht blockiert, wenn die Ausführung eines anderen Dienstes relativ lange dauert. Dies bedeutet auch, dass Sie die Datenbank auch nicht sperren müssen. Mit diesem Ansatz ist es möglich, eine gute Skalierbarkeit des Systems bei Arbeiten unter hoher Last sicherzustellen, da die vorgeschlagene Lösung asynchron ist und auf der Arbeit mit Ereignissen basiert.
Nachteile
Der Hauptnachteil dieses Ansatzes besteht darin, dass er keine Leseisolation bietet. Im obigen Beispiel sieht der Kunde also, dass die Bestellung erstellt wurde, aber nach einer Sekunde wird die Bestellung während der Gegentransaktion gelöscht. Darüber hinaus wird es mit zunehmender Anzahl von Microservices schwieriger, diese zu debuggen und zu warten.
Fazit
Die erste Alternative zum vorgeschlagenen Ansatz besteht darin, verteilte Transaktionen vollständig aufzugeben. Wenn Sie eine neue Anwendung erstellen, beginnen Sie mit einer monolithischen Architektur, wie in MonolithFirst von Martin Fowler beschrieben. Ich werde ihn zitieren.
, , . , , . —Wenn Sie aufgrund eines einzelnen Ereignisses Daten an zwei Stellen gleichzeitig aktualisieren müssen, ist der letztendlich konsistente / SAGA-Ansatz für die Verarbeitung verteilter Transaktionen dem zweiphasigen Ansatz vorzuziehen. Der Hauptgrund ist, dass der Zwei-Phasen-Ansatz in einer verteilten Umgebung nicht skalierbar ist. Die Verwendung von Konsistenz wirft letztendlich auch eigene Probleme auf, z. B. das atomare Aktualisieren der Datenbank und das Auslösen eines Ereignisses. Um zu einer solchen Entwicklungsphilosophie überzugehen, ist es notwendig, ihre Wahrnehmung sowohl aus Sicht des Entwicklers als auch aus Sicht des Testers zu ändern.