it-swarm.com.de

DB-Konsistenz mit Microservices

Was ist der beste Weg, um DB-Konsistenz in Microservice-basierten Systemen zu erreichen?

Auf der GOTO in Berlin sprach Martin Fowler über Mikrodienste und eine "Regel", die er erwähnte, war "per Dienst" -Datenbanken, was bedeutet, dass Dienste keine direkte Verbindung zu einer DB herstellen können, die "einem anderen Dienst" gehört.

Das ist super nett und elegant, aber in der Praxis wird es ein bisschen schwierig. Angenommen, Sie haben einige Dienste:

  • ein Frontend
  • ein Auftragsverwaltungsdienst
  • ein Treueprogramm-Service

Nun kauft ein Kunde an Ihrem Frontend ein und ruft den Order Management Service auf, der alles in der DB speichert - kein Problem. An diesem Punkt wird auch der Treueprogrammdienst angerufen, so dass Punkte von Ihrem Konto gutgeschrieben/abgebucht werden.

Wenn sich nun alles auf demselben DB/DB-Server befindet, ist dies alles einfach, da Sie alles in einer Transaktion ausführen können: Wenn der Treueprogramm-Dienst nicht in die Datenbank schreibt, können Sie das Ganze zurücksetzen.

Wenn Sie DB-Operationen über mehrere Dienste hinweg ausführen, ist dies nicht möglich, da wir nicht auf eine einzige Verbindung angewiesen sind und nur eine einzige Transaktion ausführen. Was sind die besten Muster, um die Dinge konsistent zu halten und ein glückliches Leben zu führen Leben?

Ich bin sehr gespannt auf Ihre Vorschläge! ... und vielen Dank im Voraus!

29
odino

Das ist super nett und elegant, aber in der Praxis wird es ein bisschen schwierig

"In der Praxis" bedeutet, dass Sie Ihre Mikrodienste so gestalten müssen, dass die erforderliche Geschäftskonsistenz erfüllt ist, wenn Sie der Regel folgen:

diese Dienste können keine direkte Verbindung zu einer Datenbank herstellen, die einem anderen Dienst gehört.

Mit anderen Worten: Nehmen Sie keine Annahmen über ihre Verantwortlichkeiten vor und ändern Sie die Grenzen nach Bedarf, bis Sie einen Weg finden, um dies zu erreichen.

Nun zu deiner Frage:

Was sind die besten Muster, um die Dinge konstant zu halten und ein glückliches Leben zu führen?

Für Dinge, die keine sofortige Konsistenz erfordern, und das Aktualisieren von Treuepunkten scheint in diese Kategorie zu fallen, könnten Sie ein zuverlässiges Pub/Sub-Muster verwenden, um Ereignisse von einem Mikroservice aus zu senden und von anderen verarbeitet zu werden. Das zuverlässige Bit ist, dass Sie gute Wiederholungen, Rollbacks und Idempotenz (oder Transaktionalität) für die Verarbeitung von Ereignissen wünschen.

Wenn Sie unter .NET laufen, sind einige Beispiele für Infrastrukturen, die diese Zuverlässigkeit unterstützen, NServiceBus und MassTransit . Vollständige Offenlegung - Ich bin der Gründer von NServiceBus.

Update: Nach folgenden Anmerkungen zu Bedenken hinsichtlich der Treuepunkte: "Wenn Saldeaktualisierungen verzögert verarbeitet werden, kann ein Kunde tatsächlich mehr Artikel bestellen, als für die er Punkte hat". 

Viele Menschen kämpfen mit diesen Anforderungen um eine starke Beständigkeit. Die Sache ist, dass diese Arten von Szenarien normalerweise durch die Einführung zusätzlicher Regeln behandelt werden können, beispielsweise wenn ein Benutzer negative Loyalitätspunkte erhält, die ihn benachrichtigen. Wenn T durchgeht, ohne die Treuepunkte auszusortieren, benachrichtigen Sie den Benutzer, dass ihm M aufgrund einer Conversion-Rate in Rechnung gestellt wird. Diese Richtlinie sollte für Kunden sichtbar sein, wenn sie Punkte zum Kauf von Sachen verwenden.

11
Udi Dahan

Normalerweise beschäftige ich mich nicht mit Microservices, und dies ist möglicherweise keine gute Vorgehensweise, aber hier ist eine Idee:

Um das Problem zu wiederholen, besteht das System aus drei unabhängigen, aber kommunizierenden Teilen: dem Frontend, dem Backend für das Auftragsmanagement und dem Backend für das Treueprogramm. Das Frontend möchte sicherstellen, dass sowohl im Order-Management-Backend als auch im Loyalty-Programm-Backend Status gespeichert wird.

Eine mögliche Lösung wäre die Implementierung einer Art von Zwei-Phasen-Commit :

  1. Zunächst legt das Frontend einen Datensatz mit allen Daten in seiner eigenen Datenbank ab. Nennen Sie dies den Frontend-Datensatz .
  2. Das Frontend fragt das Backend der Auftragsverwaltung nach einer Transaktions-ID und übergibt diese an alle Daten, die zum Abschließen der Aktion erforderlich wären. Das Backend der Auftragsverwaltung speichert diese Daten in einem Bereitstellungsbereich, verknüpft eine neue Transaktions-ID und gibt diese an das Frontend zurück.
  3. Die Transaktions-ID der Auftragsverwaltung wird als Teil des Frontend-Datensatzes gespeichert.
  4. Das Frontend fragt das Backend des Treueprogramms nach einer Transaktions-ID und übergibt diese an alle Daten, die zum Abschluss der Aktion erforderlich sind. Das Treueprogramm-Backend speichert diese Daten in einem Bereitstellungsbereich, verknüpft eine neue Transaktions-ID und gibt diese an das Frontend zurück.
  5. Die Transaktions-ID des Treueprogramms wird als Teil des Frontend-Datensatzes gespeichert.
  6. Das Frontend weist das Backend der Auftragsverwaltung an, die mit der Transaktions-ID verknüpfte Transaktion abzuschließen, die im Frontend gespeichert ist.
  7. Das Frontend weist das Backend des Treueprogramms an, die mit der Transaktions-ID verknüpfte Transaktion abzuschließen, die im Frontend gespeichert ist.
  8. Das Frontend löscht seinen Frontend-Record.

Wenn dies implementiert ist, sind die Änderungen nicht unbedingt atomar , aber es sind schließlich konsistent . Denken wir an die Orte, an denen es versagen könnte:

  • Wenn im ersten Schritt ein Fehler auftritt, werden sich keine Daten ändern.
  • Wenn der zweite, dritte, vierte oder fünfte Fehler ausfällt, kann das System, wenn das System wieder online ist, alle Frontend-Datensätze durchsuchen und nach Datensätzen ohne zugehörige Transaktions-ID (eines der beiden Typen) suchen. Wenn er auf einen solchen Datensatz stößt, kann er ab Schritt 2 wiedergegeben werden. (Wenn in Schritt 3 oder 5 ein Fehler auftritt, werden in den Backends einige aufgegebene Datensätze zurückgelassen, es wird jedoch nie aus dem Bereitstellungsbereich verschoben es ist okay.)
  • Wenn im sechsten, siebten oder achten Schritt ein Fehler auftritt, kann das System, wenn das System wieder online ist, nach allen Frontend-Datensätzen mit beiden Transaktions-IDs suchen. Anschließend kann es die Backends abfragen, um den Status dieser Transaktionen anzuzeigen (festgeschrieben oder nicht festgeschrieben) . Abhängig davon, welche festgeschrieben wurden, kann der entsprechende Schritt fortgesetzt werden.
7
icktoofay

Selbst bei verteilten Transaktionen können Sie in den Status "Transaktion in Zweifel" übergehen, wenn einer der Teilnehmer mitten in der Transaktion abstürzt. Wenn Sie die Dienste als idempotente Operation entwerfen, wird das Leben etwas einfacher. Man kann Programme schreiben, um die Geschäftsbedingungen ohne XA zu erfüllen. Pat Helland hat zu diesem Thema "Life Beyond XA" eine hervorragende Arbeit geschrieben. Grundsätzlich besteht der Ansatz darin, möglichst geringe Annahmen über entfernte Entitäten zu treffen. Er illustrierte auch einen Ansatz namens Open Nested Transactions ( http://www.cidrdb.org/cidr2013/Papers/CIDR13_Paper142.pdf ), um Geschäftsprozesse zu modellieren. In diesem speziellen Fall wäre die Kauftransaktion ein Top-Flow und die Loyalitäts- und Auftragsverwaltung der nächste Level. Der Trick besteht darin, granulare Dienste als idempotente Dienste mit Kompensationslogik zu verwenden. Wenn also irgendetwas irgendwo im Fluss versagt, können einzelne Dienste dies kompensieren. So z. Wenn die Bestellung aus irgendeinem Grund fehlschlägt, kann die Treue den aufgelaufenen Punkt für den Kauf abziehen.

Ein anderer Ansatz ist das Modellieren unter Verwendung einer eventuellen Konsistenz unter Verwendung von CALM oder CRDTs. Ich habe einen Blog geschrieben, um CALM im realen Leben hervorzuheben - http://shripad-agashe.github.io/2015/08/Art-Of-Disorderly-Programming Vielleicht hilft es Ihnen.

0
Shripad

Ich stimme dem zu, was @Udi Dahan gesagt hat. Ich möchte nur seine Antwort hinzufügen. 

Ich denke, Sie müssen die Anfrage an das Treueprogramm fortsetzen, damit das Versagen an einem anderen Punkt erfolgen kann. Es gibt verschiedene Möglichkeiten, dies zu tun.

1) Machen Sie den API-Fehler des Treueprogramms wiederherstellbar. Das heißt, es kann Anforderungen beibehalten, so dass sie nicht verloren gehen und zu einem späteren Zeitpunkt wiederhergestellt (erneut ausgeführt) werden können. 

2) Führen Sie die Treueprogrammanfragen asynchron aus. Das heißt, die Anforderung sollte zunächst irgendwo bestehen bleiben, dann darf der Dienst sie aus diesem permanenten Speicher lesen. Nur aus erfolgreichem Speicher aus dem persistenten Speicher entfernen. 

3) Tun Sie, was Udi gesagt hat, und legen Sie es in eine gute Warteschlange (Pub/Sub-Muster, um genau zu sein). Dies erfordert normalerweise, dass der Abonnent eine der beiden folgenden Aktionen ausführt ... entweder die Anforderung beibehalten, bevor sie aus der Warteschlange entfernt wird (goto 1) - ODER - zuerst die Anforderung aus der Warteschlange ausleihen, dann nach der erfolgreichen Verarbeitung der Anforderung die Anforderung erhalten aus der Warteschlange entfernt (dies ist meine Präferenz). 

Alle drei erreichen dasselbe. Sie verschieben die Anfrage an einen Ort, an dem sie bis zum erfolgreichen Abschluss bearbeitet werden kann. Die Anforderung geht nie verloren und wird bei Bedarf wiederholt, bis ein zufriedenstellender Zustand erreicht ist.

Ich benutze gerne das Beispiel eines Staffellaufs. Jeder Dienst oder Code muss die Anforderung in Besitz nehmen, bevor der vorherige Code losgelassen werden kann. Nach der Übergabe darf der aktuelle Besitzer die Anforderung nicht verlieren, bis er verarbeitet oder an einen anderen Code übergeben wird.

0
Jose Martinez