Microservices: Umgang mit eventueller Konsistenz


22

Angenommen, wir haben eine Funktion, die das Kennwort eines Benutzers aktualisiert.

Sobald Sie auf die Schaltfläche "Passwort aktualisieren" klicken, wird ein UpdatePasswordEvent an ein Thema gesendet, bei dem drei weitere Dienste abonniert sind:

  1. Ein Dienst, der das Kennwort des Benutzers aktualisiert
  2. Ein Dienst, der den Kennwortverlauf des Benutzers aktualisiert
  3. Ein Dienst, der dem Benutzer per E-Mail mitteilt, dass sein Passwort geändert wurde.

Ausgehend von dem, was ich über eventuelle Konsistenz verstanden habe, erhalten alle diese Dienste (Verbraucher) das Ereignis zur gleichen Zeit und verarbeiten sie getrennt, was in einem guten Szenario zu einer Konsistenz der Daten führt.

Was ist jedoch, wenn ein Dienst das Ereignis nicht verarbeiten kann? zB plötzliche Trennung, Datenbankfehler usw. Was ist eine gute Vorgehensweise, um diese Transaktionsfehler zu behandeln?

Ich habe darüber nachgedacht, ein RollbackTopic zu erstellen, bei dem ein RollbackEvent in einem Thema erstellt wird, in dem "Rollback-Services" seine Arbeit erledigen und die Daten zurücksetzen, wenn ein Ereignis nicht verarbeitet werden kann


11
Sie können eine gesendete E-Mail nicht rückgängig machen :-)
Laiv

2
Weil alle von ihnen Teil desselben Dienstes sein sollten. Mikro-Service ist im Gegensatz zu Monolithen, es bedeutet nicht, dass Sie sie so wenig wie möglich "physisch" gestalten müssen. Obwohl dies nicht direkt zusammenhängt, sollten Sie diese Frage und die beiden
wichtigsten

1
Möglicherweise möchten Sie das Kennwort des Benutzers in der Datenbank synchron aktualisieren, damit Sie dem Benutzer sofort eine Rückmeldung geben und andere Dienste asynchron auslösen, indem Sie eine Nachricht senden, dass sich das Kennwort für ein Thema geändert hat, damit Ihre Nachricht nicht geändert werden muss das Passwort enthalten.
cr3

Sagt die E-Mail dem Benutzer, dass die Transaktion abgeschlossen wurde, oder teilt sie dem Benutzer mit, dass jemand (hoffentlich sie) das Passwort geändert hat. "Wenn du es nicht warst, dann musst du handeln". Wenn der 2. dann einfach jetzt eine E-Mail schicken, so gut du kannst.
Strg-Alt-Delor

Antworten:


29

Ausgehend von dem, was ich über eventuelle Konsistenz verstanden habe, erhalten alle diese Dienste (Verbraucher) das Ereignis gleichzeitig und verarbeiten sie separat, was in einem guten Szenario zu einer Konsistenz der Daten führt.

Nein, nicht unbedingt. Wie ich bereits sagte, können wir eine gesendete E-Mail nicht rückgängig machen, daher benötigen wir immer noch eine Art "Sequenz". IPC über ereignisgesteuertes Datenmanagement ist nicht von Orchestation 1 ausgenommen .

Beispielsweise sollte die E-Mail erst gesendet werden, wenn die vorherigen Transaktionen erfolgreich abgeschlossen wurden und der E-Mail-Dienst einen Beweis dafür erhält. 3

Was ist jedoch, wenn ein Dienst das Ereignis nicht verarbeiten kann? zB plötzliche Trennung, Datenbankfehler usw. Was ist eine gute Vorgehensweise, um diese Transaktionsfehler zu behandeln?

Begrüßen Sie die Irrtümer des Distributed Computing . Sie machen die Dinge kompliziert, und es gibt, wie üblich, keine Silberkugeln, die sich mit ihnen auseinandersetzen könnten.

Bevor wir uns auf die Suche nach der Verlorenen Arche machen, müssen wir zunächst die Organisation fragen. Oft liegt die Lösung darin, wie die Organisation diesen Problemen in der realen Welt begegnet .

Was machen alle (Abteilungen), wenn bestimmte Daten fehlen oder unvollständig sind?

Wir werden feststellen, dass verschiedene Abteilungen unterschiedliche Lösungen haben, die zusammen die zu implementierende Lösung darstellen.

Wie auch immer, hier einige Übungen, die uns bei der Strategie helfen könnten, zu folgen.

Endgültige Konsistenz

Anstatt sicherzustellen, dass das System ständig in einem konsistenten Zustand ist, können wir stattdessen akzeptieren, dass das System es irgendwann in der Zukunft erhalten wird. Dieser Ansatz ist besonders nützlich für langlebige Geschäftsvorgänge.

Die Art und Weise, wie das System die Konsistenz erreicht, ist von System zu System unterschiedlich. Dies kann von automatisierten Prozessen bis zu Eingriffen durch den Menschen reichen. Zum Beispiel der typische Versuch, es später noch einmal zu versuchen, oder der Kontakt mit dem Kundendienst .

Alle Operationen abbrechen

Versetzen Sie das System durch Ausgleichstransaktionen wieder in einen konsistenten Zustand . Wir müssen jedoch berücksichtigen, dass auch diese Transaktionen fehlschlagen können, was uns zu einem Punkt führen könnte, an dem es noch schwieriger ist, die Inkonsistenz zu lösen. Auch hier können wir eine gesendete E-Mail nicht rückgängig machen.

Bei einer geringen Anzahl von Transaktionen ist dieser Ansatz durchführbar, da auch die Anzahl der Kompensationstransaktionen gering ist. Wenn mehrere Geschäftstransaktionen am IPC beteiligt wären, wäre die Abwicklung einer Ausgleichstransaktion für jede dieser Transaktionen eine Herausforderung.

Wenn wir Transaktionen kompensieren wollen , werden wir feststellen , dass das Leistungsschalter-Entwurfsmuster sehr nützlich ist - und obligatorisch, würde ich sagen -

Verteilte Transaktionen

Die Idee ist, mehrere Transaktionen innerhalb einer einzigen Transaktion über einen als Transaction Manager bezeichneten übergreifenden Steuerungsprozess zu erfassen . Ein üblicher Algorithmus für die Verarbeitung verteilter Transaktionen ist das Zwei-Phasen-Festschreiben .

Das Hauptanliegen der verteilten Transaktionen ist, dass sie die Ressourcen während ihrer Lebensdauer sperren müssen, und wie wir wissen, können auch beim Transaction Manager Probleme auftreten.

Wenn der Transaktionsmanager kompromittiert wird, kann dies zu mehreren Sperren in den verschiedenen begrenzten Kontexten führen, was zu unerwartetem Verhalten aufgrund der Einreihung der Nachrichten führt. 2

Zerlegen von Operationen. Warum?

Wenn Sie ein vorhandenes System zerlegen und eine Sammlung von Konzepten finden, die wirklich innerhalb einer einzigen Transaktionsgrenze liegen sollen, lassen Sie diese möglicherweise bis zum letzten Mal bestehen.

Sam Newman

In Übereinstimmung mit den obigen Argumenten hat Sam-in seinem Buch Building Microservices - festgestellt, dass wir es vermeiden sollten, die Operation jetzt aufzuteilen, wenn wir uns die letztendliche Konsistenz wirklich nicht leisten können.

Wenn wir es uns nicht leisten können, bestimmte Vorgänge in zwei oder mehr Transaktionen aufzuteilen, kann es sein, dass diese Transaktionen wahrscheinlich zum selben begrenzten Kontext oder zumindest zu einem übergreifenden Kontext gehören, der modelliert werden muss.

In unserem Fall stellen wir zum Beispiel fest, dass die Transaktionen Nr. 1 und Nr. 2 eng miteinander verbunden sind und wahrscheinlich beide demselben begrenzten Kontext angehören können: Konten , Benutzer , Registrieren , was auch immer ...

Ziehen Sie in Betracht, beide Operationen innerhalb der Grenzen derselben Transaktion zu platzieren. Dies würde die Handhabung der gesamten Operation erleichtern. Gewichtung der Kritikalität jeder Transaktion. Wenn Transaktion 2 fehlschlägt, sollte dies wahrscheinlich nicht den gesamten Vorgang beeinträchtigen. Im Zweifelsfall wenden Sie sich an die Organisation .


1: Nicht die Art von Orchestrierung, die Sie denken. Ich spreche nicht von ESB Orchestation. Ich spreche davon, die Dienste auf das richtige Ereignis reagieren zu lassen.

2: Vielleicht finden Sie interessante Meinungen von Sam Newman zu verteilten Transaktionen.

3: Sehen Sie sich die Antwort von David Parker zu diesem Thema an.


3
Sehr gute Antwort. Ich möchte nur betonen, wie wichtig es ist, die Risiken zu berücksichtigen, die bei der Verwendung verteilter Transaktionen auftreten - hauptsächlich das Sperren von Ressourcen, was zu Deadlocks und Systemstopps führt. Bei einem E-Commerce-Produkt, an dem ich vor ca. 3 Jahren gearbeitet habe, mussten wir DTs durch Messaging-Systeme ersetzen, da das System aufgrund der Anzahl der in den Systemen verfügbaren Benutzer sehr fehleranfällig war. Probleme mit DTs treten meistens auf, wenn eine Benutzerbasis wächst.
Andy

7

In Ihrem Fall können Sie nicht alle drei Dinge auf einmal verarbeiten. Was Sie brauchen, ist ein Prozess. Hier ist ein extrem vereinfachtes Beispiel:

Befehls- und Ereignis-Orchestrierung

Es ist wichtig zu wissen, dass Zustandsänderungsoperationen immer an einer konsistenten Entität durchgeführt werden MÜSSEN. Sofern Sie keine starke Konsistenz garantieren können , muss dies in einem Stammsatz erfolgen.

Ihr System sollte sicherstellen, dass Änderungen vor dem Auslösen eines Ereignisses in Ihrem System immer mit Transaktionssicherheit durchgeführt werden müssen. Dies soll sicherstellen, dass ein ausgelöstes Ereignis wirklich eine Bestätigung dessen ist, was wirklich passiert ist.

Es gibt verschiedene knifflige Teile des Prozesses, und ich werde die offensichtlichen ignorieren - wie zum Beispiel: Was passiert, wenn Ihr Datenbankserver abstürzt, wenn ein Benutzer mit geändertem Kennwort fortbesteht? Sie geben einfach das UpdatePassword erneut aus. Einige Teile müssen jedoch von Ihnen erledigt werden, und dies sind:

  • Behandlung der Duplizierung von Nachrichten,
  • Umgang mit E-Mail-Versand.

In einem System ist Process Orchestrator (PO) nichts anderes als eine andere Entität, die - auch im wörtlichen Sinne - einen internen Zustand enthält und Übergänge zwischen den Zuständen ermöglicht, die quasi als eine Art Zustandsmaschine fungieren. Dank des internen Status können Sie die Verarbeitung von Nachrichten-Duplikaten entfernen.

Wenn sich die Bestellung in einem NewStatus befindet und verarbeitet wird UserPasswordHasBeenUpdated, ändert sich ihr Status in UserPasswordHasBeenUpdated(oder der Name des Status, der für Sie gilt). Sollte sich die Bestellung immer noch in einer befinden UserPasswordHasBeenUpdatedund eine andere UserPasswordHasBeenUpdatedankommen, würde die Bestellung die Nachricht vollständig ignorieren, da sie weiß, dass es sich um eine Duplikation handelt. Ein ähnlicher Mechanismus würde auch für andere Staaten implementiert.

Das eigentliche Versenden der E-Mail ist etwas komplizierter. Hier haben Sie zwei Möglichkeiten:

  1. senden sie es höchstens einmal,
  2. sende es mindestens einmal.

Senden Sie es höchstens einmal

Mit dieser Option wird, wenn die Bestellung den UserPasswordHistoryHasBeenSavedStatus erreicht hat, ein Befehl zum Senden einer E-Mail als Reaktion auf die Statusänderung gesendet. Ihr System würde sicherstellen, dass der UserPasswordHistoryHasBeenSavedStatus bestehen bleibt, bevor die E-Mail gesendet wird, dh eine duplizierte Nachricht würde den E-Mail-Versand nicht erneut auslösen. Mit diesem Ansatz stellen Sie sicher, dass der korrekte Status für die Bestellung gespeichert wird, können jedoch keinen nachfolgenden Vorgang garantieren.

Senden Sie es mindestens einmal

Dafür würde ich gehen.

Anstatt UserPasswordHistoryHasBeenSaveddie E-Mail als Reaktion darauf zu speichern und zu versenden, versuchen Sie zunächst, die E-Mail zu senden. Wenn der Sendevorgang fehlschlägt, wird der Status der Bestellung nie auf geändert, UserPasswordHistoryHasBeenSavedund eine andere Nachricht desselben Typs wird noch verarbeitet. Sollte das Versenden der E-Mail tatsächlich erfolgreich sein, Ihr System jedoch ausfallen, während die Bestellung mit ihrem neuen UserPasswordHistoryHasBeenSavedStatus fortbesteht , würde eine weitere Nachricht von UserPasswordHistoryHasBeenSavederneut den Befehl zum Versenden der E-Mail auslösen, und der Benutzer hätte sie mehrmals erhalten .

In Ihrem Fall möchten Sie sicherstellen, dass der Benutzer die E-Mail tatsächlich erhält. Deshalb würde ich die zweite Option der ersten vorziehen.


2

Warteschlangensysteme sind nicht so anfällig, wie Sie vielleicht denken.

Wenn wir alle drei Prozesse in eine relationale Datenbank schreiben würden, könnten wir eine Transaktion verwenden, um einen Mid-Process-Fehler zu behandeln.

Ohne die endgültige Zusage würde die Teilarbeit verworfen.

In einem Warteschlangen-Basissystem haben Sie ähnliche Optionen, wenn Sie eine Nachricht aus der Warteschlange lesen, um mittlere Prozessfehler zu behandeln.

Amazon SQS beispielsweise blendet gelesene Nachrichten einfach aus. Wird kein endgültiger Löschbefehl gesendet, wird die Nachricht erneut angezeigt oder in eine Warteschlange für nicht zustellbare Nachrichten gestellt.

Sie können ähnliche "Transaktionen" auf verschiedene Arten implementieren, indem Sie im Wesentlichen eine Kopie der Nachricht aufbewahren, bis Sie eine Bestätigung über die erfolgreiche Verarbeitung erhalten. Wenn die Bestätigung nicht rechtzeitig eingeht. Sie können die Nachricht erneut senden oder zur manuellen Bearbeitung aufbewahren.

Möglicherweise können Sie einen "Rollback-Service" erstellen, der diese fehlerhaften Nachrichten überwacht, über verwandte Nachrichten und den vorherigen Status informiert und einen Rollback durchführt.

Jedoch! Es ist normalerweise besser, die fehlerhaften Nachrichten erneut zu senden. Immerhin sind dies eher Randfälle. Entweder ist ein Server katastrophal ausgefallen oder es ist ein Fehler bei der Behandlung eines bestimmten Nachrichtentyps aufgetreten.

Sobald der Fehler gemeldet wurde, kann der Dienst repariert und die Nachrichten erfolgreich verarbeitet werden. Bringen Sie das gesamte System wieder in einen konsistenten Zustand.


2

Was Sie hier sehen, ist das Problem der beiden Generäle . Im Wesentlichen: Wie können Sie sicher sein, dass eine Nachricht empfangen wird und eine Antwort auf diese Nachricht erfolgt? In vielen Fällen gibt es keine perfekte Lösung. Tatsächlich ist es in einem verteilten System oftmals unmöglich, Nachrichten genau einmal zuzustellen.

Eine erste offensichtliche Bemerkung ist, dass der Dienst, der das Passwort ändert, das Passwortänderungsereignis senden sollte. Auf diese Weise werden der Kennwortverlauf und die E-Mail-Sendedienste nur ausgelöst, wenn sich das Kennwort tatsächlich ändert, unabhängig davon, warum es geändert wurde.

Um Ihr Problem tatsächlich zu lösen, würde ich keine verteilten Transaktionen in Betracht ziehen, sondern in die Richtung einer mindestens einmaligen Nachrichtenübermittlung und einer idempotenten Verarbeitung schauen.

  • Mindestens einmal

    Um sicherzustellen, dass das Kennwortänderungsereignis tatsächlich von allen Verbrauchern gesehen wird, müssen Sie einen dauerhaften Kommunikationskanal verwenden, über den Nachrichten "mindestens einmal" verarbeitet werden können. Die Konsumenten erkennen eine Nachricht erst dann als konsumiert an, wenn sie vollständig verarbeitet wurde. Wenn beispielsweise der Kennwortverlaufsdienst beim Schreiben eines Verlaufseintrags abstürzt, wird nach dem Neustart dasselbe Kennwortänderungsereignis erneut gelesen und ein erneuter Versuch unternommen, wobei dieses Ereignis als schreibgeschützt anerkannt wird, nachdem es selbst in den Verlauf geschrieben wurde. Sie sollten eine Message Queue-Lösung basierend auf ihrer Fähigkeit auswählen, Nachrichten erneut zu senden, bis sie bestätigt werden.

  • Idempotenz

    Nach der mindestens einmaligen Zustellung besteht das Problem, dass doppelte Aktionen auftreten, wenn eine Nachricht teilweise verarbeitet wurde, bevor der Verbraucher unterbrochen und später erneut verarbeitet wurde. Das sollte gelöst werden, indem jeder Dienst so gestaltet wird, dass er idempotent ist. Entweder können die von ihm ausgeführten Schreibvorgänge mehrmals ohne nachteilige Auswirkungen ausgeführt werden, oder es behält seinen eigenen Speicher für die von ihm ausgeführten Aktionen und vermeidet, eine Aktion mehrmals auszuführen. Beim Versenden von E-Mails werden Sie feststellen, dass es sich wahrscheinlich nicht lohnt, ein idempotentes Verhalten zu erreichen, und dass gelegentlich eine E-Mail zweimal gesendet wird.

Seien Sie auf jeden Fall vorsichtig, wie klein Sie Ihre Dienste machen. Muss Ihr Kennwortverlaufsdienst wirklich vom Kennwortänderungsdienst unabhängig sein?


1

Ich bin mit vielen Antworten nicht einverstanden.

  1. E-Mail jetzt senden „Jemand hat Ihr Passwort geändert. Wenn du es warst, musst du nichts tun. Wenn nicht Panik. “Dies wird eintreffen, wenn es eintrifft.
  2. Änder das Passwort. Sie haben jedoch letztendlich Konsistenz. Sie möchten sicherstellen, dass in dieser Sitzung vom Benutzer vorgenommene Änderungen angezeigt werden.

Es gibt andere Konsistenzversprechen, die Sie hinzufügen können.

  • Stellen Sie sicher, dass Änderungen in der zeitlichen Reihenfolge vorgenommen werden.
  • Stellen Sie sicher, dass einem Benutzer kein Rollback angezeigt wird, anderen Benutzern die Änderung jedoch möglicherweise weiterhin nicht angezeigt wird.
  • Da sind andere

Diese zusätzlichen Konsistenzen müssen abhängig von den Taten der Anwendung implementiert werden.


Ich habe keine Ahnung, was Sie unter "Verlauf aktualisieren" verstehen, aber bitte ändern Sie niemals den Verlauf. Wenn Sie nur die DAG erweitern, sollte dies zu einer Änderung des aktuellen Status führen. Sie sind nicht unabhängig. Wenn dem so ist, kann man sich nicht darauf verlassen, dass die Geschichte widerspiegelt, was passiert ist. (und last but not least, Passwörter nicht speichern, siehe wie man Passwörter nicht speichert )


Wenn Sie die E-Mail zu Beginn senden können, ist Ihr Ansatz in Ordnung. Wenn Sie etwas zusammen mit der E-Mail senden müssen. Möglicherweise eine Art Link / Daten, die erst nach Erreichen der Konsistenz abgerufen werden können, dann können Sie die E-Mail nicht erst senden. Das habe ich als kommentiert consider asking the organization first.. Sie haben wahrscheinlich recht. Es hat sich jedoch als wichtig erwiesen, die Ereignisse zu konditionieren, die nicht rückgängig gemacht werden können. Zum Beispiel Benachrichtigungen an den Endbenutzer. Eine Benachrichtigung, die sich auf den tatsächlichen Zustand der Benutzerdaten bezieht, kann einen schlechten Eindruck hinterlassen.
Laiv,

Das heißt, für dieses spezielle Szenario (Benachrichtigung über Kennwortänderung) stimmte ich diesem Ansatz zu. Sobald es den Anforderungen entspricht.
Laiv
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.