2015-06-28 21 views
27

Qual è il modo migliore per ottenere la coerenza del DB nei sistemi basati su microservizi?Compatibilità del DB con i microservizi

Allo GOTO in Berlin, Martin Fowler parlava di microservizi e una "regola" che menzionava era di mantenere i database "per servizio", il che significa che i servizi non possono connettersi direttamente a un DB "di proprietà" di un altro servizio.

Questo è super-bello ed elegante, ma in pratica diventa un po 'complicato. Si supponga di avere un paio di servizi:

  • un frontend
  • un servizio-gestione degli ordini
  • un servizio di fedeltà programma

Ora, un cliente effettua un acquisto sul frontend, che chiamerà il servizio di gestione degli ordini, che salverà tutto nel DB - nessun problema. A questo punto, ci sarà anche una chiamata al servizio di programma fedeltà in modo che crediti/addebiti punti dal tuo account.

Ora, quando tutto è sullo stesso server DB/DB diventa tutto facile poiché è possibile eseguire tutto in un'unica transazione: se il servizio del programma fedeltà non riesce a scrivere nel DB, è possibile eseguire il rollback dell'intero processo.

Quando eseguiamo operazioni DB su più servizi, ciò non è possibile, poiché non facciamo affidamento su una connessione/approfittiamo dell'esecuzione di una singola transazione. Quali sono i modelli migliori per mantenere le cose coerenti e vivere una vita felice?

Sono abbastanza ansioso di sentire i vostri suggerimenti! .. e grazie in anticipo!

+0

Cose come BPEL Engines o gestori di transazioni distribuite possono essere utilizzate per assicurare un'eventuale coerenza tra tutti i sistemi che vengono orchestrati all'interno di una transazione commerciale. Ho scritto un articolo sul blog: http://blog.maxant.co.uk/pebble/2015/08/04/1438716480000.html e se si sta eseguendo in ambiente Java, c'è un adattatore JCA che è possibile utilizzare qui: https://github.com/maxant/genericconnector –

risposta

11

Questo è super-bello ed elegante, ma, in pratica, diventa un po 'complicato

Che cosa significa "in pratica" è che è necessario progettare le microservices in modo tale che il la coerenza aziendale necessaria è soddisfatta quando si segue la regola:

che i servizi non possono connettersi direttamente a un DB "di proprietà" di un altro servizio.

In altre parole, non fare supposizioni sulle proprie responsabilità e modificare i limiti in base alle esigenze finché non si riesce a trovare un modo per farlo funzionare.

Ora, alla tua domanda:

Quali sono i migliori modelli per mantenere le cose coerenti e vivere una vita felice?

Per le cose che non richiedono coerenza immediato e l'aggiornamento dei punti fedeltà sembra rientrare in quella categoria, è possibile utilizzare un modello pub/sub affidabile per inviare gli eventi da un Microservice per essere elaborati da altri. Il bit affidabile è che vorresti ottenere buoni tentativi, rollback e idempotence (o transactionality) per il processo di elaborazione degli eventi.

Se si utilizza .NET, alcuni esempi di infrastruttura che supportano questo tipo di affidabilità includono NServiceBus e MassTransit. Full disclosure - Sono il fondatore di NServiceBus.

Aggiornamento: Seguendo commenti preoccupazioni circa i punti fedeltà: "se gli aggiornamenti di equilibrio sono trattati con un ritardo, un cliente può effettivamente essere in grado di ordinare più oggetti di quelli che hanno punti per".

Molte persone soffrono di questo tipo di requisiti per una forte coerenza. Il fatto è che di solito questi tipi di scenari possono essere affrontati introducendo regole aggiuntive, come se un utente finisse con punti di lealtà negativi avvisarli. Se T passa senza che i punti di fedeltà siano risolti, informa l'utente che verrà addebitato M in base a un tasso di conversione. Questa politica dovrebbe essere visibile ai clienti quando utilizzano punti per acquistare materiale.

+2

writeup molto bello! In realtà impieghiamo cose che hai menzionato (cioè idempotence, async) - anche se penso di avere un esempio meno diretto che potrebbe stimolare la conversazione. Dite che una volta che un ordine viene elaborato, dovete aggiornare le scorte disponibili sul vostro servizio di magazzino, come vi occupereste di esso? Suppongo che il sistema di gestione degli ordini debba scrivere l'ordine nel DB e quindi rimuovere il prodotto dallo stock tramite HTTP chiamando il servizio di magazzino e dire che deve essere super veloce/robusto (in modo che le persone non aggiungano al carrello/acquista prodotti che sono effettivamente esauriti). – odino

+1

L'azienda probabilmente non vuole/deve garantire il tipo di consistenza che si sta descrivendo per l'inventario in quanto può essere ripristinato e l'ordine può essere soddisfatto in seguito. –

+0

Nota la parte difficile qui con i punti fedeltà: se gli aggiornamenti del saldo vengono elaborati con ritardo, il cliente potrebbe effettivamente essere in grado di ordinare più articoli di quanti ne abbia. Sto lottando con uno scenario simile e non ho trovato una buona soluzione per una consistenza forte. Ho trovato anche questo articolo di Martin Fowler, che suggerisce che una forte coerenza è una sfida con i microservizi: http://martinfowler.com/articles/microservice-trade-offs.html –

6

solito non tratto con microservices, e questo potrebbe non essere un buon modo di fare le cose, ma qui è un'idea:

Per riformulare il problema, il sistema si compone di tre parti indipendenti ma comunicanti : il frontend, il backend di gestione degli ordini e il backend del programma fedeltà. Il frontend vuole assicurarsi che alcuni stati vengano salvati sia nel backend di gestione degli ordini sia nel backend del programma fedeltà.

Una possibile soluzione potrebbe essere quella di implementare un certo tipo di two-phase commit:

  1. In primo luogo, il frontend pone un record nel proprio database con tutti i dati. Chiama questo il record del frontend.
  2. Il frontend richiede il back-end di gestione degli ordini per un ID transazione e passa i dati necessari per completare l'azione. Il backend di gestione degli ordini memorizza questi dati in un'area di gestione temporanea, associando un nuovo ID transazione e restituendolo al front-end.
  3. L'ID della transazione di gestione degli ordini è memorizzato come parte del record di frontend.
  4. Il frontend richiede il back-end del programma fedeltà per un ID transazione e passa i dati necessari per completare l'azione. Il backend del programma di fidelizzazione memorizza questi dati in un'area di staging, associandoli con un nuovo ID di transazione e restituendoli al frontend.
  5. L'ID della transazione del programma di fidelizzazione viene memorizzato come parte del record del frontend.
  6. Il frontend indica al backend di gestione degli ordini di finalizzare la transazione associata all'ID transazione il frontend memorizzato.
  7. Il frontend indica il backend del programma fedeltà per finalizzare la transazione associata all'ID transazione il frontend memorizzato.
  8. Il frontend elimina il record del frontend.

Se questo è implementato, le modifiche non saranno necessariamente atomica, ma sarà finalmente coerente. Pensiamo ai posti che potrebbe fallire:

  • Se fallisce nel primo passaggio, nessun dato cambierà.
  • Se fallisce nel secondo, terzo, quarto o quinto, quando il sistema ritorna online può eseguire la scansione di tutti i record frontend, cercando i record senza un ID transazione associato (di entrambi i tipi). Se viene rilevato un record di questo tipo, può essere riprodotto a partire dal passaggio 2. (Se si verifica un errore nei passaggi 3 o 5, restano dei record abbandonati nei backend, ma non viene mai spostato fuori dall'area di staging, quindi è OK.)
  • Se non riesce nel sesto, settimo o ottavo passaggio, quando il sistema ritorna online può cercare tutti i record di frontend con entrambi gli ID di transazione compilati. Può quindi interrogare i backend per vedere il stato di queste transazioni: commit o uncommitted. A seconda di quale è stato eseguito, può riprendere dal passaggio appropriato.
0

Sono d'accordo con quello che @Udi Dahan ha detto. Voglio solo aggiungere alla sua risposta.

Penso che sia necessario mantenere la richiesta per il programma di fidelizzazione in modo che se fallisce, può essere fatto in un altro punto. Ci sono vari modi per esprimere/fare questo.

1) Rendere possibile il fallimento dell'API del programma fedeltà. Vale a dire che può persistere le richieste in modo che non si perdano e possano essere recuperate (ri-eseguite) in un secondo momento.

2) Eseguire le richieste del programma di loyalty in modo asincrono. Vale a dire, persistere la richiesta da qualche parte prima quindi consentire al servizio di leggerlo da questo archivio persistente. Rimuove solo dall'archivio persistente quando viene eseguito correttamente.

3) Fai quello che ha detto Udi e posizionalo su una buona coda (modello pub/sub per la precisione). Questo di solito richiede che il sottoscrittore faccia una delle due cose ... o continua la richiesta prima di rimuovere dalla coda (goto 1) --OR-- prima prende in prestito la richiesta dalla coda, quindi dopo aver elaborato la richiesta, ha la richiesta rimosso dalla coda (questa è la mia preferenza).

Tutti e tre compiono la stessa cosa. Spostano la richiesta in un luogo persistente in cui può essere lavorato fino al completamento con successo. La richiesta non viene mai persa, e ritentata se necessario fino al raggiungimento di uno stato soddisfacente.

Mi piace utilizzare l'esempio di una staffetta. Ogni servizio o parte di codice deve prendere possesso e proprietà della richiesta prima di consentire alla parte precedente di codice di lasciarla andare. Una volta consegnato, l'attuale proprietario non deve perdere la richiesta fino a quando non viene elaborata o consegnata a un altro pezzo di codice.

0

Anche per le transazioni distribuite è possibile entrare in "transazione in stato dubbio" se uno dei partecipanti si blocca nel bel mezzo della transazione. Se si progettano i servizi come operazione idempotente, la vita diventa un po 'più semplice. Si possono scrivere programmi per soddisfare le condizioni commerciali senza XA. Pat Helland ha scritto un articolo eccellente sul tema "Life Beyond XA". Fondamentalmente l'approccio è quello di fare come ipotesi minime sulle entità remote possibile. Ha inoltre illustrato un approccio chiamato Open Nested Transactions (http://www.cidrdb.org/cidr2013/Papers/CIDR13_Paper142.pdf) per modellare i processi di business. In questo caso specifico, la transazione di acquisto sarebbe il flusso e la lealtà di livello superiore e la gestione degli ordini sarà flussi di livello superiore. Il trucco consiste nel creare servizi granulari come servizi idempotenti con logica di compensazione. Quindi, se qualcosa non funziona in qualsiasi punto del flusso, i singoli servizi possono compensarlo. Quindi ad es. se l'ordine fallisce per qualche motivo, la lealtà può detrarre il punto accumulato per quell'acquisto.

Altro approccio consiste nel modellare utilizzando la coerenza finale utilizzando CALM o CRDT. Ho scritto un blog per evidenziare l'utilizzo di CALM nella vita reale - http://shripad-agashe.github.io/2015/08/Art-Of-Disorderly-Programming Potrebbe essere che ti aiuterà.

Problemi correlati