Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Progettare i consumer di messaggi in modo che l'elaborazione dello stesso messaggio più di una volta abbia gli stessi effetti della sua elaborazione una sola volta. I sistemi di messaggistica che garantiscono il recapito di almeno una volta possono recapitare più volte lo stesso messaggio. La resilienza contro i duplicati garantisce che la rielaborazione di un messaggio non crei record duplicati, addebiti doppi per un cliente o altri effetti indesiderati.
Contesto e problema
Le applicazioni distribuite comunemente si scambiano attività tramite un broker di messaggi anziché utilizzare chiamate sincrone dirette. La maggior parte dei broker, tra cui bus di servizio di Azure, Hub eventi di Azure, Apache Kafka e RabbitMQ, supporta la consegna at-least-once. Questa garanzia garantisce che un messaggio raggiunga un consumer anche quando si verificano errori, ma significa anche che il broker può recapitare lo stesso messaggio più volte.
Non è pratico garantire la consegna esattamente una volta attraverso un sistema distribuito. Anche i broker che usano semantica di tipo exactly-once possono garantire solo le operazioni che controllano direttamente, ad esempio recapitando messaggi ai consumer o scrivendo nuovamente i dati nel broker. Non possono controllare gli effetti collaterali implementati dai consumer in sistemi esterni. La soluzione definitiva non consiste nell'eliminare la consegna duplicata, ma nel fare in modo che il consumer la elabori correttamente. Quando si combina il recapito almeno una volta con un consumer che ignora i duplicati, si ottiene un'elaborazione effettivamente una sola volta.
I duplicati possono derivare da diverse origini:
Tentativi del produttore. Un producer invia un messaggio, non riceve un riconoscimento a causa di un errore di rete temporaneo o di un timeout e invia di nuovo il messaggio. Il broker ora ha due copie anche se il primo invio è andato a buon fine.
Nuovo recapito in assenza di conferma di ricezione. Un consumer riceve ed elabora un messaggio ma non lo riconosce perché l'utente si arresta in modo anomalo, il blocco scade o il riconoscimento viene perso. Il broker presuppone che il messaggio non sia stato elaborato e lo recapita nuovamente.
Errori del componente consumer durante l'elaborazione. Un consumer completa la scrittura nel database ma va in crash prima di confermare la ricezione del messaggio. Un'altra istanza recupera il messaggio e ripete l'operazione di scrittura.
Soluzione
Creare un consumer idempotente facendo in modo che mantenga una traccia dei messaggi che elabora con successo e che salti quelli che ha già elaborato, in base a un identificatore stabile che sopravvive alla riconsegna. Il consumer controlla l'archivio degli identificatori persistenti per determinare se ha già elaborato quell'identificatore, quindi elabora il messaggio oppure lo scarta come duplicato.
Il flusso principale è costituito dai passaggi seguenti:
- Leggere il messaggio ed estrarre la chiave di deduplicazione.
- Controlla l'archivio di deduplicazione per quella chiave.
- Se la chiave esiste già nell'archivio, considera il messaggio come duplicato. Confermare il messaggio e interrompere l'elaborazione, restituendo facoltativamente il risultato registrato in precedenza.
- Se la chiave non esiste ancora nell'archivio, elaborare il messaggio e registrare la chiave in una singola operazione atomica e quindi confermare il messaggio.
Le sezioni seguenti forniscono indicazioni per rendere idempotenti i consumatori:
Scegliere una chiave di deduplicazione stabile
La chiave di deduplicazione deve identificare in modo univoco e coerente il messaggio logico a ogni nuova consegna. Usare un identificatore di messaggio assegnato dal produttore oppure una chiave di idempotenza a livello di business che identifichi la specifica operazione logica, non un contesto di correlazione condiviso che più messaggi possono contenere.
Ad esempio, impostare la proprietà bus di servizio MessageId su un valore che identifica in modo univoco il messaggio logico. Non usare CorrelationId come chiave, perché fa riferimento a gruppi di messaggi correlati, ad esempio una richiesta e le relative risposte. Per gli eventi che seguono la specifica CloudEvents, la combinazione di attributi source e id identifica in modo univoco un evento e rimane stabile nelle riconsegne.
Non basarti su identificatori a livello di trasporto che il broker rigenera alla nuova consegna, né su valori derivati dai tentativi di consegna. Questi valori cambiano tra le consegne e sconfiggono il rilevamento dei duplicati. Evitare inoltre di derivare la chiave da campi volatili, ad esempio i timestamp di ricezione.
Quando più consumer indipendenti elaborano lo stesso canale, ad esempio più sottoscrittori in una progettazione di pubblicazione-sottoscrizione, ogni consumer riceve la propria copia di un messaggio e deve tenere traccia in modo indipendente del completamento dell'elaborazione. Se i consumer condividono un archivio di deduplicazione, indicizza i record usando una chiave composta dall'identificativo del consumer e dall'identificativo del messaggio. Un archivio indicizzato unicamente in base all'identità del messaggio consentirebbe al primo consumatore di impedire l'elaborazione da parte di tutti gli altri.
Decidere dove archiviare le chiavi elaborate
Le opzioni di archiviazione seguenti sono comuni per le chiavi elaborate:
Tabella di deduplicazione dedicata. Il consumer mantiene una tabella separata, talvolta chiamata inbox, che contiene una riga per ogni chiave elaborata. Questo approccio mantiene i problemi di deduplicazione separati dai dati aziendali e funziona bene se molti tipi di messaggio condividono lo stesso meccanismo.
Entità aziendale stessa. Il consumer archivia la chiave nel record creato o aggiornato dal messaggio. Questo approccio evita una tabella separata, ma associa la deduplicazione al tipo di dati business.
Eseguire il commit della chiave elaborata e degli effetti collaterali in modo atomico
Un flusso controlla-poi-elabora presenta una finestra di guasto. Se un consumatore elabora un messaggio e poi registra la chiave in un passaggio separato, un crash tra le due operazioni lascia applicati gli effetti collaterali ma la chiave non viene registrata, quindi il consumatore rielabora il messaggio alla successiva riconsegna.
Evitare questa finestra di errore scrivendo il marcatore di deduplicazione e gli effetti collaterali aziendali nella stessa transazione. Se un consumer effettua il commit di entrambe le operazioni insieme oppure non ne effettua alcuna, in caso di riconsegna trova il marcatore e salta il messaggio, oppure non trova alcun marcatore perché la transazione non si è completata e rielabora il messaggio in modo sicuro. Questa variante transazionale è lo schema Inbox ed è la controparte lato consumer dello schema del producer Transactional Outbox pattern.
Proteggersi dai duplicati simultanei
Con la consegna almeno una volta con consumer in competizione, due istanze possono ricevere copie dello stesso messaggio contemporaneamente. Entrambe le istanze possono superare il controllo di esistenza prima che una delle due esegua il commit, quindi il solo controllo non impedisce la doppia elaborazione.
Applicare la correttezza all'archivio dati anziché nella logica dell'applicazione seguendo questa procedura:
Usare un vincolo di univocità sulla chiave di deduplicazione in modo che due transazioni possano tentare di inserire una chiave, ma solo una può avere esito positivo. L'altra transazione non soddisfa il vincolo e tratta il messaggio come un duplicato. Questo approccio rende il database il singolo arbitro del conflitto.
Evitare condizioni di competizione di tipo check-then-set nelle cache. Uno schema che prima controlla una chiave e poi la imposta in due operazioni separate lascia una finestra temporale in cui i tentativi ripetuti concorrenti possono appropriarsi della chiave. Usare una scrittura condizionale atomica, ad esempio un inserimento che fallisce in caso di conflitto o un'operazione di impostazione se assente, per rendere l'acquisizione della chiave un unico passaggio atomico.
Gestire gli effetti collaterali che non possono partecipare alla transazione
Alcuni processi non possono partecipare alla transazione del database del consumer, come chiamare un'API di terze parti o scrivere in un archivio esterno. Usare l'approccio in due fasi seguente per questi processi:
- Registrare la chiave con uno stato in corso e quindi eseguire l'azione esterna.
- Aggiornare il record in modo che sia completato e archiviare il risultato.
In caso di nuova consegna, un record completato indica al consumer di non ripetere la chiamata. Un record in corso segnala che un tentativo precedente potrebbe essere parzialmente completato o che viene elaborato da un altro consumer. Il consumer deve riconciliare i record obsoleti o inoltrare i casi non risolti per un intervento prima di confermare la riconsegna.
Problemi e considerazioni
Quando si decide come implementare questo modello, tenere presente quanto segue:
Preferisci operazioni intrinsecamente idempotenti. Alcune operazioni sono intrinsecamente idempotenti e non richiedono la gestione della deduplicazione. Un upsert basato su un identificatore aziendale, una scrittura che imposta un valore assoluto anziché un incremento oppure un’operazione HTTP
PUTsu un identificatore di risorsa producono gli stessi risultati, sia eseguiti una sola volta sia più volte.A volte è possibile rendere un'operazione naturalmente idempotente utilizzando il trasferimento dello stato veicolato dall'evento. Il messaggio contiene lo stato assoluto risultante, ad esempio il nuovo stato di un ordine, in modo che il consumer lo applichi come upsert invece di una modifica relativa.
Tip
Progettare tenendo conto dell'idempotenza naturale, se possibile, e utilizzare tecniche di deduplicazione solo per le operazioni che non possono essere rese naturalmente idempotenti.
Usare un framework di messaggistica invece di configurare la deduplicazione. L'implementazione corretta dell'archiviazione, del commit e della pulizia della deduplicazione è soggetta a errori. I framework basati su messaggi forniscono questo modello come funzionalità predefinita.
Ad esempio, NServiceBus deduplica i messaggi in ingresso in base ai relativi identificatori di messaggio e fornisce conservazione e pulizia configurabili per i dati di deduplicazione. Il consumer outbox di MassTransit tiene traccia dei messaggi ricevuti in base ai relativi identificatori di messaggio per garantire un comportamento del consumer exactly-once.
Gestire il ciclo di vita dei record di deduplicazione. I record di deduplicazione si accumulano a meno che non vengano impostati per scadere. Conservare ogni record almeno finché il broker può comunque recapitare di nuovo il messaggio originale. La dimensione di questa finestra dipende dal numero massimo di tentativi di recapito del broker, dal timeout di blocco o di visibilità e dal tempo di vita del messaggio.
Impostare un time-to-live per i record di deduplicazione superiore a questa finestra, in modo che una ritentata consegna tardiva trovi ancora il relativo marcatore. L'eliminazione troppo precoce dei record riapre il margine per la comparsa di duplicati. Tenere conto dei messaggi che gli operatori inviano nuovamente dalle code dei messaggi non recapitati, perché questi reinvii possono verificarsi molto tempo dopo la chiusura della normale finestra di riconsegna.
Non sostituire la deduplicazione broker per la logica consumer idempotente. Alcune piattaforme filtrano i duplicati a livello di trasporto. Ad esempio, il rilevamento dei duplicati di bus di servizio scarta i messaggi che ripetono un
MessageIdentro un intervallo di tempo configurato, sopprimendo i tentativi ripetuti di invio duplicato da parte dei producer.Questa funzionalità funziona sul lato dell'invio ed entro una finestra limitata, pertanto non impedisce a un consumer di elaborare due volte lo stesso messaggio dopo una nuova consegna. Serve ancora una logica idempotente del consumer. Utilizza le funzionalità della piattaforma per ridurre il volume di duplicati, non in sostituzione del pattern Idempotent Consumer.
Tenere conto dell'ordinamento dei messaggi. La deduplicazione rimuove i duplicati, ma non garantisce l'ordine. Se il consumer dipende dall'ordine di elaborazione, è opportuno combinare questo modello con un meccanismo di ordinamento, ad esempio le sessioni di messaggi di bus di servizio, oppure includere informazioni sulla sequenza o sulla versione in modo che il consumer possa rifiutare i messaggi obsoleti.
Strumento per l'osservabilità. Generare la chiave di deduplicazione e un identificatore di correlazione nei log strutturati e tenere traccia di una metrica per i duplicati rilevati. Un aumento del tasso di duplicati può indicare un errore di configurazione del produttore, una finestra di conferma o di blocco sottodimensionata, oppure consumatori malfunzionanti. Usare la traccia distribuita e la correlazione per seguire un messaggio tra i servizi.
Propaga l'idempotenza alle chiamate a valle. Rendere idempotente il consumer di messaggi non protegge i servizi che chiama. Quando un client richiama servizi a valle nell'ambito dell'elaborazione, propaga la chiave di idempotenza in modo che ogni livello di servizio possa eliminare eventuali duplicazioni nelle proprie attività.
Quando usare questo modello
Usare questo modello quando:
Si consumano messaggi da un broker che fornisce una consegna almeno una volta, che è l'impostazione predefinita per la maggior parte dei broker.
La rielaborazione di un messaggio può produrre risultati non corretti, ad esempio transazioni finanziarie duplicate, creazione di risorse duplicate o notifiche ripetute.
Più consumer concorrenti elaborano lo stesso canale, il che rende più probabile la consegna duplicata simultanea.
Questo modello potrebbe non essere adatto quando:
Le operazioni eseguite dal consumer sono già naturalmente idempotenti, quindi la rielaborazione è innocua e la gestione della deduplicazione aggiunge costi senza apportare benefici.
Il carico di lavoro può tollerare gli effetti di un'elaborazione duplicata occasionale e il costo di un archivio di deduplicazione supera l'effetto di un duplicato.
Elaborazione idempotente oltre la messaggistica
Questo modello applica l'idempotenza ai consumatori di messaggi, ma l'elaborazione idempotente è un principio di affidabilità più ampio che può apportare benefici a qualsiasi operazione eseguita più di una volta sullo stesso identico compito. Questo principio include trasformazioni ETL (extract, transform, load) che rielaborano dati ripetuti, l'elaborazione dei flussi che riprende da un checkpoint, processi pianificati che si sovrappongono o vengono riavviati e webhook o endpoint HTTP che ricevono richieste duplicate.
La stessa tecnica di base si applica in ogni caso.
- Usare una chiave stabile per identificare l'unità di lavoro.
- Registra ciò che elabori.
- Ignorare o assorbire le esecuzioni duplicate in modo che la ripetizione del lavoro non modifichi il risultato.
I meccanismi di questo articolo, ad esempio chiavi stabili, marcatori atomici e vincoli di univocità, vengono trasferiti a questi contesti anche quando non è coinvolto alcun broker di messaggi.
Progettazione del carico di lavoro
Valutare come usare il modello del consumer idempotente nella progettazione di un carico di lavoro per supportare gli obiettivi e i principi descritti nei pilastri dell'Azure Well-Architected Framework. La tabella seguente fornisce indicazioni su come questo modello supporta gli obiettivi di ogni pilastro.
| Pilastro | Come questo modello supporta gli obiettivi di pilastro |
|---|---|
| decisioni di progettazione dell'affidabilità consentono al carico di lavoro di diventare resiliente a un malfunzionamento e assicurano che ripristini a uno stato completamente funzionante dopo che si verifica un guasto. | Questo modello consente a un carico di lavoro di usare la consegna almeno una volta e ritentativi sicuri senza compromettere i dati, trasformando la consegna duplicata da un rischio per la correttezza in una condizione tollerata. - RE:07 Autoconservazione - errori temporanei |
Se questo modello introduce compromessi all'interno di un pilastro, considerarli contro gli obiettivi degli altri pilastri.
Example
Nell'esempio seguente viene descritto un consumer idempotente che elabora gli ordini da bus di servizio e mantiene lo stato in Azure Cosmos DB per NoSQL.
- Un produttore imposta il bus di servizio
MessageIdcome identificatore dell’ordine a livello aziendale. - Il consumer riceve il messaggio in modalità PeekLock, che rende il messaggio nuovamente disponibile per la consegna se il consumer non lo finalizza prima della scadenza del blocco.
- Il consumer di Azure Cosmos DB partiziona il contenitore in base all'identificatore dell'
/orderIdordine e imposta il documentoidcon lo stesso identificatore dell'ordine, quindi ogni copia di un determinato ordine viene associata alla stessa partizione logica e lo stesso ordineidfunge da marcatore di deduplicazione.
Il consumer esegue i passaggi seguenti per elaborare ogni messaggio:
- Leggere il messaggio e usarlo
MessageIdcome chiave di deduplicazione. - Provare a creare il documento dell'ordine con
ide la chiave di partizione entrambi impostati sull'identificatore dell'ordine. - Se la creazione ha esito positivo, completa il messaggio in modo che bus di servizio lo rimuova dalla coda.
- Se la creazione non riesce con un codice di stato HTTP 409 (Conflitto) perché esiste già un documento con quel
id, leggere il documento esistente e confrontarlo con il messaggio corrente. - Se l’hash della richiesta archiviata o i campi business immutabili corrispondono, considera il messaggio come duplicato, contrassegnalo come completato e salta l’ulteriore elaborazione.
- Se l’hash della richiesta archiviata o i campi business immutabili non corrispondono, inviare il messaggio a una coda dead-letter e generare un avviso, anziché scartare il messaggio senza segnalarlo. Il producer potrebbe aver riutilizzato l'identificatore per contenuti diversi oppure i dettagli del messaggio potrebbero essere stati modificati dopo la prima elaborazione dell'ordine.
- Se l'elaborazione non riesce per un motivo transitorio, abbandonare il messaggio in modo che bus di servizio lo riconsegni oppure lasciare scadere il blocco in modo che un altro consumer possa riceverlo.
L'operazione di creazione è atomica, quindi funge sia da controllo di deduplicazione che dall'operazione di scrittura. Due consumer che ricevono copie dello stesso messaggio non possono creare entrambi l'ordine. Un tentativo di creazione ha esito positivo e l'altro tentativo restituisce un conflitto e rimuove in modo sicuro il duplicato.
Quando l'elaborazione deve scrivere più documenti, usare un batch transazionale che include sia la chiave di deduplicazione che i documenti aziendali all'interno della stessa chiave di partizione. Poiché un batch transazionale opera all'interno di una singola partizione logica, è necessario scegliere una chiave di partizione condivisa da tutti i documenti di uno stesso messaggio. Il batch conferma tutti i documenti insieme oppure nessuno di essi, quindi un arresto anomalo tra l'elaborazione e la conferma non può causare una desincronizzazione tra l'indicatore di deduplicazione e i dati applicativi. Un batch che tenta di creare un documento già esistente restituisce lo stato 409 (Conflict), il che consente di identificare il duplicato.
Per rendere resiliente anche questo consumer idempotente rispetto ai tentativi di invio duplicati, abilitare il rilevamento dei duplicati nella coda. In una coda Standard o Premium, il rilevamento dei duplicati sopprime gli invii ripetuti entro il relativo intervallo di cronologia. Il consumer idempotente continua comunque a gestire tutti i duplicati che ricadono al di fuori di quella finestra o che risultano da una riconsegna.
Passo successivo
- La progettazione di Funzioni di Azure per l'input identico fornisce indicazioni per la creazione di funzioni idempotenti che tollerano chiamate duplicate.
" output is necessary.)
Le opzioni di messaggistica asincrone in Azure descrivono le scelte dell'infrastruttura di messaggistica che determinano le garanzie di recapito e i requisiti di gestione duplicati.
Il pattern Transactional Outbox, che pubblica i messaggi in modo affidabile effettuandone il commit nella stessa transazione dei dati aziendali, è il lato del pubblicatore del pattern Idempotent Consumer.
Il modello di ripetizione consente alle applicazioni di gestire gli errori transitori riprovando le operazioni, il che rende necessaria un'elaborazione idempotente perché i tentativi possono causare un recapito duplicato.
Progettazione resiliente di Hub eventi di Azure e Funzioni di Azure applica questo modello alle funzioni attivate da Event Hubs, comprese le tecniche di deduplicazione per i flussi di eventi.