Nehmen wir an, wir haben einen Benutzer, Wallet REST-Microservices und ein API-Gateway, das die Dinge zusammenhält. Wenn sich Bob auf unserer Website registriert, muss unser API-Gateway einen Benutzer über den Benutzer-Microservice und eine Brieftasche über den Wallet-Microservice erstellen.
Hier sind einige Szenarien, in denen etwas schief gehen könnte:
Die Erstellung des Benutzers Bob schlägt fehl: Das ist in Ordnung. Wir senden nur eine Fehlermeldung an den Bob. Wir verwenden SQL-Transaktionen, sodass niemand Bob jemals im System gesehen hat. Alles ist gut :)
Benutzer Bob wird erstellt, aber bevor unsere Brieftasche erstellt werden kann, stürzt unser API-Gateway schwer ab. Wir haben jetzt einen Benutzer ohne Brieftasche (inkonsistente Daten).
Benutzer Bob wird erstellt und während wir die Brieftasche erstellen, wird die HTTP-Verbindung unterbrochen. Die Erstellung der Brieftasche war möglicherweise erfolgreich oder nicht.
Welche Lösungen stehen zur Verfügung, um diese Art von Dateninkonsistenz zu verhindern? Gibt es Muster, mit denen Transaktionen mehrere REST-Anforderungen umfassen können? Ich habe die Wikipedia-Seite über Two-Phase-Commit gelesen, die dieses Problem zu berühren scheint, bin mir aber nicht sicher, wie ich es in der Praxis anwenden soll. Dieses Atomic Distributed Transactions: Ein RESTful-Designpapier scheint ebenfalls interessant zu sein, obwohl ich es noch nicht gelesen habe.
Alternativ weiß ich, dass REST möglicherweise nicht für diesen Anwendungsfall geeignet ist. Wäre es vielleicht die richtige Art, mit dieser Situation umzugehen, REST vollständig zu löschen und ein anderes Kommunikationsprotokoll wie ein Nachrichtenwarteschlangensystem zu verwenden? Oder sollte ich die Konsistenz in meinem Anwendungscode erzwingen (z. B. durch einen Hintergrundjob, der Inkonsistenzen erkennt und behebt, oder durch ein Attribut "state" in meinem Benutzermodell mit Werten "create", "created" usw.)?
quelle
Antworten:
Was keinen Sinn ergibt:
Was wird Ihnen Kopfschmerzen bereiten:
Was ist wahrscheinlich die beste Alternative:
Aber was ist, wenn Sie synchrone Antworten benötigen?
quelle
Dies ist eine klassische Frage, die mir kürzlich während eines Interviews gestellt wurde. Wie rufe ich mehrere Webdienste auf und behalte trotzdem eine Art Fehlerbehandlung mitten in der Aufgabe bei? Heutzutage vermeiden wir beim Hochleistungsrechnen Zweiphasen-Commits. Ich habe vor vielen Jahren eine Zeitung über das sogenannte "Starbuck-Modell" für Transaktionen gelesen: Denken Sie an den Prozess des Bestellens, Bezahlens, Zubereitens und Empfangens des bei Starbuck bestellten Kaffees ... Ich vereinfache die Dinge zu stark, aber ein Zwei-Phasen-Commit-Modell würde dies tun Schlagen Sie vor, dass der gesamte Prozess eine einzige Verpackungstransaktion für alle erforderlichen Schritte ist, bis Sie Ihren Kaffee erhalten. Bei diesem Modell würden jedoch alle Mitarbeiter warten und aufhören zu arbeiten, bis Sie Ihren Kaffee erhalten. Siehst du das bild
Stattdessen ist das "Starbuck-Modell" produktiver, indem es dem "Best-Effort" -Modell folgt und Fehler im Prozess kompensiert. Erstens sorgen sie dafür, dass Sie bezahlen! Dann gibt es Nachrichtenwarteschlangen mit Ihrer Bestellung, die an die Tasse angehängt sind. Wenn dabei etwas schief geht, wie Sie Ihren Kaffee nicht bekommen haben, es nicht das ist, was Sie bestellt haben usw., treten wir in den Vergütungsprozess ein und stellen sicher, dass Sie das bekommen, was Sie wollen oder Sie erstatten. Dies ist das effizienteste Modell für mehr Produktivität.
Manchmal verschwendet Starbucks einen Kaffee, aber der Gesamtprozess ist effizient. Es gibt andere Tricks, die Sie beim Erstellen Ihrer Webdienste berücksichtigen sollten, z. B. das Entwerfen so, dass sie beliebig oft aufgerufen werden können und dennoch das gleiche Endergebnis liefern. Meine Empfehlung lautet also:
Seien Sie bei der Definition Ihrer Webdienste nicht zu fein (ich bin nicht überzeugt von dem heutigen Hype um Mikrodienste: zu viele Risiken, zu weit zu gehen);
Async erhöht die Leistung. Ziehen Sie es daher vor, asynchron zu sein. Senden Sie Benachrichtigungen nach Möglichkeit per E-Mail.
Erstellen Sie intelligentere Services, um sie beliebig oft "abrufbar" zu machen, und verarbeiten Sie sie mit einer UID oder TaskID, die bis zum Ende der Reihenfolge von unten nach oben folgt, und validieren Sie die Geschäftsregeln in jedem Schritt.
Verwenden Sie Nachrichtenwarteschlangen (JMS oder andere) und leiten Sie zu Fehlerbehandlungsprozessoren um, die Operationen auf "Rollback" anwenden, indem Sie entgegengesetzte Operationen anwenden. Übrigens erfordert das Arbeiten mit asynchroner Reihenfolge eine Art Warteschlange, um den aktuellen Status des Prozesses zu überprüfen. also bedenke das;
Stellen Sie es als letztes Mittel (da es möglicherweise nicht häufig vorkommt) in eine Warteschlange, um Fehler manuell zu verarbeiten.
Kehren wir zum ursprünglichen Problem zurück, das veröffentlicht wurde. Erstellen Sie ein Konto und eine Brieftasche und stellen Sie sicher, dass alles erledigt wurde.
Angenommen, ein Webdienst wird aufgerufen, um den gesamten Vorgang zu orchestrieren.
Der Pseudocode des Webdienstes würde folgendermaßen aussehen:
Rufen Sie den Microservice zur Kontoerstellung auf, übergeben Sie ihm einige Informationen und eine eindeutige Aufgaben-ID. 1.1 Der Microservice zur Kontoerstellung prüft zunächst, ob dieses Konto bereits erstellt wurde. Dem Kontoeintrag ist eine Aufgaben-ID zugeordnet. Der Microservice erkennt, dass das Konto nicht vorhanden ist, erstellt es und speichert die Task-ID. HINWEIS: Dieser Dienst kann 2000 Mal aufgerufen werden und führt immer das gleiche Ergebnis aus. Der Dienst antwortet mit einer "Quittung, die nur minimale Informationen enthält, um bei Bedarf einen Rückgängig-Vorgang auszuführen".
Rufen Sie die Wallet-Erstellung auf und geben Sie die Konto-ID und die Aufgaben-ID an. Angenommen, eine Bedingung ist ungültig und die Brieftaschenerstellung kann nicht durchgeführt werden. Der Aufruf wird mit einem Fehler zurückgegeben, es wurde jedoch nichts erstellt.
Der Orchestrator wird über den Fehler informiert. Es weiß, dass es die Kontoerstellung abbrechen muss, aber es wird es nicht selbst tun. Der Brieftaschendienst wird aufgefordert, dies zu tun, indem er die am Ende von Schritt 1 erhaltene "minimale Rückgängig-Quittung" weiterleitet.
Der Kontodienst liest die Rückgängig-Quittung und weiß, wie der Vorgang rückgängig gemacht werden kann. Die Rückgängig-Quittung kann sogar Informationen über einen anderen Mikrodienst enthalten, den sie möglicherweise selbst aufgerufen hat, um einen Teil des Auftrags auszuführen. In diesem Fall kann der Rückgängig-Beleg die Konto-ID und möglicherweise einige zusätzliche Informationen enthalten, die für die Ausführung des umgekehrten Vorgangs erforderlich sind. In unserem Fall löschen wir das Konto zur Vereinfachung einfach mit seiner Konto-ID.
Angenommen, der Webdienst hat (in diesem Fall) nie den Erfolg oder Misserfolg erhalten, dass das Rückgängigmachen der Kontoerstellung durchgeführt wurde. Der Rückgängig-Dienst des Kontos wird einfach erneut aufgerufen. Und dieser Dienst sollte normalerweise niemals fehlschlagen, da sein Ziel darin besteht, dass das Konto nicht mehr existiert. Es prüft also, ob es existiert und sieht, dass nichts getan werden kann, um es rückgängig zu machen. Es wird also zurückgegeben, dass die Operation ein Erfolg ist.
Der Webdienst gibt an den Benutzer zurück, dass das Konto nicht erstellt werden konnte.
Dies ist ein synchrones Beispiel. Wir hätten es auf andere Weise verwalten und den Fall in eine Nachrichtenwarteschlange stellen können, die auf den Helpdesk ausgerichtet ist, wenn wir nicht möchten, dass das System den Fehler vollständig behebt. "Ich habe gesehen, dass dies in einem Unternehmen durchgeführt wird, in dem dies nicht ausreicht Das Back-End-System konnte mit Hooks versehen werden, um Situationen zu korrigieren. Der Helpdesk erhielt Nachrichten, die enthielten, was erfolgreich ausgeführt wurde, und verfügte über genügend Informationen, um Probleme zu beheben, für die unser Rückgängig-Beleg vollautomatisch verwendet werden konnte.
Ich habe eine Suche durchgeführt und die Microsoft-Website enthält eine Musterbeschreibung für diesen Ansatz. Es wird das kompensierende Transaktionsmuster genannt:
Ausgleich des Transaktionsmusters
quelle
Alle verteilten Systeme haben Probleme mit der Transaktionskonsistenz. Der beste Weg, dies zu tun, ist, wie Sie sagten, ein zweiphasiges Commit durchzuführen. Lassen Sie die Brieftasche und den Benutzer in einem ausstehenden Zustand erstellen. Rufen Sie nach dem Erstellen einen separaten Anruf an, um den Benutzer zu aktivieren.
Dieser letzte Anruf sollte sicher wiederholbar sein (falls Ihre Verbindung unterbrochen wird).
Dies erfordert, dass der letzte Aufruf beide Tabellen kennt (damit dies in einer einzelnen JDBC-Transaktion erfolgen kann).
Alternativ möchten Sie vielleicht darüber nachdenken, warum Sie sich so Sorgen um einen Benutzer ohne Brieftasche machen. Glauben Sie, dass dies ein Problem verursachen wird? Wenn ja, ist es vielleicht eine schlechte Idee, diese als separate Ruhegespräche zu haben. Wenn ein Benutzer ohne Brieftasche nicht existieren sollte, sollten Sie die Brieftasche wahrscheinlich dem Benutzer hinzufügen (im ursprünglichen POST-Aufruf, um den Benutzer zu erstellen).
quelle
Meiner Meinung nach ist einer der Schlüsselaspekte der Microservices-Architektur, dass die Transaktion auf den einzelnen Microservice beschränkt ist (Prinzip der Einzelverantwortung).
Im aktuellen Beispiel wäre die Benutzererstellung eine eigene Transaktion. Die Benutzererstellung würde ein USER_CREATED-Ereignis in eine Ereigniswarteschlange verschieben. Der Wallet-Dienst würde das USER_CREATED-Ereignis abonnieren und die Wallet-Erstellung durchführen.
quelle
Wenn meine Brieftasche nur eine weitere Gruppe von Datensätzen in derselben SQL-Datenbank wie der Benutzer wäre, würde ich wahrscheinlich den Benutzer- und Brieftaschenerstellungscode in denselben Dienst stellen und dies mit den normalen Datenbank-Transaktionsfunktionen verarbeiten.
Für mich klingt es so, als würden Sie fragen, was passiert, wenn der Code zur Erstellung der Brieftasche erfordert, dass Sie ein anderes System oder andere Systeme berühren? Ich würde sagen, es hängt alles davon ab, wie komplex und / oder riskant der Erstellungsprozess ist.
Wenn es nur darum geht, einen anderen zuverlässigen Datenspeicher zu berühren (z. B. einen, der nicht an Ihren SQL-Transaktionen teilnehmen kann), bin ich abhängig von den Gesamtsystemparametern möglicherweise bereit, die verschwindend geringe Wahrscheinlichkeit zu riskieren, dass kein zweiter Schreibvorgang stattfindet. Ich könnte nichts tun, aber eine Ausnahme auslösen und die inkonsistenten Daten über eine Ausgleichstransaktion oder sogar eine Ad-hoc-Methode behandeln. Wie ich meinen Entwicklern immer sage: "Wenn so etwas in der App passiert, bleibt es nicht unbemerkt."
Mit zunehmender Komplexität und zunehmendem Risiko bei der Erstellung von Brieftaschen müssen Sie Maßnahmen ergreifen, um die damit verbundenen Risiken zu verringern. Angenommen, einige der Schritte erfordern das Aufrufen mehrerer Partner-APIs.
An dieser Stelle können Sie eine Nachrichtenwarteschlange zusammen mit dem Begriff der teilweise erstellten Benutzer und / oder Brieftaschen einführen.
Eine einfache und effektive Strategie, um sicherzustellen, dass Ihre Entitäten schließlich ordnungsgemäß erstellt werden, besteht darin, die Jobs erneut zu versuchen, bis sie erfolgreich sind. Viel hängt jedoch von den Anwendungsfällen für Ihre Anwendung ab.
Ich würde auch lange und gründlich darüber nachdenken, warum ich einen fehleranfälligen Schritt in meinem Bereitstellungsprozess hatte.
quelle
Eine einfache Lösung besteht darin, einen Benutzer mithilfe des Benutzerdienstes zu erstellen und einen Messaging-Bus zu verwenden, in dem der Benutzerdienst seine Ereignisse ausgibt. Der Wallet-Dienst registriert sich auf dem Messaging-Bus, lauscht dem vom Benutzer erstellten Ereignis und erstellt eine Wallet für den Benutzer. Wenn der Benutzer in der Zwischenzeit die Brieftaschen-Benutzeroberfläche aufruft, um seine Brieftasche anzuzeigen, überprüfen Sie, ob der Benutzer gerade erstellt wurde, und zeigen Sie an, dass die Erstellung Ihrer Brieftasche im Gange ist. Überprüfen Sie dies in der Zwischenzeit
quelle
Traditionell werden verteilte Transaktionsmanager verwendet. Vor ein paar Jahren in der Java EE Welt könnten Sie diese Dienste als erstellt EJB s , die an verschiedenen Knoten und Ihre API - Gateway eingesetzt wurden würde Remote - Aufrufe auf diese EJBs gemacht haben. Der Anwendungsserver (bei korrekter Konfiguration) stellt mithilfe eines zweiphasigen Commits automatisch sicher, dass die Transaktion auf jedem Knoten entweder festgeschrieben oder zurückgesetzt wird, sodass die Konsistenz gewährleistet ist. Dies erfordert jedoch, dass alle Dienste auf demselben Anwendungsservertyp bereitgestellt werden (damit sie kompatibel sind) und in Wirklichkeit nur mit Diensten funktionieren, die von einem einzelnen Unternehmen bereitgestellt werden.
Für SOAP (ok, nicht REST) gibt es die WS-AT- Spezifikation, aber kein Dienst, den ich jemals integrieren musste, unterstützt dies. Für REST hat JBoss etwas in der Pipeline . Andernfalls besteht das "Muster" darin, entweder ein Produkt zu finden, das Sie in Ihre Architektur einbinden können, oder eine eigene Lösung zu erstellen (nicht empfohlen).
Ich habe ein solches Produkt für Java EE veröffentlicht: https://github.com/maxant/genericconnector
Gemäß dem Papier, auf das Sie verweisen, gibt es auch das Try-Cancel / Confirm-Muster und das zugehörige Produkt von Atomikos.
BPEL Engines übernehmen die Konsistenz zwischen remote bereitgestellten Diensten mithilfe der Kompensation.
Es gibt viele Möglichkeiten, nicht-transaktionale Ressourcen in eine Transaktion zu "binden":
Playing Devils Advocate: Warum so etwas bauen, wenn es Produkte gibt, die das für Sie tun (siehe oben) und es wahrscheinlich besser machen als Sie, weil sie sich bewährt haben?
quelle
Persönlich mag ich die Idee von Micro Services, Modulen, die durch die Anwendungsfälle definiert werden, aber wie Ihre Frage erwähnt, haben sie Anpassungsprobleme für die klassischen Unternehmen wie Banken, Versicherungen, Telekommunikation usw.
Verteilte Transaktionen sind, wie viele bereits erwähnt haben, keine gute Wahl. Die Leute setzen jetzt mehr auf letztendlich konsistente Systeme, aber ich bin nicht sicher, ob dies für Banken, Versicherungen usw. funktionieren wird.
Ich habe einen Blog über meine vorgeschlagene Lösung geschrieben, vielleicht kann dies Ihnen helfen ...
https://mehmetsalgar.wordpress.com/2016/11/05/micro-services-fan-out-transaction-problems-and-solutions-with-spring-bootjboss-and-netflix-eureka/
quelle
Eventuelle Konsistenz ist hier der Schlüssel.
Der Kommandant ist für die verteilte Transaktion verantwortlich und übernimmt die Kontrolle. Es kennt die auszuführende Anweisung und koordiniert deren Ausführung. In den meisten Szenarien gibt es nur zwei Anweisungen, es können jedoch mehrere Anweisungen verarbeitet werden.
Der Kommandant übernimmt die Verantwortung für die Gewährleistung der Ausführung aller Anweisungen, und das bedeutet, dass er in den Ruhestand geht. Wenn der Commander versucht, das Remote-Update durchzuführen, und keine Antwort erhält, erfolgt keine Wiederholung. Auf diese Weise kann das System so konfiguriert werden, dass es weniger fehleranfällig ist und sich selbst heilt.
Wenn wir erneut versuchen, haben wir Idempotenz. Idempotenz ist die Eigenschaft, etwas zweimal so tun zu können, dass die Endergebnisse dieselben sind, als ob es nur einmal getan worden wäre. Wir benötigen Idempotenz beim Remote-Dienst oder bei der Datenquelle, damit der Befehl, wenn er ihn mehr als einmal empfängt, nur einmal verarbeitet wird.
Eventuelle Konsistenz Dies löst die meisten Herausforderungen bei verteilten Transaktionen. Wir müssen hier jedoch einige Punkte berücksichtigen. Auf jede fehlgeschlagene Transaktion folgt ein erneuter Versuch. Die Anzahl der versuchten erneuten Versuche hängt vom Kontext ab.
Konsistenz ist letztendlich möglich, dh während das System während eines erneuten Versuchs nicht konsistent ist, z. B. wenn ein Kunde ein Buch bestellt, eine Zahlung geleistet und dann die Lagermenge aktualisiert hat. Wenn die Bestandsaktualisierungsvorgänge fehlschlagen und davon ausgegangen wird, dass der letzte Bestand verfügbar war, ist das Buch weiterhin verfügbar, bis der Wiederholungsvorgang für die Bestandsaktualisierung erfolgreich war. Nach erfolgreicher Wiederholung ist Ihr System konsistent.
quelle
Warum nicht die API Management (APIM) -Plattform verwenden, die Scripting / Programmierung unterstützt? So können Sie im APIM einen zusammengesetzten Dienst erstellen, ohne die Mikrodienste zu stören. Ich habe mit APIGEE für diesen Zweck entworfen.
quelle