In che modo i sistemi basati su eventi assicurano che il modello di lettura venga aggiornato una sola volta?

3

Attualmente analizzando Event Sourcing in un'architettura Microservices e ho trovato questo articolo . Descrive una possibile implementazione di CQRS e di gestione degli eventi.
Se la logica che riguarda l'aggiornamento del modello di lettura e la creazione degli eventi sono entrambi implementati nello stesso servizio (scalabile), in che modo questa architettura si assicura che un evento che modifica il modello di lettura venga gestito solo una volta?

Per chiarire con l'esempio utilizzato nell'articolo: Se il servizio di gestione utenti viene ridimensionato, portando a più istanze in esecuzione contemporaneamente, in che modo è possibile garantire che la logica non aggiunga lo stesso utente due volte?

Una soluzione che riesco a pensare è quella di suddividere completamente ogni servizio di microservizio in un servizio di lettura e uno di scrittura e solo ridimensionando il servizio di lettura, ma ciò non sembra ottimale.

    
posta Torsten N. 18.09.2017 - 09:19
fonte

2 risposte

4

If the User Management Service is scaling, leading to multiple instances running at the same time, how can it be ensured that the logic doesn't add the same user twice?

Qui ci sono un paio di risposte diverse.

Un buon punto di partenza sarebbe Nobody Needs Messaging affidabile di Marc de Graauw. L'idea di base è che i messaggi multipli hanno la stessa semantica aziendale, e quindi i consumatori possono rifiutare i duplicati stessi.

Questo a sua volta significa spingere all'indietro attraverso il protocollo; se abbiamo due copie di una richiesta HTTP, possibilmente separate nel tempo, che tentiamo di creare lo "stesso" utente, allora due istanze del Servizio di gestione utenti che gestiscono tali richieste dovrebbero finire per provare ad aggiungere eventi semanticamente equivalenti allo store.

Con questa proprietà, ogni consumatore può utilizzare la semantica del messaggio per eliminare i duplicati.

Le implementazioni dell'archivio eventi possono essere utili con Pubblicazione condizionale . Ad esempio, Event Store ottiene ciò supportando un expected version parametro nel comando write.

In una gara tra produttori concorrenti, i due scrittori saranno in competizione per scrivere sulla stessa versione prevista del flusso; un produttore avrà successo, e il secondo fallirà - il secondo produttore, quindi, sa che la sua rappresentazione localizzata nella cache del flusso non è aggiornata. Può quindi aggiornare la cache e riprovare a elaborare il messaggio.

In altre parole, le scritture sulle raccolte di eventi vengono raggiunte da confrontare e scambiare della coda di riferimento , piuttosto che "append".

Per quanto ne so, nel 2017 Kafka non supporta la pubblicazione condizionale. La esattamente una volta la consegna funzione in 0.11 non sembra gestire questo caso.

Più processi che scrivono sulla stessa partizione di eventi potrebbero non essere ciò che vuoi. Ragionare sul comportamento di una singola autorità è molto più facile. Anziché disporre di più istanze del servizio di gestione utenti che condividono l'autorizzazione per scrivere su un singolo flusso, è meglio servire creando più flussi, ciascuno con una singola autorità (in sostanza, ogni flusso distinto ha il proprio elezioni dei leader ).

I don't think I understood the last part 100%, though; would that look somewhat like this: i.imgur.com/b0C2xNV.png?

Sì, nel senso che ogni servizio utente ha il proprio argomento (libro del record) per gli eventi di output. Ma, inoltre, vuoi essere sicuro che tutti gli eventi relativi a un'entità specifica nel tuo modello vengano scritti sullo stesso argomento. Quindi ci sarebbe una logica responsabile per garantire che ogni comando venga gestito dall'istanza autorevole del servizio utente per quell'entità.

    
risposta data 18.09.2017 - 15:27
fonte
1

Apache Kafka include una funzionalità per la gestione automatica delle sottoscrizioni delle partizioni dei consumatori, in modo che solo un singolo consumatore in un particolare gruppo di consumatori (come l'utente che legge il gruppo di utenti del modello di aggiornamento) sia iscritto a una particolare partizione - equilibra le partizioni tra i consumatori automaticamente (utilizza ZooKeeper dietro le quinte per gestirlo). Pertanto, se si ridimensiona il servizio di gestione degli utenti, la nuova istanza si aggiungerà al gruppo di utenti di aggiornamento del modello di lettura, facendo sì che le partizioni vengano riequilibrate tra i diversi utenti. Se rimuovi un'istanza, le sue partizioni verranno aggiunte ai restanti utenti del gruppo. Questo dovrebbe garantire che solo un singolo processo e thread scriveranno su un particolare record di lettura alla volta, prevenendo qualsiasi problema di concorrenza.

Il lato di scrittura è più complicato in Zookeeper. Ci sono due problemi da superare:

  1. Zookeeper non può gestire un flusso separato per aggregato (principalmente a causa della gestione della partizione, che viene gestita per-stream, quindi non scala oltre 1000 di flussi). Ciò significa che tutti gli aggregati di un particolare tipo (Utente, per esempio) devono condividere un singolo flusso.
  2. Zookeeper non ha mezzi incorporati per bloccare un flusso per le scritture (utilizzando il blocco ottimistico o pessimistico), quindi è necessario assicurarsi che gli eventi per un particolare aggregato siano impegnati in serie personalmente.

Il primo problema significa che non puoi caricare un singolo aggregato caricando i suoi eventi (dato che puoi caricare solo eventi ad esempio per tutti utenti, che potrebbero essere milioni di eventi), quindi invece tu è necessario disporre di istantanee coerenti di tutti gli aggregati (in memoria o in un archivio, in base alla scala e alla latenza di avvio consentita). Ogni volta che pubblichi un evento, salvi immediatamente l'istantanea (e riprova finché non funziona, o il tuo processo muore). All'avvio, in caso di un precedente errore, è necessario elaborare il numero dell'evento dell'ultima istantanea scritta e rielaborare gli eventi da lì per riempire eventuali istantanee mancanti. È possibile memorizzare nella cache le istantanee in memoria, naturalmente.

Per gestire il secondo problema, un'opzione è garantire che solo un processo thread + gestisca un particolare aggregato. Il modo più semplice è solo quello di avere un singolo scrittore, come dici tu. In alternativa, puoi postare comandi (rispetto agli eventi) a un altro argomento di Kafka e utilizzare nuovamente la gestione delle partizioni per allocare le partizioni ai gestori di comandi, con la certezza che tutti i comandi per un particolare aggregato colpiranno lo stesso consumatore e quindi saranno elaborati in serie. Il gestore comandi può potenzialmente inviare una risposta a un argomento specificato nei metadati del comando in modo che il gateway API possa restituire il risultato del comando quando è disponibile.

    
risposta data 19.09.2017 - 22:10
fonte