it-swarm.com.de

Transaktionen über REST Microservices?

Nehmen wir an, wir haben ein Benutzer-, Wallet REST -Microservices und ein API-Gateway, das die Dinge miteinander verbindet. Wenn sich Bob auf unserer Website registriert, muss unser API-Gateway einen Benutzer über den User-Microservice und eine Geldbörse über den Wallet-Microservice erstellen. 

Hier sind einige Szenarien, in denen Dinge schief gehen könnten:

  • User Bob-Erstellung schlägt fehl: Das ist in Ordnung, wir senden einfach eine Fehlermeldung an den Bob zurück. Wir verwenden SQL-Transaktionen, sodass niemand Bob im System gesehen hat. Alles ist gut :)

  • Benutzer Bob wurde erstellt, aber bevor unser Wallet erstellt werden kann, stürzt unser API-Gateway stark ab. Wir haben jetzt einen Benutzer ohne Geldbörse (inkonsistente Daten).

  • Benutzer Bob wird erstellt, und während wir das Wallet erstellen, wird die HTTP-Verbindung abgebrochen. Die Brieftaschenerstellung war möglicherweise erfolgreich oder nicht erfolgreich.

Welche Lösungen gibt es, um zu verhindern, dass diese Art von Dateninkonsistenz auftritt? Gibt es Muster, mit denen Transaktionen mehrere REST -Anforderungen umfassen können? Ich habe die Wikipedia-Seite zu Zwei-Phasen-Commit gelesen, die dieses Thema zu berühren scheint, aber ich bin nicht sicher, wie ich es in der Praxis anwenden soll. Diese Atomic Distributed Transactions: ein RESTful Design Papier erscheint auch interessant, obwohl ich es noch nicht gelesen habe.

Alternativ weiß ich, dass REST für diesen Anwendungsfall möglicherweise nicht geeignet ist. Wäre es vielleicht der richtige Weg, um mit dieser Situation fertig zu werden, REST vollständig zu verwerfen 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 indem in meinem Benutzermodell ein Attribut "state" mit Werten für "Erstellen", "erstellte" usw.) angezeigt wird?

145
Olivier Lalonde

Was nicht sinnvoll ist:

  • verteilte Transaktionen mit REST Services. REST -Dienste sind definitionsgemäß zustandslos. Daher sollten sie nicht an einer Transaktionsgrenze teilnehmen, die mehr als einen Dienst umfasst. Ihr Anwendungsszenario für die Benutzerregistrierung ist sinnvoll, aber das Design mit REST - Microservices zum Erstellen von Benutzer- und Wallet-Daten ist nicht gut. 

Was wird Ihnen Kopfschmerzen bereiten: 

  • EJBs mit verteilten Transaktionen. Es ist eines dieser Dinge, die theoretisch funktionieren, aber nicht in der Praxis. Im Moment versuche ich, eine verteilte Transaktion für Remote-EJBs über JBoss EAP 6.3-Instanzen hinweg zu ermöglichen. Wir haben seit Wochen mit dem RedHat-Support gesprochen, und es hat noch nicht funktioniert. 
  • Zweiphasige Commit-Lösungen im Allgemeinen. Ich denke, das 2PC-Protokoll ist ein großartiger Algorithmus (vor vielen Jahren habe ich ihn in C mit RPC implementiert). Es erfordert umfassende Fail-Recovery-Mechanismen mit Wiederholungsversuchen, Status-Repository usw. Die gesamte Komplexität ist im Rahmen der Transaktion verborgen (z. B. JBoss Arjuna). 2PC ist jedoch nicht ausfallsicher. Es gibt Situationen, die die Transaktion einfach nicht abschließen kann. Anschließend müssen Sie Datenbankinkonsistenzen manuell identifizieren und beheben. Wenn Sie Glück haben, kann es einmal in einer Million Transaktionen vorkommen, aber je nach Plattform und Szenario alle 100 Transaktionen. 
  • Sagas (Ausgleichsgeschäfte). Der Implementierungsaufwand für die Erstellung der Kompensationsoperationen und der Koordinierungsmechanismus zur Aktivierung der Kompensation am Ende sind gegeben. Aber auch eine Entschädigung ist nicht ausfallsicher. Es kann immer noch Inkonsistenzen geben (= einige Kopfschmerzen). 

Was ist wahrscheinlich die beste Alternative:

  • Eventuelle Konsistenz. Weder ACID-ähnliche verteilte Transaktionen noch kompensierende Transaktionen sind fehlersicher, und beide können zu Inkonsistenzen führen. Die eventuelle Konsistenz ist oft besser als "gelegentliche Inkonsistenz". Es gibt verschiedene Design-Lösungen, wie:
    • Sie können unter Verwendung der asynchronen Kommunikation eine robustere Lösung erstellen. Wenn sich Bob in Ihrem Szenario registriert, kann das API-Gateway eine Nachricht an eine Warteschlange für neue Benutzer senden und dem Benutzer sofort antworten, dass er "Sie erhalten eine E-Mail zur Bestätigung der Kontoerstellung." Ein Warteschlangendienendienst könnte die Nachricht verarbeiten, die Datenbankänderungen in einer einzigen Transaktion durchführen und die E-Mail an Bob senden, um die Kontoerstellung zu benachrichtigen. 
    • Der Benutzer-Microservice erstellt den Benutzerdatensatz und einen Wallet-Datensatz in derselben Datenbank. In diesem Fall ist der Wallet-Speicher im Benutzer-Microservice eine Replik des Master-Wallet-Speichers, die nur für den Wallet-Microservice sichtbar ist. Es gibt einen Datensynchronisationsmechanismus, der auf Triggern basiert oder periodisch eintritt, um Datenänderungen (z. B. neue Wallets) von der Replik an den Master zu senden und umgekehrt.

Aber was ist, wenn Sie synchrone Antworten benötigen?

  • Die Microservices umgestalten. Wenn die Lösung mit der Warteschlange nicht funktioniert, weil der Service-Konsument sofort eine Antwort benötigt, sollte ich die Benutzer- und Wallet-Funktionalität umgestalten, um sie in demselben Service (oder zumindest in derselben VM) zu lokalisieren. verteilte Transaktionen zu vermeiden). Ja, es ist ein Schritt weiter von Mikrodienstleistungen und näher an einem Monolithen, aber es wird Ihnen Kopfschmerzen ersparen. 
107
Paulo Merson

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 während der Ausführung der Aufgabe bei? Beim Hochleistungsrechnen vermeiden wir heutzutage zweiphasige Festschreibungen. Ich habe vor vielen Jahren eine Zeitung über das sogenannte "Starbuck-Modell" für Transaktionen gelesen: Denken Sie an den Vorgang des Bestellens, Bezahlens, Zubereitens und Empfangens des bei Starbuck bestellten Kaffees. Ich vereinfache die Dinge, aber ein Zwei-Phasen-Festschreibungsmodell würde dies tun Schlagen Sie vor, dass der gesamte Vorgang eine einzelne Verpackungstransaktion für alle beteiligten 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 bekommen. Siehst du das Bild?

Stattdessen ist das "Starbuck-Modell" produktiver, indem es dem "Best Effort" -Modell folgt und Fehler im Prozess ausgleicht. Zuerst stellen sie sicher, dass Sie bezahlen! Dann gibt es Nachrichtenwarteschlangen mit Ihrer Bestellung an der Tasse. Wenn dabei etwas schief geht, z. B. wenn Sie Ihren Kaffee nicht bekommen haben, er nicht von Ihnen bestellt wurde usw., nehmen wir an der Entschädigung teil und sorgen dafür, dass Sie das bekommen, was Sie möchten oder erstatten. Dies ist das effizienteste Modell für mehr Produktivität.

Manchmal verschwendet Starbuck einen Kaffee, aber der gesamte Prozess ist effizient. Wenn Sie Ihre Web-Services so gestalten, dass sie beliebig oft aufgerufen werden können und dennoch dasselbe Endergebnis liefern, sollten Sie andere Tricks beachten. Meine Empfehlung lautet also:

  • Seien Sie nicht zu fein bei der Definition Ihrer Web-Services (ich bin nicht überzeugt von dem Mikro-Service-Hype in diesen Tagen: zu viele Risiken, zu weit zu gehen);

  • Async erhöht die Leistung, also lieber async, Benachrichtigungen per E-Mail senden, wann immer dies möglich ist.

  • Erstellen Sie intelligentere Services, um sie beliebig oft "abrufbar" zu machen. Verarbeiten Sie sie mit einer UID oder Task-ID, die der Reihenfolge von unten nach oben bis zum Ende folgt, und überprüfen Sie die Geschäftsregeln in jedem Schritt.

  • Verwenden Sie Nachrichtenwarteschlangen (JMS oder andere) und leiten Sie sie zu Fehlerbehandlungsprozessoren um, die Operationen auf "Rollback" anwenden, indem Sie entgegengesetzte Operationen anwenden. Übrigens erfordert die Arbeit mit der asynchronen Reihenfolge eine Art Warteschlange, um den aktuellen Status des Prozesses zu validieren. so bedenke das;

  • In letzter Instanz (da dies möglicherweise nicht häufig vorkommt) stellen Sie es in eine Warteschlange für die manuelle Verarbeitung von Fehlern.

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:

  1. Rufen Sie den Microservice zur Kontoerstellung an, übergeben Sie ihm einige Informationen und eine eindeutige Aufgaben-ID. 1.1 Der Microservice zur Kontoerstellung prüft zunächst, ob das Konto bereits erstellt wurde. Eine Aufgaben-ID ist dem Datensatz des Kontos zugeordnet. Der Microservice erkennt, dass das Konto nicht vorhanden ist, erstellt es und speichert die Aufgaben-ID. HINWEIS: Dieser Dienst kann 2000-mal aufgerufen werden. Er führt immer das gleiche Ergebnis aus. Der Dienst antwortet mit einer "Quittung, die nur minimale Informationen enthält, um einen Vorgang rückgängig zu machen, falls erforderlich".

  2. Rufen Sie die Wallet-Erstellung auf und geben Sie ihr die Konto- und Aufgaben-ID. Angenommen, eine Bedingung ist ungültig und die Erstellung der Brieftasche kann nicht durchgeführt werden. Der Aufruf wird mit einem Fehler zurückgegeben, es wurde jedoch nichts erstellt.

  3. Der Orchestrator wird über den Fehler informiert. Es weiß, dass es die Kontoerstellung abbrechen muss, aber es wird es nicht selbst tun. Er fordert den Brieftaschendienst auf, dies zu tun, indem er den am Ende von Schritt 1 eingegangenen "Minimal-Undo-Beleg" weiterleitet.

  4. Der Kontodienst liest den Beleg zum Rückgängigmachen und weiß, wie der Vorgang rückgängig gemacht wird. Der Rückgängig-Beleg kann sogar Informationen über einen anderen Mikrodienst enthalten, den er selbst zur Ausführung eines Teils des Auftrags hätte aufrufen können. 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 zur Vereinfachung einfach das Konto mit seiner Konto-ID.

  5. Angenommen, der Webdienst hat nie den Erfolg oder Misserfolg (in diesem Fall) erhalten, dass die Kontoerstellung rückgängig gemacht wurde. Der Dienst zum Rückgängigmachen des Kontos wird einfach erneut aufgerufen. Und dieser Dienst sollte normalerweise niemals fehlschlagen, da sein Ziel darin besteht, dass der Account nicht mehr existiert. Es prüft also, ob es existiert und sieht, dass nichts unternommen werden kann, um es rückgängig zu machen. Die Operation ist also ein Erfolg.

  6. 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 eine andere Art und Weise verwalten und den Fall in eine an den Helpdesk gerichtete Nachrichtenwarteschlange stellen können, 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 nicht genug vorhanden ist Das Back-End-System konnte mit Hooks ausgestattet werden, um Situationen zu korrigieren. Das Helpdesk empfing Nachrichten, die die erfolgreich durchgeführten Vorgänge enthielten, und verfügte über genügend Informationen, um Probleme zu beheben.

Ich habe eine Suche durchgeführt und die Microsoft-Website enthält eine Musterbeschreibung für diesen Ansatz. Es wird das kompensierende Transaktionsmuster genannt:

Ausgleichstransaktionsmuster

51
user8098437

Alle verteilten Systeme haben Probleme mit der Transaktionskonsistenz. Der beste Weg, dies zu tun, ist, wie Sie sagten, ein zweiphasiges Commit. Haben Sie die Brieftasche und den Benutzer in einem ausstehenden Zustand erstellt. Führen Sie nach der Erstellung einen separaten Anruf aus, um den Benutzer zu aktivieren.

Dieser letzte Anruf sollte sicher wiederholbar sein (falls Ihre Verbindung abbricht). 

Dies erfordert, dass der letzte Aufruf beide Tabellen kennt (damit er in einer einzigen JDBC-Transaktion ausgeführt werden kann). 

Alternativ möchten Sie vielleicht darüber nachdenken, warum Sie sich so Sorgen um einen Benutzer ohne Geldbörse machen. Glauben Sie, dass dies ein Problem verursachen wird? Wenn dies der Fall ist, ist es vielleicht eine schlechte Idee, diese als separate Ruhezustände zu haben. Wenn ein Benutzer ohne Brieftasche nicht existieren sollte, sollten Sie die Brieftasche wahrscheinlich dem Benutzer hinzufügen (im ursprünglichen POST - Aufruf zum Erstellen des Benutzers).

25
Rob Conklin

IMHO Einer der Schlüsselaspekte der Microservices-Architektur ist, dass die Transaktion auf den einzelnen Microservice beschränkt ist (Single-Responsibility-Prinzip). 

Im vorliegenden Beispiel wäre die Benutzererstellung eine eigene Transaktion. Die Erstellung eines Benutzers würde ein USER_CREATED -Ereignis in eine Ereigniswarteschlange verschieben. Der Wallet-Dienst würde das Ereignis USER_CREATED abonnieren und die Wallet-Erstellung durchführen.

9
mithrandir

Wenn mein Portemonnaie nur eine weitere Gruppe von Datensätzen in derselben SQL-Datenbank wie der Benutzer wäre, würde ich wahrscheinlich den Code zum Erstellen von Benutzern und Portemonnaie in demselben Dienst platzieren und diesen mit den normalen Datenbanktransaktionseinrichtungen handhaben.

Es klingt für mich, dass Sie fragen, was passiert, wenn der Erstellungscode für die Brieftasche ein anderes System oder andere Systeme berührt. Ich sage, alles hängt davon ab, wie komplex und/oder riskant der Erstellungsprozess ist. 

Wenn es nur darum geht, einen anderen zuverlässigen Datastore (z. B. einen, der nicht an Ihren SQL-Transaktionen teilnehmen kann) zu berühren, kann ich abhängig von den allgemeinen Systemparametern die verschwindend geringe Chance riskieren, dass ein zweites Schreiben nicht möglich ist. Ich kann nichts tun, aber eine Ausnahme auslösen und die inkonsistenten Daten über eine kompensierende Transaktion oder sogar über eine Ad-hoc-Methode behandeln. Wie ich meinen Entwicklern immer sage: "Wenn so etwas in der App passiert, wird es nicht unbemerkt bleiben".

Da die Komplexität und das Risiko der Geldbörsenerstellung zunehmen, müssen Sie Schritte unternehmen, um die damit verbundenen Risiken zu verbessern. Angenommen, einige der Schritte erfordern das Aufrufen mehrerer Partner-APIs. 

An dieser Stelle können Sie eine Nachrichtenwarteschlange zusammen mit der Vorstellung von teilweise konstruierten Benutzern und/oder Wallets einführen.

Eine einfache und effektive Strategie, um sicherzustellen, dass Ihre Entitäten schließlich ordnungsgemäß erstellt werden, besteht darin, die Jobs erneut zu starten, bis sie erfolgreich sind, aber es hängt viel von den Anwendungsfällen für Ihre Anwendung ab.

Ich würde auch lange und gründlich darüber nachdenken, warum ich in meinem Bereitstellungsprozess einen fehleranfälligen Schritt hatte.

7
Robert Moskal

Welche Lösungen gibt es, um zu verhindern, dass diese Art von Dateninkonsistenz auftritt? 

Üblicherweise werden verteilte Transaktionsmanager verwendet. Vor einigen Jahren haben Sie in der Java EE-Welt möglicherweise diese Services als EJB s erstellt, die auf verschiedenen Knoten bereitgestellt wurden, und Ihr API-Gateway hätte diese EJBs remote aufgerufen. Der Anwendungsserver stellt (bei korrekter Konfiguration) automatisch mithilfe von Zwei-Phasen-Commit sicher, dass die Transaktion auf jedem Knoten entweder festgeschrieben oder rückgängig gemacht wird, sodass die Konsistenz gewährleistet ist. Dies setzt jedoch voraus, dass alle Dienste auf dem gleichen Typ von Anwendungsserver bereitgestellt werden (damit sie kompatibel sind) und in Wirklichkeit immer nur mit Diensten gearbeitet wurden, die von einem einzigen Unternehmen bereitgestellt werden.

Gibt es Muster, mit denen Transaktionen mehrere REST -Anforderungen umfassen können?

Für SOAP (ok, nicht REST) ​​gibt es die Spezifikation WS-AT , aber kein Dienst, den ich je integrieren musste, hat dies unterstützt. Für REST hat JBoss etwas in der Pipeline . Ansonsten besteht das "Muster" darin, entweder ein Produkt zu finden, das Sie in Ihre Architektur integrieren 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

Entsprechend dem Artikel, den Sie referenzieren, gibt es auch das Try-Cancel/Confirm-Muster und das zugehörige Produkt von Atomikos.

BPEL-Engines handhaben die Konsistenz zwischen remote bereitgestellten Services mithilfe von Kompensation.

Alternativ weiß ich, dass REST für diesen Anwendungsfall möglicherweise nicht geeignet ist. Wäre es vielleicht der richtige Weg, um mit dieser Situation fertig zu werden, REST vollständig zu verwerfen und ein anderes Kommunikationsprotokoll wie ein Nachrichtenwarteschlangensystem zu verwenden? 

Es gibt viele Möglichkeiten, nicht transaktionsfähige Ressourcen in eine Transaktion zu "binden":

  • Wie Sie vorschlagen, könnten Sie eine Transaktionsnachrichtenwarteschlange verwenden, die jedoch asynchron ist. Wenn Sie also auf die Antwort angewiesen sind, wird sie unübersichtlich.
  • Sie könnten die Tatsache schreiben, dass Sie die Back-End-Services in Ihre Datenbank aufrufen müssen, und dann die Back-End-Services mithilfe eines Stapels aufrufen. Wieder asynchron, kann also unordentlich werden.
  • Sie können eine Business Process Engine als API-Gateway verwenden, um die Back-End-Mikrodienste zu koordinieren.
  • Sie können Remote-EJB verwenden, wie eingangs erwähnt, da dies verteilte Transaktionen außerhalb des Postens unterstützt.

Oder sollte ich die Konsistenz in meinem Anwendungscode erzwingen (z. B. durch einen Hintergrundjob, der Inkonsistenzen erkennt und behebt, oder indem in meinem Benutzermodell ein Attribut "state" mit Werten für "Erstellen", "erstellte" usw.) angezeigt wird?

Teufelsanwalt spielen: Warum so etwas bauen, wenn es Produkte gibt, die das für Sie tun (siehe oben) und es wahrscheinlich besser als Sie können, weil sie sich bewährt haben?

3
Ant Kutschera

Ich persönlich mag die Idee von Micro Services, von den Anwendungsfällen definierten Modulen, 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 gehen jetzt mehr für eventuell konsistente Systeme, aber ich bin nicht sicher, ob dies für Banken, Versicherungen usw. funktioniert.

Ich habe einen Blog über meine vorgeschlagene Lösung geschrieben. Vielleicht kann Ihnen das helfen ....

https://mehmetsalgar.wordpress.com/2016/11/05/mikro-services-fan-out-transaction-problems-and-solutions-mit-spring-bootjboss-and-netflix-eureka/

2
posthumecaver

Eine einfache Lösung ist, dass Sie einen Benutzer mithilfe des Benutzerdiensts erstellen und einen Nachrichtenbus verwenden, über den der Benutzerdienst seine Ereignisse ausgibt. Der Wallet-Dienst registriert sich auf dem Nachrichtenbus, überwacht das vom Benutzer erstellte Ereignis und erstellt Wallet für den Benutzer. Wenn der Benutzer in der Zwischenzeit die Wallet-Benutzeroberfläche besucht, um sein Wallet anzuzeigen, überprüfen Sie, ob der Benutzer gerade erstellt wurde und zeigen Sie an, dass die Erstellung des Wallets in Bearbeitung ist. Bitte überprüfen Sie einige Zeit

1
techagrammer

Eventuelle Konsistenz ist hier der Schlüssel. 

  • Einer der Services wird als primärer Handler der Veranstaltung ausgewählt. 
  • Dieser Dienst behandelt das ursprüngliche Ereignis mit einmaligem Commit. 
  • Der primäre Handler übernimmt die Verantwortung für die asynchrone Übermittlung der sekundären Auswirkungen an andere Dienste. 
  • Der primäre Handler übernimmt die Orchestrierung anderer Dienstaufrufe. 

Der Commander 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 ist dafür verantwortlich, die Ausführung aller Anweisungen zu garantieren, und das bedeutet Rückzug. Wenn der Commander versucht, das Remote-Update durchzuführen und keine Antwort erhält, hat er keine Wiederholung. Auf diese Weise kann das System so konfiguriert werden, dass es weniger fehleranfällig ist und sich selbst heilt. 

Da wir Wiederholungen haben, haben wir Idempotenz. Idempotenz ist die Eigenschaft, etwas zweimal so machen zu können, dass das Endergebnis das gleiche ist, als ob es nur einmal gemacht worden wäre. Wir brauchen Idempotenz beim Remote-Service oder bei der Datenquelle, damit der Befehl, der den Befehl mehrmals empfängt, nur einmal verarbeitet wird. 

Eventuelle Konsistenz Dies löst die meisten verteilten Transaktionsherausforderungen. Allerdings müssen hier einige Punkte berücksichtigt werden. .__ Auf jede fehlgeschlagene Transaktion folgt eine Wiederholung. Die Anzahl der Wiederholversuche hängt vom Kontext ab. 

Die Konsistenz ist möglich, d. H., Während sich das System während eines erneuten Versuchs nicht im konsistenten Zustand befindet, beispielsweise wenn ein Kunde ein Buch bestellt hat, eine Zahlung getätigt hat und dann die Bestandsmenge aktualisiert. Wenn die Bestandsaktualisierungsvorgänge fehlschlagen und vorausgesetzt, dass dies der letzte verfügbare Bestand war, ist das Buch weiterhin verfügbar, bis der Wiederholungsvorgang für die Bestandsaktualisierung erfolgreich war. Nach erfolgreicher Wiederholung ist Ihr System konsistent. 

0