Bearbeiten

Muster „Kompensierende Transaktion“

Azure

Wenn Sie einen letztlich konsistenten Vorgang verwenden, der aus einer Reihe von Schritten besteht, kann das Muster kompensierende Transaktion nützlich sein. Insbesondere wenn einer oder mehrere der Schritte fehlschlagen, können Sie das Muster kompensierende Transaktion verwenden, um die Arbeit rückgängig zu machen, die die Schritte ausgeführt haben. Vorgänge, die dem Modell der letztlichen Konsistenz folgen, sind häufig in cloudbasierten Anwendungen anzutreffen, die komplexe Geschäftsprozesse und -workflows implementieren.

Kontext und Problem

Anwendungen, die in der Cloud ausgeführt werden, ändern häufig Daten. Diese Daten können auf verschiedene Datenquellen verteilt sein, die sich an unterschiedlichen geographischen Standorten befinden. Um in einer verteilten Umgebung Konflikte zu vermeiden und die Leistung zu verbessern, sollte eine Anwendung nicht dafür eingesetzt werden, eine hohe Transaktionskonsistenz zu gewährleisten. Vielmehr sollte die Anwendung letztliche Konsistenz implementieren. In diesem Modell besteht ein typischer Geschäftsvorgang aus einer Reihe von separaten Schritten. Während der Vorgang diese Schritte ausführt, kann die Gesamtansicht des Systemzustands inkonsistent sein. Wenn der Vorgang jedoch abgeschlossen ist und alle Schritte ausgeführt wurden, sollte das System wieder konsistent werden.

Der Datenkonsistenzprimer liefert Informationen darüber, warum verteilte Transaktionen nicht gut skaliert werden können. Diese Ressource listet auch die Prinzipien des letztlichen Konsistenzmodells auf.

Eine Herausforderung beim Modell der letztlichen Konsistenz stellt die Frage dar, wie mit einem fehlerhaften Schritt umgegangen wird. Nach einem Fehler müssen Sie möglicherweise alle Arbeiten rückgängig machen, die die vorherigen Schritte im Vorgang abgeschlossen haben. Für die Daten kann jedoch nicht ohne Weiteres ein Rollback ausgeführt werden, da andere gleichzeitige Instanzen der Anwendung diese möglicherweise geändert haben. Selbst in Fällen, in denen gleichzeitige Instanzen die Daten nicht geändert haben, kann das Rückgängigmachen eines Schritts komplexer sein als das Wiederherstellen des ursprünglichen Zustands. Es kann erforderlich sein, verschiedene geschäftsspezifische Regeln anzuwenden. Ein Beispiel finden Sie auf der Reisewebsite, die im Abschnitt Beispiel weiter unten in diesem Artikel beschrieben wird.

Wenn sich ein Vorgang, der letztliche Konsistenz implementiert, über mehrere heterogene Datenspeicher erstreckt, muss zum Rückgängigmachen der Schritte des Vorgangs nacheinander jeder einzelne Datenspeicher aufgerufen werden. Um zu verhindern, dass das System inkonsistent bleibt, müssen Sie die Arbeit, die Sie in jedem Datenspeicher ausgeführt haben, zuverlässig rückgängig machen.

Nicht alle Daten, die von einem Vorgang, der letztliche Konsistenz implementiert, betroffen sind, können in einer Datenbank gespeichert werden. Betrachten Sie beispielsweise eine SOA-Umgebung (Service-Oriented Architecture). Ein SOA-Vorgang kann eine Aktion in einem Dienst aufrufen und eine Änderung des Zustands bewirken, der von diesem Dienst gehalten wird. Um den Vorgang rückgängig zu machen, müssen Sie auch diese Zustandsänderung rückgängig machen. Dieser Prozess kann den Dienst erneut aufrufen und eine weitere Aktion ausführen, die die Auswirkungen der ersten Aktion rückgängig macht.

Lösung

Die Lösung besteht darin, eine kompensierende Transaktion zu implementieren. Die Schritte einer kompensierenden Transaktion müssen die Auswirkungen der Schritte des ursprünglichen Vorgangs rückgängig machen. Ein intuitiver Ansatz besteht darin, den aktuellen Zustand durch den Zustand zu ersetzen, in dem sich das System zu Beginn des Vorgangs befand. Eine kompensierende Transaktion kann diesen Ansatz jedoch nicht immer verwenden, da sie Möglicherweise Änderungen überschreibt, die andere gleichzeitige Instanzen einer Anwendung vorgenommen haben. Stattdessen muss eine kompensierende Transaktion ein intelligenter Prozess sein, bei dem alle Arbeiten berücksichtigt werden, die gleichzeitige Instanzen ausführen. Dieser Prozess ist in der Regel anwendungsspezifisch und hängt von der Art der Aufgaben ab, die im ursprünglichen Vorgang durchgeführt wurden.

Eine übliche Vorgehensweise besteht darin, einen Workflow einzusetzen, um einen letztlich konsistenten Vorgang zu implementieren, der eine Kompensierung erfordert. Im weiteren Verlauf des ursprünglichen Vorgangs zeichnet das System Informationen über jeden einzelnen Schritt auf und zeigt an, wie die in diesem Schritt durchgeführten Aufgaben rückgängig gemacht werden können. Wenn beim Vorgang an irgendeiner Stelle ein Fehler auftritt, geht der Workflow die abgeschlossenen Schritte rückwärts durch und führt die Aufgabe zum Rückgängigmachen jedes Schritts durch. Bei jedem Schritt führt der Workflow die Arbeit aus, die diesen Schritt umkehrt.

Zwei wichtige Punkte sind:

  • Eine ausgleichende Transaktion muss die Arbeit möglicherweise nicht in der genau umgekehrten Reihenfolge des ursprünglichen Vorgangs rückgängig machen.
  • Es kann möglich sein, einige der Rückgängig-Schritte parallel auszuführen.

Diese Vorgehensweise ähnelt der Sagas-Strategie, die im Blog von Clemens Vasters diskutiert wird.

Eine kompensierende Transaktion ist auch ein letztlich konsistenter Vorgang und kann auch Fehler verursachen. Das System sollte in der Lage sein, die kompensierende Transaktion an der Stelle, an der der Fehler aufgetreten ist, wieder aufzunehmen und fortzusetzen. Da ein fehlerhafter Schritt eventuell wiederholt werden muss, sollten die Schritte in einer kompensierenden Transaktion als idempotente Befehle definiert werden. Weitere Informationen finden Sie unter Idempotenzmuster im Blog von Jonathan Oliver.

In einigen Fällen kann ein manueller Eingriff die einzige Möglichkeit sein, eine Wiederherstellung nach einem fehlgeschlagenen Schritt durchzuführen. In diesen Situationen sollte das System eine Warnung auslösen und so viele Informationen wie möglich über die Ursache des Fehlers bereitstellen.

Probleme und Überlegungen

Beachten Sie die folgenden Punkte bei Ihrer Entscheidung, wie dieses Muster implementiert werden soll:

  • Es mag nicht einfach sein, festzustellen, wann bei einem Schritt in einem Vorgang, der letztliche Konsistenz implementiert, ein Fehler aufgetreten ist. Ein Schritt schlägt möglicherweise nicht sofort fehl. Stattdessen kann es blockiert werden. Möglicherweise müssen Sie einen Timeoutmechanismus implementieren.

  • Es ist nicht einfach, die Kompensationslogik zu generalisieren. Eine kompensierende Transaktion ist anwendungsspezifisch. Sie setzt voraus, dass die Anwendung über ausreichende Informationen verfügt, um die Auswirkungen jedes einzelnen Schrittes in einem fehlerhaften Vorgang rückgängig machen zu können.

  • Sie sollten die Schritte einer kompensierenden Transaktion als idempotente Befehle definieren. Anschließend können die Schritte wiederholt werden, wenn die kompensierende Transaktion selbst fehlschlägt.

  • Die Infrastruktur, die die Schritte verarbeitet, muss die folgenden Kriterien erfüllen:

    • Sie ist im ursprünglichen Vorgang und in der ausgleichenden Transaktion resilient.
    • Es gehen nicht die Informationen verloren, die erforderlich sind, um einen fehlerhaften Schritt zu kompensieren.
    • Sie überwacht zuverlässig den Fortschritt der Kompensationslogik.
  • Eine kompensierende Transaktion gibt die Daten im System nicht unbedingt in dem Zustand zurück, in dem sich diese zu Beginn des ursprünglichen Vorgangs befanden. Stattdessen kompensiert die Transaktion die Arbeit, die der Vorgang erfolgreich abgeschlossen hat, bevor er fehlgeschlagen ist.

  • Die Reihenfolge der Schritte in der kompensierenden Transaktion muss nicht unbedingt in der genau gegenteiligen Reihenfolge der Schritte im ursprünglichen Vorgang erfolgen. Beispielsweise kann ein Datenspeicher sensibler gegenüber Inkonsistenzen sein als ein anderer. Die Schritte in der kompensierenden Transaktion, die die Änderungen an diesem Speicher rückgängig machen, sollten zuerst erfolgen.

  • Bestimmte Measures können dazu beitragen, die Wahrscheinlichkeit zu erhöhen, dass die Gesamtaktivität erfolgreich ist. Insbesondere können Sie für jede Ressource, die zum Abschließen eines Vorgangs erforderlich ist, eine kurzfristige, timeoutbasierte Sperre festlegen. Sie können diese Ressourcen auch im Voraus erhalten. Führen Sie dann die Arbeit erst aus, nachdem Sie alle Ressourcen abgerufen haben. Schließen Sie alle Aktionen ab, bevor die Sperren ablaufen.

  • Wiederholungslogik, die mehr verzeihend als üblich ist, kann dazu beitragen, Fehler zu minimieren, die eine kompensierende Transaktion auslösen. Wenn bei einem Schritt eines Vorgangs zur Implementierung von letztlicher Konsistenz ein Fehler auftritt, behandeln Sie den Fehler als vorübergehende Ausnahme, und wiederholen Sie den Schritt. Beenden Sie den Vorgang, und initiieren Sie eine kompensierende Transaktion, wenn ein Schritt wiederholt fehlschlägt oder nicht wiederhergestellt werden kann.

  • Wenn Sie eine kompensierendeTransaktion implementieren, stehen Ihnen viele der gleichen Herausforderungen gegenüber, mit denen Sie konfrontiert sind, wenn Sie letztliche Konsistenz implementieren. Weitere Informationen hierzu finden Sie im Datenkonsistenzprimer im Abschnitt „Überlegungen zur Implementierung von letztlicher Konsistenz“.

Verwendung dieses Musters

Verwenden Sie dieses Muster nur bei Vorgängen, die bei auftretenden Fehlern rückgängig gemacht werden müssen. Entwerfen Sie möglichst Lösungen, mit denen Sie die Komplexität kompensierender Transaktionen vermeiden können.

Workloadentwurf

Ein Architekt sollte evaluieren, wie das Compensating Transaction-Pattern im Design seines Workloads verwendet werden kann, um die Ziele und Prinzipien zu adressieren, die in den Azure Well-Architected Framework-Säulen behandelt werden. Zum Beispiel:

Säule So unterstützt dieses Muster die Säulenziele
Zuverlässigkeitsdesignentscheidungen tragen dazu bei, dass Ihre Workload ausfallsicher wird und dass sie nach einem Ausfall wieder in einen voll funktionsfähigen Zustand zurückkehrt. Kompensationsmaßnahmen beheben Fehlfunktionen in kritischen Workload-Pfaden, indem sie Prozesse wie das direkte Zurücksetzen von Datenänderungen, das Aufheben von Transaktionssperren oder sogar die Ausführung nativen Systemverhaltens nutzen, um den Effekt umzukehren.

- RE:02 Kritische Flows
- RE:09 Notfallwiederherstellung

Berücksichtigen Sie wie bei jeder Designentscheidung alle Kompromisse im Hinblick auf die Ziele der anderen Säulen, die mit diesem Muster eingeführt werden könnten.

Beispiel

Kunden verwenden eine Reisewebsite, um Reiserouten zu buchen. Eine einzelne Reiseroute kann aus einer Reihe von Flügen und Hotels bestehen. Ein Kunde, der von Seattle nach London und dann weiter nach Paris reist, könnte die bei der Erstellung einer Reiseroute folgenden Schritte durchführen:

  1. Buchen Sie einen Sitzplatz auf dem Flug F1 von Seattle nach London.
  2. Buchen Sie einen Sitzplatz auf dem Flug F2 von London nach Paris.
  3. Buchen Sie einen Sitzplatz auf dem Flug F3 von Paris nach Seattle.
  4. Reservieren Sie ein Zimmer im Hotel H1 in London.
  5. Reservieren Sie ein Zimmer im Hotel H2 in Paris.

Diese Schritte bilden einen letztlich konsistenten Vorgang, obwohl jeder Schritt eine separate Aktion darstellt. Neben diesen Schritten muss das System auch die Zählervorgänge zum Rückgängigmachen der einzelnen Schritte aufzeichnen. Diese Informationen sind für den Fall erforderlich, dass der Kunde die Reiseroute storniert. Die zur Durchführung der entgegengesetzten Vorgänge erforderlichen Schritte können dann als kompensierende Transaktion ausgeführt werden.

Die Schritte in der kompensierenden Transaktion sind möglicherweise nicht das genaue Gegenteil der ursprünglichen Schritte. Außerdem muss die Logik in jedem Schritt der ausgleichenden Transaktion geschäftsspezifische Regeln berücksichtigen. Wenn Sie beispielsweise eine Flugreservierung stornieren, berechtigt der Kunde möglicherweise nicht zu einer vollständigen Rückerstattung.

Die folgende Abbildung zeigt die Schritte in einer lang andauernden Transaktion zum Buchen einer Reiseroute. Sie können auch die kompensierenden Transaktionsschritte anzeigen, die die Transaktion rückgängig machen.

Diagramm, das die Schritte zum Erstellen einer Reiseroute zeigt. Die Schritte der kompensierenden Transaktion, die die Reiseroute storniert, werden ebenfalls angezeigt.

Hinweis

Abhängig vom Entwurf der Kompensationslogik für die einzelnen Schritte können die Schritte der kompensierenden Transaktion parallel ausgeführt werden.

In vielen Unternehmenslösungen ist beim Auftreten eines Fehlers bei einem einzelnen Schritt nicht immer ein Rollback des Systems mit einer kompensierenden Transaktion erforderlich. Nehmen wir zum Beispiel das Szenario einer Reisewebsite. Angenommen, der Kunde bucht die Flüge F1, F2 und F3, kann aber kein Zimmer im Hotel H1 reservieren. Es ist besser, dem Kunden ein Zimmer in einem anderen Hotel in derselben Stadt anzubieten, als die Flüge zu stornieren. Der Kunde kann sich trotzdem für eine Kündigung entscheiden. In diesem Fall wird die kompensierende Transaktion ausgeführt und hebt die Buchungen für die Flüge F1, F2 und F3 auf. Aber der Kunde sollte diese Entscheidung treffen, nicht das System.

Nächste Schritte

  • Data Consistency Primer (Grundlagen der Datenkonsistenz). Das Muster „Kompensierende Transaktion“ wird häufig verwendet, um Vorgänge rückgängig zu machen, die das Modell der letztlichen Konsistenz implementieren. Dieser Primer liefert Informationen über die Vor- und Nachteile von letztlicher Konsistenz.
  • Idempotenz-Muster In einer kompensierenden Transaktion empfiehlt es sich, idempotente Befehle zu verwenden. In diesem Blogbeitrag werden Faktoren beschrieben, die bei der Implementierung von Idempotenz zu berücksichtigen sind.
  • Scheduler Agent Supervisor-Muster. Beschreibt die Implementierung stabiler Systeme, die Geschäftsvorgänge durchführen, bei denen verteilte Dienste und Ressourcen zum Einsatz kommen. In diesen Systemen müssen Sie manchmal eine kompensierende Transaktion verwenden, um die Von einem Vorgang ausgeführte Arbeit rückgängig zu machen.
  • Wiederholungsmuster: Die Kompensierung von Transaktionen kann rechenintensiv sein. Sie können versuchen, ihre Verwendung zu minimieren, indem Sie das Wiederholungsmuster verwenden, um eine effektive Richtlinie zum Wiederholen fehlgeschlagener Vorgänge zu implementieren.
  • Saga-Muster für verteilte Transaktionen. In diesem Artikel wird erläutert, wie Sie das Saga-Muster verwenden, um die Datenkonsistenz über Microservices hinweg in Szenarien mit verteilten Transaktionen zu verwalten. Das Saga-Muster behandelt die Fehlerwiederherstellung mit kompensierenden Transaktionen.
  • Muster „Pipes und Filter“. In diesem Artikel wird das Muster Pipes and Filters beschrieben, mit dem Sie eine komplexe Verarbeitungsaufgabe in eine Reihe wiederverwendbarer Elemente zerlegen können. Die Verwendung des Musters „Pipes und Filter“ in Verbindung mit dem Muster „Kompensierende Transaktion“ ist eine alternative Vorgehensweise zur Implementierung verteilter Transaktionen.
  • Entwurf mit Blick auf Selbstreparatur. In diesem Leitfaden wird erläutert, wie Sie Selbstreparaturanwendungen entwerfen. Sie können kompensierende Transaktionen als Teil eines Selbstreparaturansatzes verwenden.