Idempotent Consumer-Muster

Entwerfen Sie Nachrichtenkonsumenten so, dass die Verarbeitung derselben Nachricht mehr als einmal denselben Effekt hat wie die einmale Verarbeitung. Messagingsysteme, die mindestens einmal die Zustellung garantieren, können dieselbe Nachricht mehrmals übermitteln. Ohne Resilienz gegen Duplikate kann das Erneute Verarbeiten einer Nachricht doppelte Datensätze erstellen, einen Kunden aufladen oder andere unerwünschte Auswirkungen haben.

Kontext und Problem

Verteilte Anwendungen werden häufig über einen Nachrichtenbroker anstelle von direkten synchronen Anrufen ausgetauscht. Die meisten Broker, einschließlich Azure Service Bus, Azure Event Hubs, Apache Kafka und RabbitMQ, bieten mindestens einmal Lieferung. Diese Garantie stellt sicher, dass eine Nachricht auch dann einen Verbraucher erreicht, wenn Fehler auftreten, aber es bedeutet auch, dass der Broker dieselbe Nachricht mehrmals übermitteln kann.

Duplikate ergeben sich aus mehreren Quellen:

  • Produzenten-Wiederholungen

    Ein Produzent sendet eine Nachricht, erhält aufgrund eines vorübergehenden Netzwerkfehlers oder eines Timeouts keine Bestätigung und sendet die Nachricht erneut. Der Broker enthält jetzt zwei Kopien, obwohl das Senden das erste Mal erfolgreich war.

  • Neuleben nach fehlender Bestätigung

    Ein Verbraucher empfängt und verarbeitet eine Nachricht, erkennt sie aber nicht an, weil der Verbraucher abstürzt, die Sperre abläuft oder die Bestätigung verloren geht. Der Broker geht davon aus, dass die Nachricht nicht verarbeitet und erneut übermittelt wurde.

  • Verbraucherfehler bei mittlerer Verarbeitung

    Ein Consumer schließt einen Datenbankschreibvorgang ab, stürzt aber ab, bevor die Nachricht bestätigt wird. Wenn eine andere Instanz die Nachricht erneut öffnet, wiederholt sie den Schreibvorgang.

Genau einmal lieferung über ein verteiltes System ist unpraktisch zu garantieren. Selbst Broker, die genau einmal semantische Semantik beanspruchen, garantieren nur Vorgänge, die sie direkt steuern, z. B. das Übermitteln von Nachrichten an Verbraucher oder das Zurückschreiben von Daten an den Broker. Sie können die externen Nebenwirkungen, die Verbraucher in anderen Systemen ausführen, nicht garantieren. Die dauerhafte Lösung besteht nicht darin, doppelte Zustellungen zu beseitigen. Es ist, den Verbraucher tolerieren zu lassen. Wenn Sie die Lieferung mindestens einmal mit einem Verbraucher kombinieren, der Duplikate ignoriert, erreichen Sie die Verarbeitung effektiv genau.

Lösung

Machen Sie den Verbraucher zum Idempotent, indem Sie eine Aufzeichnung von verarbeiteten Nachrichten beibehalten und alle zuvor gesehenen Nachrichten überspringen. Der Consumer schlüsselt diese Entscheidung für einen stabilen Bezeichner, der die Neubelebung überlebt, überprüft einen beständigen Speicher, um festzustellen, ob dieser Bezeichner bereits verarbeitet wurde, und verarbeitet die Nachricht oder verwirft sie als Duplikat.

Die folgenden Schritte beschreiben den Kernfluss:

  1. Lesen Sie die Nachricht, und extrahieren Sie den Deduplizierungsschlüssel.
  2. Überprüfen Sie den Deduplizierungsspeicher für diesen Schlüssel.
  3. Wenn der Schlüssel vorhanden ist, behandeln Sie die Nachricht als Duplikat. Bestätigen Sie sie, und beenden Sie es, und geben Sie optional das zuvor aufgezeichnete Ergebnis zurück.
  4. Wenn der Schlüssel nicht vorhanden ist, verarbeiten Sie die Nachricht, und notieren Sie den Schlüssel in einem einzelnen atomischen Vorgang, und bestätigen Sie die Nachricht.

Auswählen eines stabilen Deduplizierungsschlüssels

Der Schlüssel muss die logische Botschaft in jeder Neubelebung eindeutig und konsistent identifizieren. Verwenden Sie einen vom Hersteller zugewiesenen Nachrichtenbezeichner oder einen idempotenten Schlüssel auf Unternehmensebene, der den spezifischen logischen Vorgang identifiziert, nicht einen freigegebenen Korrelationskontext, den mehrere Nachrichten übertragen können. In Azure Service Bus dient die MessageId Eigenschaft diesem Zweck, da sie die Nachricht und ihre Nutzlast eindeutig identifiziert. Verwenden CorrelationId Sie nicht als Schlüssel, da sie verwandte Nachrichten gruppiert, z. B. eine Anforderung und deren Antworten. Bei Ereignissen, die der CloudEvents-Spezifikation folgen, identifiziert die Kombination aus und sourceid Attributen ein Ereignis eindeutig und bleibt in allen Neubelebungen stabil.

Schlüssel nicht bei Bezeichnern auf Transportebene, die der Broker bei Neuleben oder auf Werten generiert, die von Übermittlungsversuchen abgeleitet wurden, da sich diese Werte zwischen Duplikaten ändern und die Erkennung verhindern. Vermeiden Sie außerdem das Ableiten des Schlüssels von veränderlichen Feldern wie z. B. Empfangen von Zeitstempeln.

Wenn mehr als ein unabhängiger Verbraucher denselben Kanal verarbeitet, z. B. mehrere Abonnenten in einem Veröffentlichungsabonnent-Design, verarbeitet jeder Verbraucher legitim seine eigene Kopie einer Nachricht und muss unabhängig den Abschluss der Nachrichtenverarbeitung nachverfolgen. Wenn diese Verbraucher einen Deduplizierungsspeicher teilen, schlüsseln Sie den Datensatz für eine Kombination aus der Consumeridentität und der Nachrichtenidentität. Mit einem Speicherschlüssel für die Nachrichtenidentität allein kann der erste Consumer die Verarbeitung für alle anderen unterdrücken.

Entscheiden, wo verarbeitete Schlüssel gespeichert werden sollen

Sie haben zwei allgemeine Optionen:

  • Eine dedizierte Deduplizierungstabelle. Der Consumer verwaltet eine separate Tabelle, die manchmal als Posteingang bezeichnet wird, die eine Zeile pro verarbeitetem Schlüssel enthält. Bei diesem Ansatz werden Deduplizierungsbedenken von Geschäftsdaten getrennt und funktionieren gut, wenn viele Nachrichtentypen einen Mechanismus gemeinsam nutzen.

  • Die Geschäftseinheit selbst. Der Verbraucher speichert den Schlüssel im Datensatz, den die Nachricht erstellt oder aktualisiert. Bei diesem Ansatz wird eine separate Tabelle vermieden, aber die Deduplizierung mit der Form der Geschäftsdaten wird gekoppelt.

Übernehmen Sie die Markierung und die Nebenwirkungen atomar

Der Ablauf des Überprüfungsvorgangs weist ein Fehlerfenster auf. Wenn der Consumer die Nachricht verarbeitet und dann den Schlüssel in einem separaten Schritt aufzeichnet, lässt ein Absturz zwischen den beiden Vorgängen die Nebeneffekte auf, aber der Schlüssel wird nicht aufgezeichnet, sodass die nächste Übermittlung die Nachricht erneut verarbeitet.

Beheben Sie dieses Fehlerfenster, indem Sie die Deduplizierungsmarkierung und die geschäftsseitigen Effekte in derselben Transaktion schreiben. Wenn beide commits gemeinsam oder gar nicht ausgeführt werden, findet eine Erneute Überprüfung entweder die zugesicherte Markierung und überspringt oder findet keine Markierung, da die Transaktion zurückgesetzt und sicher erneut verarbeitet wurde. Diese Transaktionsvariante ist das Posteingangsmuster, und es ist der konsumseitige Begleiter zum Transaktionsausgangsmuster auf der Produktseite.

Schutz vor gleichzeitigen Duplikaten

Unter mindestens einmaler Zustellung mit mehreren konkurrierenden Verbrauchern können zwei Instanzen gleichzeitig Kopien derselben Nachricht empfangen. Beide können die Existenzüberprüfung vor beiden Commits bestehen, sodass die Überprüfung allein keine doppelte Verarbeitung verhindert.

Erzwingen Sie die Korrektheit im Datenspeicher anstelle der Anwendungslogik:

  • Verwenden Sie eine eindeutige Einschränkung für den Deduplizierungsschlüssel. Beide Transaktionen versuchen, den Schlüssel einzufügen, aber nur eine ist erfolgreich. Die andere schlägt die Einschränkung fehl und behandelt die Nachricht als Duplikat. Dieser Ansatz macht die Datenbank zum einzigen Arbiter des Rennens.

  • Vermeiden Sie check-then-set-Rennen in Caches. Ein Muster, das einen Schlüssel überprüft und dann über zwei separate Vorgänge hinweg festlegt, verfügt über ein Fenster, mit dem gleichzeitige Wiederholungen beide den Schlüssel beanspruchen können. Verwenden Sie einen atombasierten bedingten Schreibvorgang, z. B. einen Einfügevorgang, der bei Konflikten fehlschlägt, oder einen set-if-absent-Vorgang, sodass der Anspruch auf den Schlüssel ein einzelner atomer Schritt ist.

Behandeln von Nebenwirkungen, die der Transaktion nicht beitreten können

Einige Prozesse können nicht an der Datenbanktransaktion des Verbrauchers teilnehmen, z. B. das Aufrufen einer Drittanbieter-API oder das Schreiben in einen externen Speicher. Verwenden Sie für diese Prozesse einen zweistufigen Ansatz:

  1. Notieren Sie den Schlüssel mit einem status in Bearbeitung , bevor Sie die externe Aktion ausführen.
  2. Führen Sie den Prozess aus.
  3. Aktualisieren Sie den Datensatz so, dass er abgeschlossen ist , und speichern Sie das Ergebnis.

Bei der Neubelebendung können Sie mit einem abgeschlossenen Datensatz die Wiederholung des Anrufs überspringen. Ein laufender Datensatz signalisiert, dass ein vorheriger Versuch teilweise abgeschlossen wurde oder von einem anderen Verbraucher bearbeitet wird.

Probleme und Überlegungen

Berücksichtigen Sie die folgenden Punkte, wenn Sie sich für die Implementierung dieses Musters entscheiden:

  • Bevorzugen Sie natürlich idempotente Operationen. Einige Vorgänge sind inhärent idempotent und benötigen keine Deduplizierungsbuchführung. Ein Upsert-Schlüssel für einen Geschäftsbezeichner, ein Schreibzugriff, der einen absoluten Wert anstelle eines Inkrements festlegt, oder ein HTTP PUT zu einem Ressourcenbezeichner erzeugt dasselbe Ergebnis, unabhängig davon, ob er einmal oder mehrmals ausgeführt wird.

    Manchmal können Sie einen Vorgang natürlich durch ereignisgestützte Zustandsübertragung durchführen, wobei die Nachricht den resultierenden absoluten Zustand trägt, z. B. den neuen Status einer Bestellung, sodass der Verbraucher ihn als Upsert anstelle einer relativen Änderung anwendet.

    Tip

    Entwerfen Sie zuerst natürliche idempotenz, und fügen Sie Deduplizierungstechniken nur für Vorgänge hinzu, die nicht natürlich idempotent gemacht werden können.

  • Verwalten des Lebenszyklus von Deduplizierungsdatensätzen. Deduplizierungsdatensätze werden gesammelt, es sei denn, Sie laufen sie ab. Bewahren Sie jeden Datensatz mindestens so lange auf, wie der Broker die ursprüngliche Nachricht erneut senden kann. Dieses Fenster hängt von den maximalen Übermittlungsversuchen des Brokers, seinem Sperr- oder Sichtbarkeitstimeout und dem Zeitlimit der Nachricht ab. Legen Sie einen Zeit-zu-Live-Vorgang für Deduplizierungsdatensätze fest, die dieses Fenster überschreiten, sodass eine späte Neubelebung ihre Markierung immer noch findet. Das Löschen von Datensätzen zu früh öffnet das Fenster für Duplikate erneut. Konto für Nachrichten, die ein Operator aus einer Warteschlange mit inaktiven Briefen erneut sendet, da eine erneute Übermittlung lange nach dem normalen Neubelebensfenster auftreten kann.

  • Verwenden Sie ein Messaging-Framework anstelle der Handrolldeduplizierung. Das Implementieren des Deduplizierungsspeichers, des Atom-Commits und der ordnungsgemäßen Bereinigung des Datensatzes ist fehleranfällig. Nachrichtenbasierte Frameworks stellen dieses Muster als integriertes Feature bereit.

    Beispielsweise dedupliziert NServiceBus eingehende Nachrichten anhand ihres Nachrichtenbezeichners und stellt konfigurierbare Aufbewahrung und Bereinigung für Deduplizierungsdaten bereit. Der Massentransit-Consumer-Posteingang verfolgt empfangene Nachrichten anhand ihrer Nachrichten-ID nach, um genau einmal das Verbraucherverhalten bereitzustellen.

  • Die Brokerdeduplizierung verringert sich, entfernt jedoch nicht die Notwendigkeit einer idempotenten Consumerlogik. Einige Plattformen filtern Duplikate auf der Transportebene. Azure Service Bus doppelte Erkennung verwirft Nachrichten, die innerhalb eines konfigurierten Zeitfensters wiederholt werdenMessageId, wodurch Duplikate unterdrückt werden, die von Produzenten-Wiederholungen verursacht werden. Dieses Feature wird auf der Sendeseite und innerhalb eines gebundenen Fensters ausgeführt. Es hindert einen Verbraucher nicht daran, die gleiche Nachricht zweimal nach einer Neubelebung zu verarbeiten, daher benötigen Sie immer noch idempotente Consumerlogik. Behandeln Sie Plattformfeatures als erste Verteidigungsebene, die das doppelte Volumen verringert, nicht als Ersatz für das Muster.

  • Konto für die Nachrichtenbestellung. Die Deduplizierung entfernt Duplikate, garantiert jedoch keine Reihenfolge. Wenn der Verbraucher von der Verarbeitungsreihenfolge abhängt, kombinieren Sie dieses Muster mit einem Bestellmechanismus, z. B. Azure Service Bus Nachrichtensitzungen, oder fügen Sie Sequenz- oder Versionsdaten hinzu, mit denen der Verbraucher veraltete Nachrichten ablehnen kann.

  • Instrument zur Beobachtbarkeit. Geben Sie den Deduplizierungsschlüssel und einen Korrelationsbezeichner in strukturierten Protokollen aus, und verfolgen Sie eine Metrik für erkannte Duplikate. Eine steigende Duplikatrate kann auf falsch konfigurierte Produzenten, eine untergeordnete Bestätigung oder ein Sperrfenster oder fehlerhafte Verbraucher hinweisen. Verwenden Sie End-to-End-Ablaufverfolgung und Korrelation , um einer Nachricht über Dienste hinweg zu folgen.

  • Verteilen der Idempotenz an nachgeschaltete Aufrufe. Ein Verbraucher-Idempotent schützt nicht die Dienste, die er aufruft. Wenn ein Verbraucher nachgeschaltete Dienste als Teil der Verarbeitung aufruft, verteilen Sie den idempotenten Schlüssel, damit jede Ebene ihre eigene Arbeit deduplizieren kann.

Wann dieses Muster verwenden

Verwenden Sie dieses Muster in folgenden Fällen:

  • Sie nutzen Nachrichten von einem Broker, der mindestens einmal die Zustellung bereitstellt, was die Standardeinstellung für die meisten Broker ist.

  • Das Erneute Verarbeiten einer Nachricht erzeugt falsche Ergebnisse, z. B. doppelte Finanztransaktionen, doppelte Ressourcenerstellung oder wiederholte Benachrichtigungen.

  • Mehrere konkurrierende Verbraucher verarbeiten denselben Kanal, wodurch die gleichzeitige Doppelte Zustellung wahrscheinlich wird.

Dieses Muster ist möglicherweise nicht geeignet, wenn:

  • Jeder vom Verbraucher ausgeführte Vorgang ist bereits natürlich idempotent, sodass die Neuverarbeitung harmlos ist und die Deduplizierungsbuchhaltung Kosten ohne Nutzen hinzufügt.

  • Die Workload kann die Auswirkungen einer gelegentlichen doppelten Verarbeitung tolerieren, und die Kosten einer Deduplizierungsspeicher überwiegen die Auswirkungen eines Duplikats.

Idempotent-Verarbeitung über Messaging hinaus

Dieses Muster wendet idempotenz auf Nachrichtenkonsumenten an, aber die idempotente Verarbeitung ist ein breiteres Zuverlässigkeitsprinzip. Jeder Vorgang, der über einen identischen Vorgang ausgeführt werden kann, profitiert davon. Dieses Prinzip umfasst Extrahieren, Transformieren, Laden (ETL)-Transformationen, die replayed-Daten verarbeiten, Datenstromverarbeitung, die von einem Prüfpunkt fortgesetzt wird, geplante Aufträge, die sich überschneiden oder neu starten, sowie Webhook- oder HTTP-Endpunkte, die doppelte Lieferungen empfangen.

In jedem Fall gilt die gleiche Kerntechnik:

  1. Identifizieren Sie die Arbeitseinheit mit einem stabilen Schlüssel.
  2. Notieren Sie sich, was Sie bereits verarbeitet haben.
  3. Überspringen oder absorbieren Sie Duplikate, damit das Wiederholen der Arbeit das Ergebnis nicht ändert.

Die Mechanismen in diesem Artikel, z. B. stabile Schlüssel, Atommarkierungen und eindeutige Einschränkungen, werden an diese Kontexte übertragen, auch wenn kein Nachrichtenbroker beteiligt ist.

Arbeitslastgestaltung

Bewerten Sie, wie Sie das Idempotente Consumer-Muster im Entwurf einer Workload verwenden, um die in den Azure Well-Architected Framework-Säulen behandelten Ziele und Prinzipien zu erfüllen. Die folgende Tabelle enthält Anleitungen dazu, wie dieses Muster die Ziele jeder Säule unterstützt.

Säule So unterstützt dieses Muster die Säulenziele
Zuverlässigkeitsentwurfsentscheidungen helfen Ihrer Arbeitsauslastung, ausfallsicher zu werden und sicherzustellen, dass sie nach auftreten eines Fehlers wieder in einen voll funktionsfähigen Zustand versetzt wird. Mit diesem Muster kann eine Arbeitsauslastung mindestens einmal Lieferungen und sichere Wiederholungen ohne beschädigte Daten verwenden, wodurch die doppelte Zustellung von einem Korrektheitsrisiko in eine tolerierte Bedingung umgewandelt wird.

- RE:07 Selbsterhaltung
- Behandeln vorübergehender Fehler

Wenn dieses Muster Kompromisse innerhalb einer Säule einführt, sollten Sie sie gegen die Ziele der anderen Säulen berücksichtigen.

Beispiel

Das folgende Beispiel zeigt einen idempotenten Verbraucher, der Bestellungen von Azure Service Bus verarbeitet und in Azure Cosmos DB für NoSQL beibehalten wird.

Ein Produzent legt die Service Bus MessageId auf einen Auftragsbezeichner auf Unternehmensebene fest. Der Verbraucher empfängt Nachrichten im PeekLock-Modus , die eine Nachricht erneut senden, wenn der Verbraucher sie nicht innerhalb der Sperrdauer abschließt. Die Azure Cosmos DB Containerpartitionen des Consumers auf dem Auftragsbezeichner (/orderId) und legt das Dokument id auf denselben Auftragsbezeichner fest, sodass jede Kopie einer bestimmten Reihenfolge in dieselbe logische Partition aufgelöst wird und der Auftragsdatensatz selbst als Deduplizierungsmarkierung dient.

Der Verbraucher verarbeitet jede Nachricht wie folgt:

  1. Lesen Sie die Nachricht, und verwenden Sie sie MessageId als Deduplizierungsschlüssel.
  2. Erstellen Sie das Bestelldokument mit id und dem Partitionsschlüssel, der auf den Auftragsbezeichner festgelegt ist.
  3. Wenn die Erstellung erfolgreich ist, schließen Sie die Nachricht ab, sodass Service Bus sie aus der Warteschlange entfernt.
  4. Wenn die Erstellung mit einem HTTP 409 -Status (Konflikt) fehlschlägt, da ein Dokument mit dem id vorhandenen Dokument bereits vorhanden ist, lesen Sie das vorhandene Dokument, und vergleichen Sie es mit der aktuellen Nachricht. Wenn ein gespeicherter Anforderungshash oder unveränderliche Geschäftsfelder übereinstimmen, behandeln Sie die Nachricht als Duplikat, schließen Sie ihn ab, und überspringen Sie die Verarbeitung. Wenn sie nicht übereinstimmen, hat der Produzent möglicherweise den Bezeichner für unterschiedliche Inhalte wiederverwendet, oder die Bestelldetails wurden seit der ersten Verarbeitung geändert, sodass die Nachricht in totschreiben oder eine Warnung ausgelöst wird, anstatt sie im Hintergrund zu verwerfen.
  5. Wenn die Verarbeitung aus vorübergehendem Grund fehlschlägt, geben Sie die Nachricht so ab, dass Service Bus sie erneut senden oder die Sperre ablaufen lassen, damit ein anderer Verbraucher sie erhält.

Der Erstellungsvorgang ist atomisch, sodass er sowohl als Deduplizierungsprüfung als auch als Schreibvorgang dient. Zwei Verbraucher, die Kopien derselben Nachricht erhalten, können nicht beide die Bestellung erstellen. Eine Erstellt gewinnt, und der andere gibt einen Konflikt zurück und verwirft sicher sein Duplikat.

Bei der Verarbeitung müssen mehrere Dokumente geschrieben werden, verwenden Sie einen Transaktionsbatch , der sowohl das Deduplizierungsdokument als auch die Geschäftsdokumente innerhalb desselben Partitionsschlüssels enthält. Da ein Transaktionsbatch innerhalb einer einzigen logischen Partition ausgeführt wird, wählen Sie einen Partitionsschlüssel aus, den alle Dokumente für eine Nachrichtenfreigabe aufweisen. Der Batch führt überhaupt keinen Commit für alle Dokumente durch, sodass ein Absturz zwischen Verarbeitung und Bestätigung die Deduplizierungsmarkierung und die Geschäftsdaten nicht synchron lassen können. Ein Batch, der versucht, ein Dokument zu erstellen, das bereits vorhanden ist, gibt einen Status von 409 (Konflikt) zurück, der das Duplikat identifiziert.

Damit dieser Verbraucher auch gegen wiederholungsversuche mit doppelten Wiederholungen ausfallsicher ist, aktivieren Sie die Duplikaterkennung in der Warteschlange. Bei der doppelten Erkennung werden wiederholte Senden innerhalb des Verlaufsfensters unterdrückt, und der idempotente Consumer verarbeitet alle Duplikate, die außerhalb dieses Fensters liegen oder die sich aus einer Neubelebung ergeben.

Nächster Schritt

  • Asynchrone Messagingoptionen in Azure beschreiben die Auswahlmöglichkeiten der Messaginginfrastruktur, die Ihre Übermittlungsgarantien und Anforderungen an die Duplikatbehandlung bestimmen.