Qual è la strategia di transazione più accettata per i microservizi

71

Uno dei principali problemi che ho riscontrato in un sistema con microservizi è il modo in cui le transazioni funzionano quando si estendono su diversi servizi. All'interno della nostra stessa architettura, abbiamo utilizzato transazioni distribuite per risolvere questo problema, ma hanno i loro problemi. Soprattutto i deadlock sono stati un dolore finora.

Un'altra opzione sembra essere una sorta di gestore delle transazioni personalizzato, che conosce i flussi all'interno del sistema, e si prenderà cura dei rollback per te come un processo in background che si estende su tutto il tuo sistema (così dirà all'altro servizi di rollback e se sono inattivo, avvisali in seguito).

Esiste un'altra opzione accettata? Entrambi sembrano avere i loro svantaggi. Il primo potrebbe causare deadlock e un sacco di altri problemi, il secondo potrebbe causare incoerenze nei dati. Ci sono opzioni migliori?

    
posta Kristof 27.07.2015 - 07:13
fonte

7 risposte

36

L'approccio abituale è isolare i microservizi il più possibile - trattarli come unità singole. Quindi le transazioni possono essere sviluppate nel contesto del servizio nel suo insieme (cioè non fanno parte delle normali transazioni DB, sebbene sia comunque possibile avere transazioni DB interne al servizio).

Pensa a come avvengono le transazioni e che tipo ha senso per i tuoi servizi, allora puoi implementare un meccanismo di rollback che disfa l'operazione originale, o un sistema di commit a 2 fasi che riserva l'operazione originale fino a quando non viene detto di eseguire il commit per davvero. Ovviamente entrambi questi sistemi significano che stai implementando il tuo, ma poi stai già implementando i tuoi microservizi.

I servizi finanziari fanno sempre questo genere di cose - se voglio trasferire denaro dalla mia banca alla tua banca, non esiste una singola transazione come quella che avresti in un DB. Non si conoscono i sistemi in cui sta funzionando una banca, quindi devono trattare efficacemente ciascuno come i propri microservizi. In questo caso, la mia banca trasferirà i miei soldi dal mio account a un conto di deposito e poi dirà alla vostra banca che hanno dei soldi, se l'invio fallisce, la mia banca rimborserà il mio account con i soldi che hanno provato a inviare.

    
risposta data 27.07.2015 - 14:41
fonte
27

Penso che la saggezza standard sia quella di non avere mai transazioni che attraversino i confini dei microservizi. Se una data serie di dati ha davvero bisogno di essere atomicamente coerenti con un'altra, queste due cose appartengono insieme.

Questo è uno dei motivi per cui è molto difficile dividere un sistema in servizi finché non lo hai completamente progettato. Che nel mondo moderno significa probabilmente scritto ...

    
risposta data 27.07.2015 - 09:00
fonte
16

Penso che se la coerenza è un requisito fondamentale nella tua applicazione, dovresti chiederti se i microservizi rappresentano l'approccio migliore. Come Martin Fowler dice :

Microservices introduce eventual consistency issues because of their laudable insistence on decentralized data management. With a monolith, you can update a bunch of things together in a single transaction. Microservices require multiple resources to update, and distributed transactions are frowned upon (for good reason). So now, developers need to be aware of consistency issues, and figure out how to detect when things are out of sync before doing anything the code will regret.

Ma forse nel tuo caso, puoi sacrificare la coerenza in posizione di disponibilità

Business processes are often more tolerant of inconsistencies than you think because businesses often prize availability more.

Tuttavia mi chiedo anche se esista una strategia per le transazioni distribuite nei microservizi, ma forse il costo è troppo alto. Volevo darti i miei due centesimi con l'eccellente articolo di Martin Fowler e il CAP teorema.

    
risposta data 27.07.2015 - 14:00
fonte
14

Come suggerito in almeno una delle risposte qui, ma anche altrove sul web, è possibile progettare un microservizio che persiste insieme le entità all'interno di una normale transazione se è necessaria la coerenza tra le due entità.

Ma allo stesso tempo, potresti avere la situazione in cui le entità non appartengono effettivamente allo stesso microservizio, ad esempio, record delle vendite e record degli ordini (quando ordini qualcosa per soddisfare la vendita). In questi casi, potrebbe essere necessario un modo per garantire la coerenza tra i due microservizi.

Sono state utilizzate transazioni tradizionalmente distribuite e, secondo la mia esperienza, funzionano bene fino a ridurle a una dimensione in cui il blocco diventa un problema. Puoi rilassare il blocco in modo che solo le risorse pertinenti (ad es. L'oggetto venduto) siano "bloccate" usando un cambio di stato, ma è qui che inizia a diventare complicato perché stai entrando nel territorio in cui devi costruire tutto il logica per fare questo, te stesso, piuttosto che avere, dire un database gestirlo per te.

Ho lavorato con aziende che hanno intrapreso la strada della costruzione del proprio framework delle transazioni per gestire questo problema complesso, ma non lo consiglio perché è costoso e richiede tempo per maturare.

Ci sono prodotti là fuori che possono essere imbullonati al tuo sistema che si prendono cura della coerenza. Un motore di processo aziendale è un buon esempio e in genere gestiscono la coerenza alla fine e utilizzando la compensazione. Altri prodotti funzionano in modo simile. In genere finisci con un livello di software vicino al / i cliente / i, che si occupa della coerenza e delle transazioni e dei servizi di chiamata (micro) per eseguire l'effettiva elaborazione aziendale . Uno di questi è un connettore JCA generico che può essere utilizzato con le soluzioni Java EE (per trasparenza: sono l'autore). Vedi link per maggiori dettagli e una discussione più approfondita sui problemi qui sollevati.

Un altro modo di gestire le transazioni e la consistenza è quello di racchiudere una chiamata a un microservizio in una chiamata a qualcosa di transazionale come una coda di messaggi. Prendete l'esempio del record di vendite / record di ordini dall'alto: potreste semplicemente lasciare che il microservizio di vendita invii un messaggio al sistema di ordini, che è impegnato nella stessa transazione che scrive la vendita al database. Il risultato è una soluzione asincrona che ridimensiona bene molto . Utilizzando tecnologie come i socket Web è possibile aggirare il problema del blocco, spesso correlato al potenziamento di soluzioni asincrone. Per ulteriori idee su modelli come questo, vedere un altro dei miei articoli: link .

Qualunque soluzione tu scelga, è importante riconoscere che solo una piccola parte del tuo sistema scrive cose che devono essere coerenti - la maggior parte degli accessi è probabile che sia di sola lettura. Per questo motivo, crea la gestione delle transazioni solo nelle parti pertinenti del sistema, in modo che possa ancora scalare bene.

    
risposta data 15.08.2015 - 10:36
fonte
1

Nei microservizi ci sono tre modi per ottenere la coerenza tra diff. servizi:

  1. Orchestrazione: un processo che gestisce la transazione e il rollback tra i servizi.

  2. Coreografia: il servizio passa i messaggi tra loro e infine raggiunge uno stato coerente.

  3. Ibrido - Mescolando i due precedenti.

Per una lettura completa vai al link: link

    
risposta data 08.02.2018 - 07:21
fonte
0

Vorrei iniziare con lo spazio dei problemi in decomposizione - identificando i confini del servizio . Se fatto correttamente, non avresti mai bisogno di avere transazioni attraverso i servizi.

Diversi servizi hanno i propri dati, comportamenti, forze motivazionali, governo, regole aziendali, ecc. Un buon inizio è elencare le capacità di alto livello dell'azienda. Ad esempio, marketing, vendite, contabilità, supporto. Un altro punto di partenza è la struttura organizzativa, ma bisogna essere consapevoli che esiste un avvertimento: per alcuni motivi (politici, ad esempio) potrebbe non essere lo schema di decomposizione aziendale ottimale. Un approccio più rigoroso è Analisi della catena del valore . Ricorda, i tuoi servizi possono includere anche le persone, non è strettamente software. I servizi devono comunicare tra loro tramite gli eventi .

Il prossimo passo è quello di ritagliare questi servizi. Di conseguenza ottieni ancora aggregati relativamente indipendenti. Rappresentano un'unità di consistenza. In altre parole, i loro interni dovrebbero essere coerenti e ACID. Gli aggregati comunicano tra loro tramite eventi.

Se pensi che il tuo dominio richieda prima la coerenza, ripensateci. Nessuno dei grandi sistemi mission-critical è stato costruito pensando a questo. Sono tutti distribuiti e alla fine coerenti. Controlla carta classica

di Pat Helland .

Ecco alcuni suggerimenti pratici su come creare un sistema distribuito .

    
risposta data 11.09.2017 - 15:56
fonte
0

Ci sono molte soluzioni che compromettono più di quanto mi sento a mio agio. Certo, se il tuo caso d'uso è complesso, come lo spostamento di denaro tra banche diverse, alternative più piacevoli potrebbero essere impossibili. Ma vediamo cosa possiamo fare nello scenario comune, in cui l'uso dei microservizi interferisce con le nostre potenziali transazioni del database.

Opzione 1: evita la necessità di transazioni se è tutto possibile

Ovvio e menzionato prima, ma ideale se possiamo gestirlo. I componenti appartenevano effettivamente allo stesso microservizio? O possiamo ridisegnare il / i sistema / i in modo tale che la transazione non sia necessaria? Forse accettare la non-transazione è il sacrificio più conveniente.

Opzione 2: utilizza una coda

Se c'è abbastanza certezza che l'altro servizio riuscirà a qualunque cosa vogliamo fare, possiamo chiamarlo tramite una qualche forma di coda. L'elemento in coda non verrà raccolto fino a un momento successivo, ma possiamo assicurarci che l'elemento sia messo in coda .

Ad esempio, diciamo che vogliamo inserire un'entità e inviare un'e-mail, come una singola transazione. Invece di chiamare il server di posta, accodiamo l'e-mail in una tabella.

Begin transaction
Insert entity
Insert e-mail
Commit transaction

Un chiaro svantaggio è che più microservizi avranno bisogno di accedere alla stessa tabella.

Opzione 3: dura il lavoro esterno, prima di completare la transazione

Questo approccio si fonda sul presupposto che è improbabile che il commit della transazione fallisca.

Begin transaction
Insert entity
Insert another entity
Make external call
Commit transaction

Se le query non riescono, la chiamata esterna non ha ancora avuto luogo. Se la chiamata esterna non riesce, la transazione non viene mai eseguita.

Questo approccio viene fornito con le limitazioni che possiamo solo effettuare una chiamata esterna one e deve essere eseguita per ultima (ovvero non possiamo usare il suo risultato nelle nostre query).

Opzione 4: crea oggetti in sospeso

Come pubblicato qui , possiamo avere più microservizi per creare componenti diversi, ciascuno in uno stato in sospeso, non transazionale.

Qualsiasi convalida viene eseguita, ma nulla viene creato in uno stato definitivo. Dopo che tutto è stato creato con successo, ogni componente è attivato. Di solito, questa operazione è così semplice e le probabilità che qualcosa vada storto sono così piccole che potremmo persino preferire l'attivazione non transazionale.

Il più grande svantaggio è probabilmente che dobbiamo tener conto dell'esistenza di elementi in sospeso. Qualsiasi query di selezione deve considerare se includere i dati in sospeso. La maggior parte dovrebbe ignorarlo. E gli aggiornamenti sono un'altra storia.

Opzione 5: lascia che il microservizio condivida la sua query

Nessuna delle altre opzioni lo fa per te? Allora prendiamo non ortodossi .

A seconda della compagnia, questa potrebbe essere inaccettabile. Sono consapevole. Questo non è ortodosso. Se non è accettabile, segui un'altra strada. Ma se questo si adatta alla tua situazione, risolve il problema in modo semplice e potente. Potrebbe essere solo il compromesso più accettabile.

C'è un modo per trasformare le query da più microservizi in una semplice transazione di database singolo.

Restituisce la query, anziché eseguirla.

Begin transaction
Execute our own query
Make external call, receiving a query
Execute received query
Commit transaction

Dal punto di vista della rete, ogni microservizio deve essere in grado di accedere a ciascun database. Tienilo a mente, anche per quanto riguarda il futuro ridimensionamento.

Se i database coinvolti nella transazione si trovano sullo stesso server, questa sarà una transazione normale. Se sono su server diversi, sarà una transazione distribuita. Il codice è lo stesso a prescindere.

Riceviamo la query, incluso il suo tipo di connessione, i suoi parametri e la sua stringa di connessione. Possiamo racchiuderlo in una classe Command eseguibile, mantenendo il flusso leggibile: la chiamata microservice genera un comando, che eseguiamo, come parte della nostra transazione.

La stringa di connessione è ciò che ci fornisce il microservizio di origine, quindi, a tutti gli effetti, la query viene ancora considerata eseguita da quel microservizio. Lo stiamo instradando fisicamente attraverso il microservice del client. Questo fa la differenza? Bene, ci consente di metterlo nella stessa transazione con un'altra query.

Se il compromesso è accettabile, questo approccio ci fornisce la linearità diretta di un'applicazione monolite, in un'architettura di microservizi.

    
risposta data 23.05.2018 - 14:44
fonte

Leggi altre domande sui tag