Non solo elaborazione: come abbiamo creato un database distribuito da Kafka Streams e cosa ne è derivato

Ehi Habr!

Ti ricordiamo che seguendo il libro su Kafka abbiamo pubblicato un lavoro altrettanto interessante sulla biblioteca API Kafka Stream.

Non solo elaborazione: come abbiamo creato un database distribuito da Kafka Streams e cosa ne è derivato

Per ora, la comunità sta solo imparando i limiti di questo potente strumento. Pertanto, recentemente è stato pubblicato un articolo, la cui traduzione vorremmo presentarvi. Dalla propria esperienza, l'autore racconta come trasformare Kafka Streams in un archivio dati distribuito. Buona lettura!

Libreria Apache Flussi di Kafka utilizzato in tutto il mondo nelle aziende per l'elaborazione di flussi distribuiti su Apache Kafka. Uno degli aspetti sottovalutati di questo framework è che consente di archiviare lo stato locale prodotto in base all'elaborazione dei thread.

In questo articolo ti racconterò come la nostra azienda è riuscita a sfruttare proficuamente questa opportunità nello sviluppo di un prodotto per la sicurezza delle applicazioni cloud. Utilizzando Kafka Streams, abbiamo creato microservizi di stato condiviso, ciascuno dei quali funge da fonte di tolleranza agli errori e altamente disponibile di informazioni affidabili sullo stato degli oggetti nel sistema. Per noi questo è un passo avanti sia in termini di affidabilità che di facilità di supporto.

Se sei interessato ad un approccio alternativo che ti permetta di utilizzare un unico database centrale per supportare lo stato formale dei tuoi oggetti, leggilo, sarà interessante...

Perché abbiamo pensato che fosse giunto il momento di cambiare il modo in cui lavoriamo con lo stato condiviso

Avevamo bisogno di mantenere lo stato di vari oggetti in base ai report degli agenti (ad esempio: il sito era sotto attacco)? Prima della migrazione a Kafka Streams, spesso facevamo affidamento su un unico database centrale (+ API di servizio) per la gestione dello stato. Questo approccio ha i suoi svantaggi: situazioni ad alta intensità di appuntamenti mantenere la coerenza e la sincronizzazione diventa una vera sfida. Il database potrebbe diventare un collo di bottiglia o finire dentro condizione di gara e soffrono di imprevedibilità.

Non solo elaborazione: come abbiamo creato un database distribuito da Kafka Streams e cosa ne è derivato

Figura 1: Un tipico scenario di stato diviso visto prima della transizione
Kafka e Kafka Streams: gli agenti comunicano le loro opinioni tramite API, lo stato aggiornato viene calcolato tramite un database centrale

Scopri Kafka Streams, che semplifica la creazione di microservizi a stato condiviso

Circa un anno fa, abbiamo deciso di esaminare attentamente i nostri scenari di stati condivisi per affrontare questi problemi. Abbiamo deciso immediatamente di provare Kafka Streams: sappiamo quanto sia scalabile, altamente disponibile e tollerante agli errori, quali ricche funzionalità di streaming abbia (trasformazioni, comprese quelle stateful). Proprio quello di cui avevamo bisogno, per non parlare di quanto maturo e affidabile sia diventato il sistema di messaggistica di Kafka.

Ciascuno dei microservizi con stato che abbiamo creato è stato creato su un'istanza di Kafka Streams con una topologia abbastanza semplice. Consisteva in 1) una sorgente 2) un processore con un archivio di valori-chiave persistente 3) un sink:

Non solo elaborazione: come abbiamo creato un database distribuito da Kafka Streams e cosa ne è derivato

Figura 2: la topologia predefinita delle nostre istanze di streaming per i microservizi con stato. Tieni presente che qui è presente anche un repository che contiene metadati di pianificazione.

In questo nuovo approccio, gli agenti compongono messaggi che vengono inseriti nell'argomento di origine e i consumatori, ad esempio un servizio di notifica di posta, ricevono lo stato condiviso calcolato attraverso il sink (argomento di output).

Non solo elaborazione: come abbiamo creato un database distribuito da Kafka Streams e cosa ne è derivato

Figura 3: Nuovo flusso di attività di esempio per uno scenario con microservizi condivisi: 1) l'agente genera un messaggio che arriva all'argomento di origine Kafka; 2) un microservizio con stato condiviso (utilizzando Kafka Streams) lo elabora e scrive lo stato calcolato nell'argomento Kafka finale; dopodiché 3) i consumatori accettano il nuovo stato

Ehi, questo archivio di valori-chiave integrato è in realtà molto utile!

Come accennato in precedenza, la nostra topologia a stati condivisi contiene un archivio di valori-chiave. Abbiamo trovato diverse opzioni per utilizzarlo e due di esse sono descritte di seguito.

Opzione n. 1: utilizzare un archivio di valori-chiave per i calcoli

Il nostro primo archivio di valori-chiave conteneva i dati ausiliari di cui avevamo bisogno per i calcoli. Ad esempio, in alcuni casi lo stato condiviso è stato determinato dal principio della “maggioranza dei voti”. Il repository potrebbe contenere tutti gli ultimi report dell'agente sullo stato di alcuni oggetti. Quindi, quando riceviamo un nuovo report da un agente o da un altro, potremmo salvarlo, recuperare i report da tutti gli altri agenti sullo stato dello stesso oggetto dall'archivio e ripetere il calcolo.
La Figura 4 seguente mostra come abbiamo esposto l'archivio chiave/valore al metodo di elaborazione del processore in modo che il nuovo messaggio potesse essere elaborato.

Non solo elaborazione: come abbiamo creato un database distribuito da Kafka Streams e cosa ne è derivato

Illustrazione 4: apriamo l'accesso all'archivio chiave-valore per il metodo di elaborazione del processore (dopodiché ogni script che funziona con lo stato condiviso deve implementare il metodo doProcess)

Opzione n. 2: creazione di un'API CRUD su Kafka Streams

Dopo aver stabilito il nostro flusso di attività di base, abbiamo iniziato a provare a scrivere un'API CRUD RESTful per i nostri microservizi a stato condiviso. Volevamo essere in grado di recuperare lo stato di alcuni o tutti gli oggetti, nonché impostare o rimuovere lo stato di un oggetto (utile per il supporto backend).

Per supportare tutte le API Get State, ogni volta che avevamo bisogno di ricalcolare lo stato durante l'elaborazione, lo archiviavamo a lungo in un archivio di valori-chiave integrato. In questo caso, diventa abbastanza semplice implementare tale API utilizzando una singola istanza di Kafka Streams, come mostrato nell'elenco seguente:

Non solo elaborazione: come abbiamo creato un database distribuito da Kafka Streams e cosa ne è derivato

Figura 5: utilizzo dell'archivio chiave-valore integrato per ottenere lo stato precalcolato di un oggetto

Anche l'aggiornamento dello stato di un oggetto tramite l'API è facile da implementare. Fondamentalmente, tutto ciò che devi fare è creare un produttore Kafka e usarlo per creare un record che contenga il nuovo stato. Ciò garantisce che tutti i messaggi generati tramite l'API verranno elaborati allo stesso modo di quelli ricevuti da altri produttori (ad esempio agenti).

Non solo elaborazione: come abbiamo creato un database distribuito da Kafka Streams e cosa ne è derivato

Figura 6: è possibile impostare lo stato di un oggetto utilizzando il produttore Kafka

Piccola complicazione: Kafka ha molte partizioni

Successivamente, volevamo distribuire il carico di elaborazione e migliorare la disponibilità fornendo un cluster di microservizi a stato condiviso per scenario. La configurazione è stata semplicissima: una volta configurate tutte le istanze per l'esecuzione con lo stesso ID applicazione (e gli stessi server bootstrap), quasi tutto il resto è stato fatto automaticamente. Abbiamo inoltre specificato che ciascun argomento sorgente sarà costituito da più partizioni, in modo che a ciascuna istanza possa essere assegnato un sottoinsieme di tali partizioni.

Menzionerò anche che è pratica comune creare una copia di backup dell'archivio dello stato in modo che, ad esempio, in caso di ripristino dopo un errore, trasferisca questa copia su un'altra istanza. Per ogni archivio di stati in Kafka Streams, viene creato un argomento replicato con un registro delle modifiche (che tiene traccia degli aggiornamenti locali). Pertanto, Kafka sostiene costantemente il magazzino statale. Pertanto, in caso di guasto dell'una o dell'altra istanza di Kafka Streams, l'archivio degli stati può essere rapidamente ripristinato su un'altra istanza, dove andranno le partizioni corrispondenti. I nostri test hanno dimostrato che ciò avviene in pochi secondi, anche se nell'archivio sono presenti milioni di record.

Passando da un singolo microservizio con stato condiviso a un cluster di microservizi, diventa meno banale implementare l'API Get State. Nella nuova situazione, l'archivio degli stati di ciascun microservizio contiene solo una parte del quadro generale (quegli oggetti le cui chiavi sono state mappate su una partizione specifica). Dovevamo determinare quale istanza conteneva lo stato dell'oggetto di cui avevamo bisogno e lo abbiamo fatto in base ai metadati del thread, come mostrato di seguito:

Non solo elaborazione: come abbiamo creato un database distribuito da Kafka Streams e cosa ne è derivato

Figura 7: Utilizzando i metadati del flusso, determiniamo da quale istanza interrogare lo stato dell'oggetto desiderato; un approccio simile è stato utilizzato con l'API GET ALL

Risultati principali

Gli archivi statali in Kafka Streams possono fungere da database distribuito di fatto,

  • costantemente replicato in Kafka
  • Un'API CRUD può essere facilmente creata su un tale sistema
  • Gestire più partizioni è un po’ più complicato
  • È anche possibile aggiungere uno o più archivi di stati alla topologia di streaming per archiviare dati ausiliari. Questa opzione può essere utilizzata per:
  • Archiviazione a lungo termine dei dati necessari per i calcoli durante l'elaborazione del flusso
  • Archiviazione a lungo termine di dati che potrebbero essere utili al successivo provisioning dell'istanza di streaming
  • molto di piu...

Questi e altri vantaggi rendono Kafka Streams particolarmente adatto per mantenere lo stato globale in un sistema distribuito come il nostro. Kafka Streams ha dimostrato di essere molto affidabile in produzione (non abbiamo praticamente registrato alcuna perdita di messaggi da quando lo abbiamo distribuito) e siamo fiduciosi che le sue capacità non si fermeranno qui!

Fonte: habr.com

Aggiungi un commento