Microservizi: gestione dell'eventuale coerenza


22

Supponiamo di avere una funzione che aggiorna la password di un utente.

Dopo aver fatto clic sul pulsante "Aggiorna password", un UpdatePasswordEvent viene inviato a un argomento a cui sono abbonati altri 3 servizi:

  1. Un servizio che aggiorna effettivamente la password dell'utente
  2. Un servizio che aggiorna la cronologia delle password dell'utente
  3. Un servizio che invia un'e-mail per informare l'utente che la sua password è stata modificata.

Sulla base di ciò che ho capito sull'eventuale coerenza, tutti questi servizi (consumatori) riceveranno l'evento contemporaneamente e li elaboreranno separatamente, il che, in un buon scenario, porterà alla coerenza dei dati.

Tuttavia, cosa succede se un servizio non riesce a elaborare l'evento? ad es. disconnessione improvvisa, errore del database, ecc ... Qual è un buon modello / pratica per gestire questi errori di transazione?

Stavo pensando di creare un RollbackTopic in cui se un evento non viene elaborato, verrà creato un RollbackEvent in un argomento in cui "servizi di rollback" farà il suo lavoro e ripristinerà i dati


11
Non è possibile annullare un'e-mail inviata :-)
Laiv

2
Perché tutti dovrebbero far parte dello stesso servizio. Il micro servizio è contrario ai monoliti, non significa che devi progettarli il meno "fisicamente" possibile. Sebbene ciò non sia direttamente correlato, dovresti leggere questa domanda e le due risposte principali: softwareengineering.stackexchange.com/questions/339230/…
Walfrat

1
Potresti considerare di aggiornare la password dell'utente nel database in modo sincrono, in modo da fornire un feedback immediato all'utente e attivare altri servizi in modo asincrono emettendo un messaggio che la password è cambiata su un argomento, in modo che il tuo messaggio non debba contiene la password.
cr3

È l'e-mail per dire all'utente che la transazione è stata completata, oppure è lì per dire all'utente che qualcuno (si spera) abbia cambiato la password. "Se non eri tu, allora devi agire". Se il 2 ° allora basta inviare e-mail ora, nel miglior modo possibile.
ctrl-alt-delor

Risposte:


29

Sulla base di ciò che ho capito sull'eventuale coerenza, tutti questi servizi (consumatori) riceveranno l'evento contemporaneamente e li elaboreranno separatamente , il che, in un buon scenario, porterà alla coerenza dei dati.

No, non necessariamente. Come ho commentato, non possiamo annullare un'e-mail inviata, quindi abbiamo ancora bisogno di una sorta di "sequenza". L'IPC sulla gestione dei dati basata sugli eventi non è esente dall'orchestrazione 1 .

Ad esempio, l'e-mail non deve essere inviata a meno che le transazioni precedenti non vengano completate correttamente e il servizio e-mail non ne ottenga una prova. 3

Tuttavia, cosa succede se un servizio non riesce a elaborare l'evento? ad es. disconnessione improvvisa, errore del database, ecc ... Qual è un buon modello / pratica per gestire questi errori di transazione?

Saluta gli errori del calcolo distribuito . Sono ciò che rende le cose complicate e, come al solito, non ci sono proiettili d'argento per affrontarle.

Prima di iniziare il nostro viaggio alla ricerca dell'Arca perduta, dobbiamo considerare di chiedere prima all'organizzazione. Spesso, la soluzione sta nel modo in cui l'organizzazione affronta questi problemi nel mondo reale .

Cosa fanno tutti (dipartimenti) quando alcuni dati mancano o sono incompleti?

Arriveremo a capire che dipartimenti diversi hanno soluzioni diverse che, nel complesso, comprendono la soluzione da implementare.

Comunque, ecco alcune pratiche che potrebbero aiutarci con la strategia da seguire.

Consistenza finale

Piuttosto che garantire che il sistema sia costantemente in uno stato coerente, invece possiamo accettare che il sistema lo otterrà ad un certo punto in futuro. Questo approccio è particolarmente utile per operazioni commerciali di lunga durata.

Il modo in cui il sistema raggiunge la coerenza varia da sistema a sistema. Potrebbe comportare processi automatizzati a qualche tipo di intervento umano. Ad esempio, il tipico riprovare più tardi o il contatto con il servizio clienti .

Annullare tutte le operazioni

Riporta il sistema in uno stato coerente tramite transazioni compensative . Tuttavia, dobbiamo tenere conto del fatto che anche queste transazioni possono fallire, il che potrebbe portarci a un punto in cui è ancora più difficile risolvere l'incongruenza. E, ancora una volta, non possiamo annullare un'e-mail inviata.

Per un numero basso di transazioni, questo approccio è fattibile, perché anche il numero di transazioni compensative è basso. Se nell'IPC fossero coinvolte diverse transazioni commerciali, gestire una transazione di compensazione per ciascuna di esse sarebbe una sfida.

Se andiamo a compensare le transazioni , troveremo che il modello di progettazione dell'interruttore sia molto utile - e obbligatorio oserei dire -

Transazioni distribuite

L'idea è quella di estendere più transazioni all'interno di una singola transazione, attraverso un processo di governo generale noto come Transaction Manager . Un algoritmo comune per la gestione delle transazioni distribuite è il commit in due fasi .

La preoccupazione principale delle transazioni distribuite è che si affidano al blocco delle risorse durante la sua vita e, come sappiamo, le cose possono andare male anche per il Transaction Manager .

Se i Transaction Manager vengono compromessi, possiamo finire con diversi blocchi in tutti i diversi contesti limitati, con conseguenti comportamenti imprevisti dovuti all'accodamento dei messaggi. 2

Operazioni di decomposizione. Perché?

Se stai decomponendo un sistema esistente e trovi una raccolta di concetti che vogliono davvero essere all'interno di un singolo limite di transazione, forse lasciali fino all'ultimo.

Sam Newman

In linea con gli argomenti di cui sopra, Sam - nel suo libro Building Microservices - afferma che, se davvero, davvero non possiamo permetterci l'eventuale coerenza, dovremmo evitare di dividere l'operazione ora.

Se non possiamo permetterci di suddividere determinate operazioni in due o più transazioni, si potrebbe dire che, probabilmente, queste transazioni appartengono allo stesso contesto limitato o, almeno, a un contesto trasversale che rimane da modellare.

Ad esempio, nel nostro caso, ci rendiamo conto che le transazioni n. 1 e n. 2 sono strettamente correlate tra loro e probabilmente entrambe potrebbero appartenere allo stesso contesto limitato Conti , utenti , registro , qualunque cosa ...

Considerare di collocare entrambe le operazioni entro i limiti della stessa transazione. Renderebbe l'intera operazione più semplice da gestire. Inoltre pesa il livello di criticità di ogni transazione. Probabilmente, se la transazione n. 2 fallisce, non dovrebbe compromettere l'intera operazione. In caso di dubbi chiedere all'organizzazione .


1: Non il tipo di orchestrazione che pensi. Non sto parlando dell'orchestrazione di ESB. Sto parlando di far reagire i servizi all'evento corretto.

2: Potresti trovare interessanti opinioni di Sam Newman riguardo alle transazioni distribuite.

3: Dai un'occhiata alla risposta di David Parker su questo argomento.


3
Ottima risposta Vorrei solo sottolineare l'importanza di tenere conto dei rischi che si presentano quando si utilizzano le transazioni distribuite, principalmente il blocco delle risorse che produce deadlock e blocchi dei sistemi. Su un prodotto di e-commerce su cui ho lavorato circa 3 anni fa abbiamo dovuto sostituire i DT con il sistema di messaggistica, perché con la quantità di utenti disponibili nei sistemi, il sistema era molto soggetto a errori. I problemi con i DT si verificano principalmente quando cresce una base di utenti.
Andy,

7

Nel tuo caso non puoi semplicemente elaborare tutte e tre le cose contemporaneamente. Ciò di cui hai bisogno è un processo. Ecco un esempio estremamente semplificato:

Orchestrazione di comandi ed eventi

È importante sapere che le operazioni di modifica dello stato DEVONO essere sempre effettuate su un'entità coerente. A meno che non sia possibile garantire una forte coerenza , deve essere creato su un record master.

Il sistema deve garantire che prima che qualsiasi evento venga generato nelle modifiche del sistema DEVE essere persistito con la sicurezza transazionale prima. Questo per garantire che un evento generato sia davvero una conferma di ciò che è realmente accaduto.

Esistono diverse parti del processo così com'è e ho intenzione di ignorare quelle ovvie, come ad esempio: Cosa succede se il tuo server di database muore quando persisti un utente con password modificata? È sufficiente emettere nuovamente UpdatePassword. Tuttavia, alcune parti devono essere curate da te e queste sono:

  • gestire la duplicazione dei messaggi,
  • gestire l'invio di e-mail.

In un sistema, process orchestrator (PO) non è altro che un'altra entità, che contiene stato interno - anche nel termine letterale - e consente transizioni tra gli stati, agendo efficacemente come una sorta di macchina a stati. Grazie allo stato interno è possibile rimuovere l'elaborazione della duplicazione dei messaggi.

Quando l'OP si trova in uno Newstato ed elabora UserPasswordHasBeenUpdated, cambia il suo stato in UserPasswordHasBeenUpdated(o qualunque nome di stato funzioni per te). Se l'OP fosse ancora in un UserPasswordHasBeenUpdatede un altro UserPasswordHasBeenUpdatedsarebbe arrivato, l'OP ignorerebbe completamente il messaggio, sapendo che è una duplicazione. Meccanismi simili verrebbero implementati anche per altri stati.

Gestire l'effettivo invio dell'e-mail è un po 'più complicato. Qui hai due opzioni:

  1. inviarlo al massimo una volta,
  2. inviarlo almeno una volta.

Invialo al massimo una volta

Con questa opzione, quando l'OP ha raggiunto lo UserPasswordHistoryHasBeenSavedstato, viene inviato un comando per inviare un'e-mail come reazione al cambio di stato. Il tuo sistema assicurerebbe che lo UserPasswordHistoryHasBeenSavedstato sarebbe persistito prima di inviare l'e-mail, ovvero che un messaggio duplicato non avrebbe innescato nuovamente l'invio dell'e-mail. Con questo approccio, si garantisce che lo stato corretto venga salvato per l'OP, ma non è possibile garantire alcuna operazione successiva.

Invialo almeno una volta

Questo è quello che vorrei fare.

Invece di salvare UserPasswordHistoryHasBeenSavede inviare l'e-mail come reazione ad essa, si tenta di inviare prima l'e-mail. Se l'operazione di invio fallisce, lo stato dell'OP non viene mai modificato UserPasswordHistoryHasBeenSavede viene comunque elaborato un altro messaggio dello stesso tipo. Se l'invio dell'e-mail dovesse effettivamente riuscire, ma il sistema non funzionasse durante il persistere dell'OP con il suo nuovo UserPasswordHistoryHasBeenSavedstato, un altro messaggio dell'attivatore UserPasswordHistoryHasBeenSavedavrebbe nuovamente attivato il comando di inviare l'e-mail e l'utente l'avrebbe ricevuto più volte .

Nel tuo caso, vuoi assicurarti che l'utente riceva effettivamente l'e-mail. Ecco perché sceglierei le seconde opzioni rispetto alla prima.


2

I sistemi di accodamento non sono così fragili come potresti pensare.

Se scrivessimo tutti e tre i processi su un db relazionale, potremmo usare una transazione per gestire un fallimento dei processi intermedi.

Senza il commit finale il lavoro parziale verrebbe scartato.

In un sistema basato su code avrai opzioni simili quando leggi un messaggio dalla coda per gestire errori di metà processo.

Amazon SQS, ad esempio, nasconde semplicemente i messaggi letti. a meno che non venga inviato un comando Elimina finale, il messaggio riapparirà o verrà inserito in una coda di messaggi non instradabili.

Puoi implementare "transazioni" simili in vari modi, essenzialmente conservando una copia del messaggio fino a quando non ricevi la conferma della corretta elaborazione. Se la conferma non viene ricevuta in tempo. puoi inviare di nuovo il messaggio o conservarlo per l'attenzione manuale.

Potenzialmente potresti creare un "servizio di rollback" che controllava questi messaggi errati, conosceva i messaggi correlati e lo stato passato ed eseguiva un rollback.

Tuttavia! Di solito è meglio inviare nuovamente i messaggi errati. Dopo tutto, questi tendono ad essere casi limite. O un server ha fallito catastroficamente o si è verificato un errore nella gestione di un determinato tipo di messaggio.

Una volta avvisato dell'errore, il servizio può essere riparato e i messaggi elaborati correttamente. Riportare il sistema nel suo insieme a uno stato coerente.


2

Quello che stai affrontando qui è il problema dei due generali . In sostanza: come si può essere sicuri che un messaggio sia ricevuto e si verifichi una risposta a quel messaggio? In molti casi, non esiste una soluzione perfetta. In effetti, in un sistema distribuito è spesso impossibile ottenere esattamente una volta la consegna dei messaggi.

Una prima ovvia osservazione è che il servizio che modifica la password dovrebbe inviare l'evento di cambio password. In questo modo la cronologia delle password e i servizi di invio della posta vengono attivati ​​solo quando la password cambia effettivamente, indipendentemente dal motivo per cui è cambiata.

Per risolvere effettivamente il tuo problema non prenderei in considerazione le transazioni distribuite, ma guarderei invece nella direzione della consegna del messaggio almeno una volta e dell'elaborazione idempotente.

  • Almeno una volta

    Per assicurarsi che l'evento di modifica della password sia effettivamente visto da tutti i consumatori, è necessario utilizzare un canale di comunicazione durevole in cui i messaggi possano essere consumati in uno stile "almeno una volta". I consumatori riconoscono un messaggio come consumato solo quando lo hanno completamente elaborato. Se, ad esempio, il servizio di cronologia delle password si arresta in modo anomalo durante la scrittura di una voce della cronologia, rileggerà lo stesso evento di modifica della password dopo il riavvio e riproverà, riconoscendo tale evento come di sola lettura dopo che è stato scritto nella cronologia. È necessario scegliere una soluzione di coda messaggi in base alla sua capacità di reinviare i messaggi fino a quando non vengono riconosciuti.

  • idempotence

    Dopo aver raggiunto la consegna almeno una volta c'è il problema delle azioni duplicate che si verificano quando un messaggio è stato parzialmente elaborato prima che il consumatore venga interrotto e successivamente rielaborato in seguito. Ciò dovrebbe essere risolto progettando ogni servizio in modo che sia idempotente. O le scritture che esegue possono verificarsi più volte senza effetti negativi, oppure mantiene il proprio archivio delle azioni intraprese ed evita di eseguire un'azione più di una volta. Nel caso di invio di posta, probabilmente non vale la pena provare a farlo funzionare in modo idempotente e stare bene con occasionalmente una posta inviata due volte.

In ogni caso, fai attenzione a come micro rendi i tuoi servizi. Il servizio di cronologia delle password deve davvero essere indipendente dal servizio di modifica delle password?


1

Non sono d'accordo con molte risposte.

  1. Invia subito l'e-mail “Qualcuno ha cambiato la tua password. Se eri tu, non hai bisogno di fare nulla. Se non il panico. ”Questo arriverà quando arriverà.
  2. Cambia la password Sebbene tu abbia un'eventuale coerenza. Vuoi assicurarti che questa sessione veda le modifiche fatte dall'utente.

Ci sono altre promesse di coerenza che è possibile aggiungere.

  • Accertarsi che i cambiamenti avvengano nell'ordine temporale.
  • Assicurarsi che un utente non visualizzi mai un rollback, ma altri utenti potrebbero ancora non vedere la modifica.
  • Ce ne sono altri

Queste consistenze aggiuntive dovranno essere implementate a seconda degli atti dell'applicazione.


Non ho idea di cosa intendi con "aggiorna la cronologia", ma per favore non cambiare mai la cronologia. Se si sta semplicemente estendendo il DAG, ciò dovrebbe causare il cambiamento nello stato corrente. Non sono indipendenti. Se lo sono, non puoi fare affidamento sulla storia che riflette ciò che è successo. (e, ultimo ma non meno importante, non archiviare le password vedere come non memorizzare le password )


Se riesci a inviare l'e-mail all'inizio, il tuo approccio va bene. Se devi inviare qualcosa insieme all'email. Forse una sorta di collegamento / dati che può essere ottenuto solo dopo aver raggiunto la coerenza, quindi non è possibile inviare prima l'e-mail. Questo è quello che ho commentato consider asking the organization first.. Probabilmente hai ragione. Tuttavia, ho trovato importante condizionare quegli eventi che non possiamo annullare. Ad esempio notifiche all'utente finale. La notifica che si trova sullo stato reale dei dati dell'utente potrebbe provocare una brutta impressione.
Laiv

Detto questo, per questo specifico scenario (notifica di modifica della password), ho concordato con questo approccio. Non appena soddisfa i requisiti.
Laiv
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.