Was ist die am meisten akzeptierte Transaktionsstrategie für Microservices?


80

Eines der Hauptprobleme bei einem System mit Mikrodiensten ist die Funktionsweise von Transaktionen, die sich über verschiedene Dienste erstrecken. In unserer eigenen Architektur haben wir verteilte Transaktionen verwendet, um dies zu lösen, aber sie haben ihre eigenen Probleme. Besonders Deadlocks waren bisher ein Schmerz.

Eine andere Option scheint ein maßgeschneiderter Transaktionsmanager zu sein, der die Abläufe in Ihrem System kennt und die Rollbacks für Sie als Hintergrundprozess übernimmt, der sich über das gesamte System erstreckt und wenn sie nicht erreichbar sind, benachrichtigen Sie sie später).

Gibt es eine andere, akzeptierte Option? Beide scheinen ihre Nachteile zu haben. Der erste kann zu Deadlocks und vielen anderen Problemen führen, der zweite kann zu inkonsistenten Daten führen. Gibt es bessere Möglichkeiten?


Werden diese Mikrodienste von vielen Kunden gleichzeitig oder nur einzeln genutzt?
Marcel

2
Ich habe versucht, diese Frage agnostisch zu stellen, Marcel. Angenommen, wir verwenden ein System, das groß genug ist, um beides zu tun, und wir möchten eine Architektur, die beides unterstützt.
Kristof

4
Dies ist eine schlecht formulierte, aber sehr wichtige Frage. Wenn die meisten Menschen an "Transaktionen" denken, denken sie fast ausschließlich an Konsistenz und nicht an die Atomarität, Isolation oder Dauerhaftigkeit von Transaktionen. Die Frage sollte eigentlich lauten: "Wie erstellt man ein ACID-konformes System bei gegebener Microservices-Architektur? Nur die Implementierung von Konsistenz und nicht der Rest von ACID ist nicht wirklich nützlich. Es sei denn, Sie mögen schlechte Daten.
Jeff Fischer

10
Sie können immer versuchen, meine Frage so zu ändern, dass sie weniger "schlecht formuliert" ist, Jeff Fischer.
Kristof

Diese Frage und Diskussion stehen in engem Zusammenhang mit dieser anderen Frage zu SO .
Paulo Merson

Antworten:


42

Der übliche Ansatz besteht darin, diese Mikrodienste so weit wie möglich zu isolieren - sie als einzelne Einheiten zu behandeln. Dann können Transaktionen im Kontext des gesamten Dienstes entwickelt werden (dh nicht Teil der üblichen DB-Transaktionen, obwohl Sie immer noch DB-Transaktionen innerhalb des Dienstes haben können).

Überlegen Sie, wie Transaktionen ablaufen und welche Art von Transaktionen für Ihre Services sinnvoll sind. Dann können Sie einen Rollback-Mechanismus implementieren, der die ursprüngliche Operation nicht ausführt, oder ein 2-Phasen-Festschreibungssystem, das die ursprüngliche Operation reserviert, bis Sie aufgefordert werden, eine echte Festschreibung durchzuführen. Beide Systeme bedeuten natürlich, dass Sie Ihre eigenen implementieren, aber dann implementieren Sie bereits Ihre Microservices.

Finanzdienstleistungen erledigen dies die ganze Zeit. Wenn ich Geld von meiner Bank zu Ihrer Bank transferieren möchte, gibt es keine einzige Transaktion, die Sie in einer DB hätten. Sie wissen nicht, auf welchen Systemen eine der beiden Banken ausgeführt wird, und müssen sie daher wie Ihre Microservices behandeln. In diesem Fall würde meine Bank mein Geld von meinem Konto auf ein Guthabenkonto verschieben und dann Ihrer Bank mitteilen, dass sie etwas Geld hat. Wenn der Versand fehlschlägt, erstattet meine Bank mein Konto mit dem Geld, das sie zu senden versucht haben.


5
Sie gehen davon aus, dass eine Rückerstattung niemals fehlschlagen kann.
Sanjeev Kumar Dangi

9
@SanjeevKumarDangi es ist weniger wahrscheinlich und selbst wenn es fehlschlägt, kann es leicht gehandhabt werden, da das Besitzkonto und das tatsächliche Konto in der Kontrolle der Bank sind.
Gldraphael

30

Ich denke, die Standardweisheit ist, dass Transaktionen niemals die Grenzen von Mikrodiensten überschreiten. Wenn ein gegebener Datensatz wirklich atomar mit einem anderen übereinstimmen muss, gehören diese beiden Dinge zusammen.

Dies ist einer der Gründe, warum es sehr schwierig ist, ein System in Dienste aufzuteilen, bis Sie es vollständig entworfen haben. Was in der modernen Welt wahrscheinlich bedeutet, dass es geschrieben wurde ...


37
Ein solcher Ansatz könnte sehr wohl dazu führen, dass am Ende alle Mikrodienste zu einer einzigen monolithischen Anwendung zusammengeführt werden.
Slava Fomin II

4
Dies ist der richtige Ansatz. Es ist eigentlich ganz einfach: Wenn Sie eine Transaktion über mehrere Services hinweg benötigen, sind Ihre Services falsch: Neugestaltung! @SlavaFominII Was Sie sagen, ist nur wahr, wenn Sie nicht wissen, wie Sie ein Microservice-System entwerfen sollen. Wenn Sie feststellen, dass Sie gegen den Microservices-Ansatz kämpfen, tun Sie es nicht, Ihr Monolith ist besser als ein schlechtes Microservice-Design. Nur wenn Sie die richtigen Servicegrenzen finden, sollten Sie den Monolithen in Services aufteilen. Andernfalls ist die Verwendung von Microservices nicht die richtige Wahl für die Architektur, sondern folgt nur dem Hype.
Francesc Castells

@FrancescCastells Was ist, wenn für unsere Services eine Transaktion mit anderen Services tatsächlich erforderlich ist? Meinen Sie, wir sollten begrenzte Kontexte ignorieren und unsere Services so modellieren, dass sie als einzelne Transaktion enden? Ich bin ein Neuling in Microservices und lese immer noch. Verzeihung, wenn es naiv klingt.: D: D
Bilbo Baggins

@ BilboBaggins Ich meine das Gegenteil davon, begrenzte Kontexte zu ignorieren. Per Definition finden Transaktionen innerhalb eines begrenzten Kontexts statt und nicht über mehrere von ihnen hinweg. Wenn Sie daher Ihre Microservices korrekt zusammen mit Ihren begrenzten Kontexten entwerfen, sollten sich Ihre Transaktionen nicht auf mehrere Services erstrecken. Beachten Sie, dass Sie manchmal keine Transaktionen benötigen, sondern eine bessere Handhabung der eventuellen Konsistenz und angemessene Ausgleichsmaßnahmen, wenn die Dinge nicht richtig laufen.
Francesc Castells

Ich verstehe nicht, gibt es eine Chance, dass wir dies in einem Chat diskutieren können?
Bilbo Beutlin

17

Ich bin der Meinung, dass Sie sich fragen sollten, ob Microservices der bessere Ansatz ist, wenn Konsistenz eine wichtige Voraussetzung für Ihre Anwendung ist. Wie Martin Fowler sagt :

Microservices führen zu Konsistenzproblemen, da sie lobenswerterweise auf einer dezentralen Datenverwaltung bestehen. Mit einem Monolithen können Sie eine Reihe von Dingen in einer einzigen Transaktion zusammen aktualisieren. Für die Aktualisierung von Microservices sind mehrere Ressourcen erforderlich, und verteilte Transaktionen werden (aus gutem Grund) verpönt. Entwickler müssen sich also der Konsistenzprobleme bewusst sein und herausfinden, wie sie feststellen können, wenn die Dinge nicht synchron sind, bevor sie etwas tun, was der Code bereut.

Aber vielleicht können Sie in Ihrem Fall bei der Verfügbarkeit auf Konsistenz verzichten

Geschäftsprozesse sind oft widersprüchlicher als Sie denken, weil Unternehmen die Verfügbarkeit oft mehr schätzen.

Ich frage mich aber auch, ob es eine Strategie für verteilte Transaktionen in Microservices gibt, aber vielleicht sind die Kosten zu hoch. Ich wollte Ihnen meine zwei Cent mit dem immer ausgezeichneten Artikel von Martin Fowler und dem CAP- Theorem geben.


1
In verteilten Geschäftstransaktionen wird Konsistenz manchmal durch Hinzufügen einer Orchestrierungsebene wie einer Workflow-Engine oder einer sorgfältig gestalteten Warteschlange erreicht. Auf dieser Ebene werden alle zwei Phasen des Festschreibens und Zurücksetzens behandelt, und die Microservices können sich auf eine bestimmte Geschäftslogik konzentrieren. Zurück zu CAP: Wenn Sie in Verfügbarkeit und Konsistenz investieren, wird die Leistung zum Opfer. Wie stehen Microservices im Vergleich zu vielen entkoppelten Klassen, aus denen Ihr Unternehmen in OOP besteht?
Greg

Sie könnten RAFT über Microservices verwenden, um die Konsistenz FWIW
f0ster

1
+1. Ich frage mich oft, warum in einem Microservices-Kontext überhaupt Transaktionen erwünscht sind, wenn alle Pull-Ansichten der Daten als materialisierte Ansichten implementiert werden können. Wenn beispielsweise ein Mikrodienst Lastschriften von einem Konto und von einem anderen Guthaben auf ein anderes Konto ausführt, werden in den Saldoansichten nur Paare von Gutschriften und Lastschriften berücksichtigt, bei denen sich noch nicht übereinstimmende Gutschriften und Lastschriften in den Puffern der materialisierten Ansicht befinden.
Sentinel

16

Wie in mindestens einer der Antworten hier, aber auch an anderer Stelle im Web vorgeschlagen, ist es möglich, einen Mikrodienst zu entwerfen, der Entitäten innerhalb einer normalen Transaktion zusammenhält, wenn Sie Konsistenz zwischen den beiden Entitäten benötigen.

Gleichzeitig kann es jedoch vorkommen, dass die Entitäten nicht zum selben Microservice gehören, z. B. Verkaufsunterlagen und Bestellunterlagen (wenn Sie etwas bestellen, um den Verkauf zu erfüllen). In solchen Fällen ist möglicherweise eine Möglichkeit erforderlich, um die Konsistenz zwischen den beiden Mikrodiensten sicherzustellen.

Traditionell wurden verteilte Transaktionen verwendet, und meiner Erfahrung nach funktionieren sie gut, bis sie auf eine Größe skaliert werden, bei der das Sperren zum Problem wird. Sie können die Sperre lockern, damit wirklich nur die relevanten Ressourcen (z. B. der zu verkaufende Gegenstand) durch eine Statusänderung "gesperrt" werden. Hier wird es jedoch schwierig, da Sie das Gebiet betreten, in dem Sie alle Ressourcen bauen müssen Logik, dies selbst zu tun, anstatt zu sagen, dass eine Datenbank es für Sie handhabt.

Ich habe mit Unternehmen zusammengearbeitet, die es auf sich genommen haben, ein eigenes Transaktionsframework für die Behandlung dieses komplexen Problems zu entwickeln, aber ich empfehle es nicht, da es teuer ist und Zeit braucht, bis es ausgereift ist.

Es gibt Produkte, die an Ihr System angeschraubt werden können und für Konsistenz sorgen. Eine Geschäftsprozess-Engine ist ein gutes Beispiel, und in der Regel übernehmen sie die Konsistenz schließlich und mithilfe der Kompensation. Andere Produkte funktionieren ähnlich. In der Regel befindet sich in der Nähe der Clients eine Softwareschicht, die sich mit Konsistenz und Transaktionen befasst und (Mikro-) Services für die eigentliche Geschäftsabwicklung aufruft . Ein solches Produkt ist ein generischer JCA-Connector, der mit Java EE-Lösungen verwendet werden kann (aus Gründen der Transparenz: Ich bin der Autor). Unter http://blog.maxant.co.uk/pebble/2015/08/04/1438716480000.html finden Sie weitere Details und eine ausführlichere Diskussion der hier angesprochenen Probleme.

Eine andere Möglichkeit, Transaktionen und Konsistenz zu verwalten, besteht darin, einen Anruf an einen Mikrodienst in einen Anruf an eine Transaktion wie eine Nachrichtenwarteschlange umzuwandeln. Nehmen Sie das Beispiel mit den Verkaufs- / Auftragsdatensätzen von oben - Sie können den Verkaufsmikroservice einfach eine Nachricht an das Auftragssystem senden lassen, die in der gleichen Transaktion festgeschrieben wird, die den Verkauf in die Datenbank schreibt. Das Ergebnis ist eine asynchrone Lösung, die sich sehr gut skalieren lässt . Mithilfe von Technologien wie Web-Sockets können Sie sogar das Problem des Blockierens umgehen, das häufig mit der Vergrößerung asynchroner Lösungen zusammenhängt. Weitere Ideen zu Mustern wie diesem finden Sie in einem anderen meiner Artikel: http://blog.maxant.co.uk/pebble/2015/08/11/1439322480000.html .

Unabhängig davon, für welche Lösung Sie sich letztendlich entscheiden, ist es wichtig zu erkennen, dass nur ein kleiner Teil Ihres Systems Dinge schreibt, die konsistent sein müssen - der meiste Zugriff ist wahrscheinlich schreibgeschützt. Bauen Sie aus diesem Grund das Transaktionsmanagement nur in die relevanten Teile des Systems ein, damit es weiterhin gut skaliert werden kann.


In der Zwischenzeit würde ich sagen, dass man ernsthaft darüber nachdenken sollte, den Prozess in einen asynchronen zu verwandeln, bei dem jeder Schritt im Prozess vollständig transaktional ist. Weitere Informationen finden Sie hier: blog.maxant.co.uk/pebble/2018/02/18/1518974314273.html
Ant Kutschera

"Machen Sie das Beispiel für den Verkaufs- / Auftragsdatensatz von oben - Sie können einfach den Verkaufsmikroservice eine Nachricht an das Auftragssystem senden lassen, die in derselben Transaktion festgeschrieben wird, die den Verkauf in die Datenbank schreibt." Sie sind sich nicht sicher, ob es sich bei dem, was Sie vorschlagen, im Grunde um eine verteilte Transaktion handelt. Wie würden Sie in diesem Fall mit dem Rollback-Szenario umgehen? Beispielsweise wird die Nachricht in die Nachrichtenwarteschlange eingereiht, aber auf der DB-Seite zurückgesetzt.
Samstag,

@sactiw ist sich nicht sicher, ob ich ein Zwei-Phasen-Commit im Sinn hätte, aber ich würde das jetzt vermeiden und stattdessen meine Geschäftsdaten und die Tatsache, dass eine Nachricht zur Warteschlange hinzugefügt werden muss, in einer Transaktion in meine Microservice-Datenbank schreiben . Die "Tatsache", auch als "Befehl" bezeichnet, wird nach dem Festschreiben der Transaktion mithilfe eines automatischen Wiederholungsmechanismus asynchron verarbeitet. Ein Beispiel finden Sie im Blog-Artikel vom 10.03.2018. Vermeiden Sie ein Rollback oder eine Kompensation zugunsten einer Vorwärtsstrategie, da dies einfacher zu implementieren ist.
Ant Kutschera

1

In Microservices gibt es drei Möglichkeiten, die Konsistenz zwischen diff zu erreichen. Dienstleistungen:

  1. Orchestrierung - Ein Prozess, der die Transaktion und das Rollback zwischen Diensten verwaltet.

  2. Choreografie - Service-Pass-Nachrichten untereinander und erreichen schließlich einen konsistenten Zustand.

  3. Hybrid - Mischen der beiden oben genannten.

Eine vollständige Beschreibung finden Sie unter folgendem Link: https://medium.com/capital-one-developers/microservices-when-to-react-vs-orchestrate-c6b18308a14c


1

Es gibt viele Lösungen, die mehr Kompromisse eingehen, als mir gefällt. Zugegeben, wenn Ihr Anwendungsfall komplex ist, z. B. der Geldtransport zwischen verschiedenen Banken, können angenehmere Alternativen unmöglich sein. Aber schauen wir uns an, was wir in dem üblichen Szenario tun können, in dem die Verwendung von Microservices unsere potenziellen Datenbanktransaktionen beeinträchtigt.

Option 1: Vermeiden Sie Transaktionen, wenn dies möglich ist

Offensichtlich und bereits erwähnt, aber ideal, wenn wir es schaffen. Gehörten die Komponenten tatsächlich zum selben Microservice? Oder können wir die Systeme so umgestalten, dass die Transaktion unnötig wird? Vielleicht ist das Akzeptieren von Nicht-Transaktionalität das günstigste Opfer.

Option 2: Verwenden Sie eine Warteschlange

Wenn es genug Gewissheit ist , dass der andere Dienst wird erfolgreich an , was wir wollen , es zu tun, können wir sie über irgendeine Form der Warteschlange aufrufen. Der Artikel in der Warteschlange wird erst später abgeholt. Wir können jedoch sicherstellen, dass der Artikel in der Warteschlange steht .

Angenommen, Sie möchten eine Entität einfügen und eine E-Mail als einzelne Transaktion senden. Anstatt den Mailserver anzurufen, stellen wir die E-Mail in eine Tabelle.

Begin transaction
Insert entity
Insert e-mail
Commit transaction

Ein klarer Nachteil ist, dass mehrere Microservices Zugriff auf dieselbe Tabelle benötigen.

Option 3: Führen Sie die externe Arbeit zuletzt kurz vor Abschluss der Transaktion aus

Dieser Ansatz beruht auf der Annahme, dass es sehr unwahrscheinlich ist, dass die Transaktion fehlschlägt.

Begin transaction
Insert entity
Insert another entity
Make external call
Commit transaction

Wenn die Abfragen fehlschlagen, ist der externe Anruf noch nicht erfolgt. Wenn der externe Anruf fehlschlägt, wird die Transaktion niemals festgeschrieben.

Dieser Ansatz hat die Einschränkungen, dass wir nur einen externen Anruf tätigen können und dass er zuletzt durchgeführt werden muss (dh, wir können sein Ergebnis nicht in unseren Abfragen verwenden).

Option 4: Erstellen Sie Dinge in einem ausstehenden Zustand

Wie hier veröffentlicht , können mehrere Microservices verschiedene Komponenten erstellen, die sich nicht transaktionell in einem ausstehenden Zustand befinden.

Eine Validierung wird durchgeführt, es wird jedoch nichts in einem endgültigen Zustand erstellt. Nachdem alles erfolgreich erstellt wurde, wird jede Komponente aktiviert. Normalerweise ist diese Operation so einfach und die Wahrscheinlichkeit, dass etwas schief geht, so gering, dass wir es vielleicht sogar vorziehen, die Aktivierung nicht-transaktional durchzuführen.

Der größte Nachteil ist wahrscheinlich, dass wir das Vorhandensein ausstehender Posten berücksichtigen müssen. Bei jeder Auswahlabfrage muss berücksichtigt werden, ob ausstehende Daten einbezogen werden sollen. Die meisten sollten es ignorieren. Und Updates sind eine andere Geschichte.

Option 5: Lassen Sie den Mikrodienst seine Abfrage freigeben

Keine der anderen Optionen erledigt das für Sie? Dann lasst uns unorthodox werden .

Je nach Unternehmen kann dies inakzeptabel sein. Es ist mir bewusst. Das ist unorthodox. Wenn es nicht akzeptabel ist, gehen Sie einen anderen Weg. Aber wenn dies zu Ihrer Situation passt, löst es das Problem einfach und kraftvoll. Dies könnte der akzeptabelste Kompromiss sein.

Es gibt eine Möglichkeit, Abfragen von mehreren Microservices in eine einfache, einzelne Datenbanktransaktion umzuwandeln.

Geben Sie die Abfrage zurück, anstatt sie auszuführen.

Begin transaction
Execute our own query
Make external call, receiving a query
Execute received query
Commit transaction

In Bezug auf das Netzwerk muss jeder Mikrodienst auf jede Datenbank zugreifen können. Beachten Sie dies auch bei der zukünftigen Skalierung.

Wenn sich die an der Transaktion beteiligten Datenbanken auf demselben Server befinden, handelt es sich um eine reguläre Transaktion. Wenn sie sich auf verschiedenen Servern befinden, handelt es sich um eine verteilte Transaktion. Der Code ist unabhängig davon der gleiche.

Wir erhalten die Abfrage, einschließlich ihres Verbindungstyps, ihrer Parameter und ihrer Verbindungszeichenfolge. Wir können es in eine ordentliche ausführbare Befehlsklasse einbinden, um den Ablauf lesbar zu halten: Der Microservice-Aufruf führt zu einem Befehl, den wir als Teil unserer Transaktion ausführen.

Die Verbindungszeichenfolge ist das, was der ursprüngliche Mikrodienst uns gibt. In jeder Hinsicht wird die Abfrage weiterhin als von diesem Mikrodienst ausgeführt betrachtet. Wir leiten es nur physisch durch den Client-Microservice. Macht das einen Unterschied? Nun, wir können es mit einer anderen Abfrage in dieselbe Transaktion einfügen.

Wenn der Kompromiss akzeptabel ist, bietet dieser Ansatz die unkomplizierte Transaktionalität einer Monolith-Anwendung in einer Microservice-Architektur.


0

Ich würde mit der Zerlegung des Problemraums beginnen - der Identifizierung Ihrer Dienstgrenzen . Bei ordnungsgemäßer Ausführung müssen Sie niemals dienstübergreifende Transaktionen durchführen.

Unterschiedliche Services haben ihre eigenen Daten, Verhaltensweisen, Motivationskräfte, Regierungs- und Geschäftsregeln usw. Ein guter Anfang ist die Auflistung der Fähigkeiten Ihres Unternehmens auf höchster Ebene. Zum Beispiel Marketing, Vertrieb, Buchhaltung, Support. Ein weiterer Ausgangspunkt ist die Organisationsstruktur. Beachten Sie jedoch, dass dies eine Einschränkung darstellt. Aus bestimmten Gründen (beispielsweise aus politischen Gründen) ist dies möglicherweise nicht das optimale Geschäftszerlegungsschema. Ein strengerer Ansatz ist die Analyse der Wertschöpfungskette . Denken Sie daran, dass Ihre Dienste auch Personen umfassen können, es handelt sich nicht ausschließlich um Software. Die Dienste sollten über Ereignisse miteinander kommunizieren .

Der nächste Schritt ist, diese Dienste zu schnitzen. Als Ergebnis erhalten Sie noch relativ unabhängige Aggregate . Sie stellen eine Einheit der Konsistenz dar. Mit anderen Worten, ihre Interna sollten konsistent und ACID sein. Aggregate kommunizieren auch über Ereignisse miteinander.

Wenn Sie der Meinung sind, dass Ihre Domain zuerst Konsistenz erfordert, überlegen Sie es sich noch einmal. Keines der großen und unternehmenskritischen Systeme ist darauf ausgelegt. Sie sind alle verteilt und schließlich konsistent. Überprüfen Sie Pat Hellands klassisches Papier .

Hier finden Sie einige praktische Tipps zum Erstellen eines verteilten Systems.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.