Idempotent consumentpatroon

Ontwerp berichtgebruikers zodat het verwerken van hetzelfde bericht meer dan één keer hetzelfde effect heeft als de verwerking ervan. Berichtsystemen die garanderen dat een bericht ten minste één keer wordt afgeleverd, kunnen hetzelfde bericht meerdere keren afleveren. Zonder tolerantie voor duplicaten kan het opnieuw verwerken van een bericht dubbele records maken, een klant dubbel opladen of andere ongewenste effecten hebben.

Context en probleem

Gedistribueerde toepassingen wisselen vaak werk uit via een berichtenbroker in plaats van via directe synchrone aanroepen. De meeste brokers, waaronder Azure Service Bus, Azure Event Hubs, Apache Kafka en RabbitMQ, bieden ten minste één keer levering. Deze garantie zorgt ervoor dat een bericht een consument bereikt, zelfs wanneer er fouten optreden, maar het betekent ook dat de broker hetzelfde bericht meer dan één keer kan bezorgen.

Duplicaten ontstaan uit verschillende bronnen:

  • Herhalingspogingen van de producer

    Een producent verzendt een bericht, ontvangt geen bevestiging vanwege een tijdelijke netwerkfout of time-out en verzendt het bericht opnieuw. De broker bevat nu twee kopieën, ook al is het verzenden de eerste keer geslaagd.

  • Herlevering na een ontbrekende ontvangstbevestiging

    Een consument ontvangt en verwerkt een bericht, maar kan het bericht niet bevestigen omdat de consument vastloopt, de vergrendeling verloopt of de bevestiging verloren gaat. De broker gaat ervan uit dat het bericht niet is verwerkt en opnieuw wordt bezorgd.

  • Consumentenfouten tijdens de verwerking

    Een consument rondt een schrijfbewerking in de database af, maar crasht voordat deze het bericht bevestigt. Wanneer een ander exemplaar het opnieuw bezorgde bericht ophaalt, wordt de schrijfbewerking herhaald.

Exactly-once aflevering kan in een gedistribueerd systeem in de praktijk niet worden gegarandeerd. Zelfs brokers die exact-once-semantiek beweren, garanderen alleen operaties die ze rechtstreeks beheersen, zoals het afleveren van berichten aan consumenten of het terugschrijven van gegevens naar de broker. Ze kunnen niet garanderen welke externe neveneffecten afnemers in andere systemen uitvoeren. De duurzame oplossing is niet om dubbele levering te elimineren. Het is om de consument het te laten verdragen. Wanneer u at-least-once-levering combineert met een consumer die duplicaten negeert, realiseert u effectief exact-once-verwerking.

Oplossing

Maak de consumer idempotent door een overzicht van verwerkte berichten bij te houden en elk bericht dat al eerder is verwerkt over te slaan. De consumer baseert deze beslissing op een stabiele identifier die behouden blijft bij herlevering, controleert een persistente opslag om te bepalen of die identifier al is verwerkt, en verwerkt het bericht of verwerpt het als duplicaat.

In de volgende stappen wordt de kernstroom beschreven:

  1. Lees het bericht en haal de ontdubbelingssleutel eruit.
  2. Controleer het ontdubbelingsarchief voor die sleutel.
  3. Als de sleutel bestaat, behandelt u het bericht als een duplicaat. Bevestig dit en stop, waarbij eventueel het eerder vastgelegde resultaat wordt geretourneerd.
  4. Als de sleutel niet bestaat, verwerkt u het bericht en registreert u de sleutel in één atomische bewerking en bevestigt u het bericht.

Een stabiele ontdubbelingssleutel kiezen

De sleutel moet uniek en consistent het logische bericht voor elke herlevering identificeren. Gebruik een door de producent toegewezen bericht-id of een idempotentiesleutel op bedrijfsniveau die de specifieke logische bewerking identificeert, niet een gedeelde correlatiecontext die meerdere berichten kunnen bevatten. In Azure Service Bus dient de MessageId eigenschap dit doel omdat het bericht en de nettolading uniek worden geïdentificeerd. Gebruik niet CorrelationId als de sleutel, omdat hiermee gerelateerde berichten worden gegroepeerd, zoals een aanvraag en de antwoorden. Voor gebeurtenissen die voldoen aan de Specificatie van CloudEvents, identificeert de combinatie van de source en id kenmerken een gebeurtenis en blijft deze stabiel bij herliveries.

Baseer de sleutel niet op transportniveau-ID's die de broker bij herlevering opnieuw genereert, of op waarden die zijn afgeleid van afleverpogingen, omdat die waarden tussen duplicaten verschillen en duplicaatdetectie onmogelijk maken. Vermijd ook het afleiden van de sleutel uit vluchtige velden, zoals het ontvangen van tijdstempels.

Wanneer meer dan één onafhankelijke consument hetzelfde kanaal verwerkt, zoals meerdere abonnees in een ontwerp voor publiceren-abonneren, verwerkt elke consument legitiem een eigen kopie van een bericht en moet de voltooiing van de berichtverwerking onafhankelijk bijhouden. Als deze consumenten één deduplicatieopslag delen, gebruik dan voor het record een sleutel die is samengesteld uit de identiteit van de consument en de identiteit van het bericht. Een opslag die uitsluitend is gebaseerd op de berichtidentiteit, zorgt ervoor dat de eerste consument de verwerking door alle andere consumenten kan voorkomen.

Bepalen waar verwerkte sleutels moeten worden opgeslagen

U hebt twee algemene opties:

  • Een speciale ontdubbelingstabel. De afnemer onderhoudt een afzonderlijke tabel, soms een inbox genoemd, met één rij per verwerkte sleutel. Deze aanpak houdt de ontdubbelingsproblemen gescheiden van bedrijfsgegevens en werkt goed wanneer veel berichttypen één mechanisme delen.

  • De bedrijfsentiteit zelf. De consument slaat de sleutel op in de record die door het bericht wordt gemaakt of bijgewerkt. Deze benadering voorkomt een afzonderlijke tabel, maar koppelt ontdubbeling aan de vorm van de bedrijfsgegevens.

De markering en de bijwerkingen atomisch doorvoeren

De stroom waarbij eerst wordt gecontroleerd en daarna verwerkt, kent een faalvenster. Als de consument het bericht verwerkt en vervolgens de sleutel in een afzonderlijke stap vastlegt, leidt een crash tussen die twee bewerkingen ertoe dat de neveneffecten wel zijn toegepast maar de sleutel niet is vastgelegd, zodat het bericht bij de volgende aflevering opnieuw wordt verwerkt.

Los dit foutvenster op door de ontdubbelingsmarkering en de zakelijke neveneffecten in dezelfde transactie te schrijven. Wanneer beide tegelijk worden gecommit of allebei helemaal niet, vindt een herlevering óf de gecommitte marker en slaat die over, óf geen marker omdat de transactie is teruggedraaid, en wordt deze veilig opnieuw verwerkt. Deze transactionele variant is het inbox pattern en vormt aan de consumerzijde de tegenhanger van het Transactional Outbox pattern aan de producerzijde.

Bescherming tegen gelijktijdige duplicaten

Bij ten minste één levering met meerdere concurrerende consumenten kunnen twee exemplaren tegelijkertijd kopieën van hetzelfde bericht ontvangen. Beide kunnen de controle op bestaan doorstaan voordat een van beide wordt vastgelegd, dus de controle op zichzelf voorkomt geen dubbele verwerking.

Dwing correctheid af in de gegevensopslag in plaats van in de applicatielogica:

  • Gebruik een unieke beperking voor de ontdubbelingssleutel. Beide transacties proberen de sleutel in te voegen, maar er slaagt slechts één. Het andere voldoet niet aan de beperking en behandelt het bericht als een duplicaat. Deze benadering maakt de database de individuele arbiter van de race.

  • Vermijd racecondities van het type check-then-set in caches. Een patroon dat een sleutel controleert en deze vervolgens instelt op twee afzonderlijke bewerkingen, heeft een venster waarmee gelijktijdige nieuwe pogingen kunnen worden gedaan om beide de sleutel te claimen. Gebruik een voorwaardelijke, atomaire schrijfbewerking, zoals een invoeging die mislukt bij een conflict of een instellen-als-afwezig-bewerking, zodat het opeisen van de sleutel één atomaire stap is.

Neveneffecten verwerken die niet aan de transactie kunnen deelnemen

Sommige processen kunnen niet deelnemen aan de databasetransactie van de consument, zoals het aanroepen van een API van derden of het schrijven naar een externe winkel. Gebruik voor deze processen een benadering in twee fasen:

  1. Leg de sleutel vast met de status bezig voordat u de externe actie uitvoert.
  2. Voer het proces uit.
  3. Werk de record bij naar voltooid en sla het resultaat op.

Bij het opnieuw uitvoeren van een voltooide record kunt u het herhalen van de oproep overslaan. Een actieve record geeft aan dat een eerdere poging gedeeltelijk is voltooid of waaraan een andere consument heeft gewerkt.

Problemen en overwegingen

Houd rekening met de volgende punten wanneer u besluit hoe u dit patroon implementeert:

  • Gebruik bij voorkeur van nature idempotente bewerkingen. Sommige bewerkingen zijn inherent idempotent en hebben geen ontdubbelingsboekhouding nodig. Een upsert-sleutel voor een bedrijfs-id, een schrijfbewerking waarmee een absolute waarde wordt ingesteld in plaats van een verhoging, of een HTTP PUT naar een resource-id produceert hetzelfde resultaat, ongeacht of deze eenmaal of meerdere keren wordt uitgevoerd.

    Soms kunt u een bewerking natuurlijk idempotent maken via door gebeurtenissen overgedragen statusoverdracht, waarbij het bericht de resulterende absolute status heeft, zoals de nieuwe status van een order, zodat de consument deze toepast als een upsert in plaats van een relatieve wijziging.

    Tip

    Ontwerp eerst voor natuurlijke idempotentie en voeg alleen ontdubbelingstechnieken toe voor bewerkingen die niet van nature idempotent kunnen worden gemaakt.

  • De levenscyclus van ontdubbelingsrecords beheren. Deduplicatierecords stapelen zich op, tenzij u ze laat verlopen. Behoud elke record ten minste zolang de broker het oorspronkelijke bericht opnieuw kan verzenden. Dit venster is afhankelijk van het maximale aantal afleverpogingen van de broker, de time-out voor vergrendeling of zichtbaarheid daarvan en de levensduur van het bericht. Stel een time-to-live (TTL) in voor deduplicatierecords die langer is dan dit venster, zodat een late herlevering de bijbehorende markering nog steeds vindt. Als u records te vroeg verwijdert, wordt het venster voor duplicaten opnieuw geopend. Houd rekening met berichten die een operator opnieuw indient vanuit een wachtrij met onbestelbare berichten, omdat een herindiening ruim na het normale herleveringsvenster kan plaatsvinden.

  • Gebruik een messagingframework in plaats van zelf deduplicatie te bouwen. Het correct implementeren van de deduplicatieopslag, de atomaire commit en het opschonen van records kan gemakkelijk tot fouten leiden. Frameworks op basis van berichten bieden dit patroon als een ingebouwde functie.

    NServiceBus ontdubbelt bijvoorbeeld binnenkomende berichten op basis van hun bericht-id en biedt configureerbare retentie en opschoning voor ontdubbelingsgegevens. De MassTransit consumer inbox houdt ontvangen berichten bij aan de hand van hun bericht-ID om te garanderen dat de consumer elk bericht precies één keer verwerkt.

  • Deduplicatie door de broker vermindert, maar neemt de noodzaak van idempotente consumentenlogica niet weg. Sommige platforms filteren duplicaten op de transportlaag. Azure Service Bus duplicaatdetectie negeert berichten die binnen een geconfigureerd tijdvenster een herhaalde MessageId bevatten, waardoor duplicaten als gevolg van herhaalde verzendpogingen van de producent worden onderdrukt. Deze functie werkt aan de verzendzijde en binnen een begrensd venster. Het voorkomt niet dat een consument hetzelfde bericht twee keer na een herlevering verwerkt, dus u hebt nog steeds idempotente consumentenlogica nodig. Beschouw platformfuncties als een eerste verdedigingslaag die het aantal duplicaten vermindert, niet als vervanging van het patroon.

  • Houd rekening met berichtvolgorde. Ontdubbeling verwijdert duplicaten, maar garandeert niet dat de volgorde behouden blijft. Als de consument afhankelijk is van de verwerkingsorder, combineert u dit patroon met een bestelmechanisme, zoals Azure Service Bus berichtsessies, of neemt u reeks- of versiegegevens op waarmee de consument verouderde berichten kan weigeren.

  • Instrument voor waarneembaarheid. De ontdubbelingssleutel en een correlatie-id verzenden in gestructureerde logboeken en een metrische waarde bijhouden voor gedetecteerde duplicaten. Een stijgend duplicaatpercentage kan wijzen op een onjuiste configuratie van de producer, een te klein bevestigings- of vergrendelingsvenster, of niet goed functionerende consumers. Gebruik end-to-end tracering en correlatie om een bericht tussen services te volgen.

  • Geef idempotentie door aan onderliggende aanroepen. Als u één consument idempotent maakt, worden de services die worden aanroepen niet beschermd. Wanneer een consument downstreamservices aanroept als onderdeel van de verwerking, geeft u de idempotentiesleutel door, zodat elke laag zijn eigen werk kan ontdubbelen.

Wanneer gebruikt u dit patroon?

Gebruik dit patroon wanneer:

  • U ontvangt berichten van een broker die garandeert dat berichten ten minste één keer worden afgeleverd, wat de standaard is voor de meeste brokers.

  • Het opnieuw verwerken van een bericht produceert onjuiste resultaten, zoals dubbele financiële transacties, het maken van dubbele resources of herhaalde meldingen.

  • Meerdere concurrerende consumenten verwerken hetzelfde kanaal, waardoor gelijktijdige dubbele levering waarschijnlijk wordt.

Dit patroon is mogelijk niet geschikt wanneer:

  • Elke bewerking die de consument uitvoert, is al van nature idempotent, dus herverwerking is ongevaarlijk en ontdubbelingsboekhouding voegt kosten toe zonder voordeel.

  • De workload kan de effecten van incidentele dubbele verwerking tolereren en de kosten van een ontdubbelingsopslag wegen op tegen de impact van een duplicaat.

Idempotente verwerking buiten berichtenverkeer

Dit patroon past idempotentie toe bij berichtconsumenten, maar idempotente verwerking is een breder principe voor betrouwbaarheid. Elke bewerking die meer dan één keer kan worden uitgevoerd ten opzichte van een identieke taak, profiteert hiervan. Dit principe omvat ETL-transformaties (extract, transform, load) waarmee opnieuw afgespeelde gegevens worden verwerkt, stroomverwerking die wordt hervat vanaf een controlepunt, geplande taken die overlappen of opnieuw worden opgestart, en webhook- of HTTP-eindpunten die dubbele leveringen ontvangen.

In elk geval is dezelfde kerntechniek van toepassing:

  1. Identificeer de werkeenheid met een stabiele sleutel.
  2. Noteer wat u al hebt verwerkt.
  3. Dubbele waarden overslaan of absorberen, zodat het herhalen van het werk het resultaat niet wijzigt.

De mechanismen in dit artikel, zoals stabiele sleutels, atomische markeringen en unieke beperkingen, worden overgedragen naar deze contexten, zelfs als er geen berichtbroker betrokken is.

Ontwerp van werkbelasting

Evalueer hoe u het Idempotent Consumer-patroon gebruikt in het ontwerp van een workload om de doelstellingen en principes te verhelpen die worden behandeld in de pijlers van het Azure Well-Architected Framework. De volgende tabel bevat richtlijnen over hoe dit patroon de doelstellingen van elke pijler ondersteunt.

Pijler Hoe dit patroon ondersteuning biedt voor pijlerdoelen
betrouwbaarheid ontwerpbeslissingen helpen uw workload tolerant te worden defect te raken en ervoor te zorgen dat deze herstelt naar een volledig functionerende status nadat er een storing is opgetreden. Met dit patroon kan een workload ten minste eenmaal levering en veilige pogingen gebruiken zonder beschadigde gegevens, waardoor dubbele levering van een juistheidsrisico wordt omgezet in een getolereerde voorwaarde.

- RE:07 Zelfbehoud
- Tijdelijke fouten afhandelen

Als dit patroon compromissen binnen een pijler introduceert, moet u deze tegen de doelstellingen van de andere pijlers overwegen.

Example

In het volgende voorbeeld ziet u een idempotente consument die orders van Azure Service Bus verwerkt en de status behoudt in Azure Cosmos DB voor NoSQL.

Een producent stelt de Service Bus MessageId in op een order-id op bedrijfsniveau. De consument ontvangt berichten in de PeekLock-modus , waardoor een bericht opnieuw wordt verzonden als de consument het niet binnen de vergrendelingsduur voltooit. De Azure Cosmos DB-container van de consumer partitioneert op de orderidentificatie (/orderId) en stelt de documentwaarde id in op diezelfde orderidentificatie, zodat elke kopie van een bepaalde order in dezelfde logische partitie terechtkomt en het orderrecord zelf als deduplicatiemarker fungeert.

De consument verwerkt elk bericht als volgt:

  1. Lees het bericht en gebruik de MessageId als sleutel voor deduplicatie.
  2. Maak het orderdocument waarbij id en de partitiesleutel beide zijn ingesteld op de orderidentificatie.
  3. Als het aanmaken lukt, voltooi je het bericht zodat Service Bus het uit de wachtrij verwijdert.
  4. Als het aanmaken mislukt met de HTTP 409-status (Conflict) omdat er al een document met die id bestaat, lees dan het bestaande document en vergelijk dit met het huidige bericht. Als een opgeslagen aanvraag-hash of onveranderbare zakelijke velden overeenkomen, behandelt u het bericht als een duplicaat, voltooit u het en slaat u de verwerking over. Als ze niet overeenkomen, heeft de producent de identifier mogelijk opnieuw gebruikt voor andere content, of zijn de ordergegevens mogelijk gewijzigd sinds die voor het eerst zijn verwerkt. Plaats het bericht in dat geval in de dead-letter queue of genereer een waarschuwing, in plaats van het stilzwijgend te negeren.
  5. Als de verwerking door een tijdelijke oorzaak mislukt, moet u het bericht vrijgeven zodat Service Bus het opnieuw aflevert, of moet u de vergrendeling laten verlopen, zodat een andere ontvanger het ontvangt.

De maakbewerking is atomisch, dus deze fungeert als zowel de ontdubbelingscontrole als de schrijfbewerking. Twee consumenten die kopieën van hetzelfde bericht ontvangen, kunnen niet beide de bestelling maken. Eén create-bewerking slaagt, en de andere geeft een conflict terug en verwerpt het duplicaat veilig.

Wanneer de verwerking meer dan één document moet schrijven, gebruikt u een transactionele batch met zowel het ontdubbelingsdocument als de zakelijke documenten binnen dezelfde partitiesleutel. Omdat een transactiebatch binnen één logische partitie plaatsvindt, kiest u een partitiesleutel die door alle documenten voor één bericht wordt gedeeld. De batch committeert alle documenten in één keer of anders geen enkel document, dus een crash tussen verwerking en bevestiging kan de deduplicatiemarkering en de bedrijfsgegevens niet uit synchronisatie brengen. Een batch die probeert een document te maken dat al bestaat, retourneert een 409-status (Conflict), waarmee het duplicaat wordt geïdentificeerd.

Om deze consumer ook bestand te maken tegen dubbele nieuwe verzendpogingen, schakelt u duplicaatdetectie in op de wachtrij. Detectie van duplicaten onderdrukt herhaalde verzendingen binnen het bewaartijdvenster, en de idempotente verbruiker verwerkt eventuele duplicaten die buiten dat venster vallen of ontstaan door herlevering.

Volgende stap