Non solo elaborazione: come abbiamo trasformato Kafka Streams in un database distribuito e quale risultato abbiamo ottenuto

Ciao, Habr!

Ricordiamo che, dopo il libro su Kafka abbiamo pubblicato un'opera altrettanto interessante sulla biblioteca Kafka Streams API.

Non solo elaborazione: come abbiamo trasformato Kafka Streams in un database distribuito e quale risultato abbiamo ottenuto

Mentre la comunità sta ancora esplorando i limiti delle capacità di questo potente strumento. Recentemente è stato pubblicato un articolo di cui vogliamo presentarvi la traduzione. Con la propria esperienza, l'autore racconta come trasformare Kafka Streams in un sistema di archiviazione distribuita dei dati. Buona lettura!

La biblioteca Apache Kafka Streams è utilizzata in tutto il mondo nel settore enterprise per l'elaborazione distribuita in streaming sopra Apache Kafka. Uno degli aspetti sottovalutati di questo framework è che consente di memorizzare stati locali, generati sulla base dell'elaborazione in streaming.

In questo articolo spiegherò come nella nostra azienda siamo riusciti a sfruttare favorevolmente questa possibilità nello sviluppo di un prodotto per la sicurezza delle applicazioni cloud. Con Kafka Streams abbiamo creato microservizi con stato condiviso, ognuno dei quali funge da fonte resiliente e altamente disponibile di informazioni affidabili sullo stato degli oggetti nel sistema. Per noi è un passo avanti sia in termini di affidabilità che di facilità di supporto.

Se siete interessati a un approccio alternativo che consenta di utilizzare un'unica base di dati centrale per mantenere lo stato formale dei vostri oggetti, leggete, sarà interessante…

Perché abbiamo ritenuto che fosse giunto il momento di cambiare il nostro approccio al lavoro con stati condivisi

Avevamo la necessità di mantenere lo stato di diversi oggetti, basandoci sulle segnalazioni degli agenti (ad esempio: il sito è stato attaccato?). Prima di passare a Kafka Streams, spesso ci affidavamo a un'unica base di dati centrale (+ API del servizio) per gestire lo stato. Questo approccio ha i suoi svantaggi: in situazioni ad alta intensità di dati mantenere la coerenza e la sincronizzazione diventa una vera sfida. Il database può diventare un collo di bottiglia o trovarsi in uno stato di competizione e soffrire di imprevedibilità.

Non solo elaborazione: come abbiamo trasformato Kafka Streams in un database distribuito e quale risultato abbiamo ottenuto

Illustrazione 1: uno scenario tipico di separazione dello stato riscontrato prima di passare a
Kafka e Kafka Streams: gli agenti comunicano le loro rappresentazioni tramite l'API, lo stato aggiornato viene calcolato attraverso il database centrale

Familiarizzate con Kafka Streams: ora è facile creare microservizi con stato condiviso

Circa un anno fa abbiamo deciso di rivedere attentamente i nostri scenari di lavoro con lo stato condiviso, per affrontare tali problematiche. Abbiamo subito deciso di provare Kafka Streams – è nota la sua scalabilità, alta disponibilità e resilienza, oltre all'ampia gamma di funzionalità di streaming (trasformazioni, inclusa la conservazione dello stato). Proprio ciò che ci serviva, senza contare quanto fosse matura e affidabile la sistema di messaggistica che si è sviluppata in Kafka.

Ciascuno dei microservizi con stato condiviso che abbiamo creato si basava su un'istanza di Kafka Streams con una topologia piuttosto semplice. Era composta da 1) una sorgente 2) un processore con uno storage persistente di chiavi e valori 3) uno scarico:

Non solo elaborazione: come abbiamo trasformato Kafka Streams in un database distribuito e quale risultato abbiamo ottenuto

Illustrazione 2: la topologia predefinita delle nostre istanze di flusso per i microservizi con stato condiviso. Nota: qui c'è anche uno storage che contiene i metadati sulla pianificazione.

Con questo nuovo approccio, gli agenti compongono i messaggi inviati al topic di origine, mentre i consumatori – ad esempio, il servizio di notifiche via email – ricevono lo stato condiviso calcolato tramite lo scarico (topic in uscita).

Non solo elaborazione: come abbiamo trasformato Kafka Streams in un database distribuito e quale risultato abbiamo ottenuto

Illustrazione 3: un nuovo esempio di flusso di attività per uno scenario con microservizi condivisi: 1) l'agente genera un messaggio che entra nel topic di origine di Kafka; 2) il microservizio con stato condiviso (che utilizza Kafka Streams) lo elabora e scrive lo stato calcolato nel topic finale di Kafka; dopo di che 3) i consumatori ricevono il nuovo stato.

Ehi, questo storage di chiavi e valori incorporato è davvero molto utile!

Come accennato in precedenza, la nostra topologia con stato condiviso include uno storage di chiavi e valori. Abbiamo trovato diverse modalità di utilizzo, e due di esse sono descritte di seguito.

Opzione #1: utilizzo dello storage di chiavi e valori durante i calcoli.

Il nostro primo archivio di chiavi e valori conteneva dati ausiliari necessari per i nostri calcoli. Ad esempio, in alcuni casi, lo stato condiviso veniva determinato secondo il principio della 'maggioranza dei voti'. Nell'archivio era possibile tenere tutti gli ultimi rapporti degli agenti sullo stato di un certo oggetto. Poi, ricevuto un nuovo rapporto da un agente, potevamo salvarlo, estrarre dall'archivio i rapporti di tutti gli altri agenti sullo stato dello stesso oggetto e ripetere il calcolo.
Di seguito, nell'illustrazione 4, viene mostrato come abbiamo aperto l'accesso all'archivio di chiavi e valori al metodo di elaborazione del processore, in modo da poter elaborare un nuovo messaggio.

Non solo elaborazione: come abbiamo trasformato Kafka Streams in un database distribuito e quale risultato abbiamo ottenuto

Illustrazione 4: apriamo l'accesso all'archivio di chiavi e valori per il metodo di elaborazione del processore (dopo di che, in ogni scenario che lavora con stati condivisi, è necessario implementare il metodo doProcess)

Opzione #2: creazione di un'API CRUD sopra Kafka Streams

Dopo aver impostato il nostro flusso di lavoro di base, abbiamo provato a scrivere un'API RESTful CRUD per i nostri microservizi con stati condivisi. Volevamo poter estrarre lo stato di alcuni o tutti gli oggetti, oltre a impostare o rimuovere lo stato di un oggetto (ciò è utile per il supporto del backend).

Per supportare tutte le API Get State, ogni volta che era necessario ricalcolare lo stato durante l'elaborazione, lo archiviavamo a lungo nel nostro archivio integrato di chiavi e valori. In questo caso, diventa piuttosto semplice implementare tale API con un'unica istanza di Kafka Streams, come mostrato nel listato seguente:

Non solo elaborazione: come abbiamo trasformato Kafka Streams in un database distribuito e quale risultato abbiamo ottenuto

Illustrazione 5: utilizzo dell'archivio integrato di chiavi e valori per ottenere lo stato pre-calcolato di un oggetto

L'aggiornamento dello stato di un oggetto tramite API è anche facile da implementare. In sostanza, per fare ciò è necessario solo creare un produttore Kafka e utilizzare questo per effettuare una scrittura contenente il nuovo stato. Questo garantisce che tutti i messaggi generati tramite l'API vengano elaborati esattamente come quelli provenienti da altri produttori (ad es. agenti).

Non solo elaborazione: come abbiamo trasformato Kafka Streams in un database distribuito e quale risultato abbiamo ottenuto

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

Una piccola complicazione: Kafka ha molte partizioni

Successivamente, volevamo distribuire il carico relativo all'elaborazione e migliorare l'accessibilità, fornendo un cluster di microservizi con stato condiviso per ciascun scenario. La configurazione si è rivelata molto semplice: dopo aver configurato tutte le istanze affinché funzionassero con lo stesso ID dell'applicazione (e con gli stessi server di avvio), praticamente tutto il resto è stato fatto automaticamente. Abbiamo anche specificato che ogni topic sorgente sarebbe consistito in più partizioni, in modo che a ciascuna istanza potesse essere assegnato un sottoinsieme di queste partizioni.

Voglio anche menzionare che qui è normale eseguire un backup dello stato, in modo che, ad esempio, nel caso di un ripristino dopo un guasto, si possa trasferire questo backup su un'altra istanza. Per ogni stato in Kafka Streams viene creato un topic replicabile con un log delle modifiche (che tiene traccia degli aggiornamenti locali). In questo modo, Kafka offre continuamente una protezione allo stato. Pertanto, in caso di guasto di una particolare istanza, lo stato di Kafka Streams può essere rapidamente ripristinato su un'altra istanza, a cui andranno le corrispondenti partizioni. I nostri test hanno mostrato che questo avviene in pochi secondi, anche se nello stato ci sono milioni di registrazioni.

Passando da un microservizio con stato condiviso a un cluster di microservizi, diventa meno banale implementare l'API Get State. Nella nuova situazione, lo stato di ciascun microservizio contiene solo una parte del quadro generale (quegli oggetti i cui chiavi erano assegnati a una specifica partizione). Dobbiamo determinare su quale istanza fosse contenuto lo stato dell'oggetto di nostro interesse, e lo facciamo sulla base dei metadati dei flussi, come mostrato di seguito:

Non solo elaborazione: come abbiamo trasformato Kafka Streams in un database distribuito e quale risultato abbiamo ottenuto

Illustrazione 7: utilizzando i metadati dei flussi, definiamo da quale istanza richiedere lo stato dell'oggetto desiderato; questo approccio è stato applicato con l'API GET ALL

Conclusioni principali

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

  • costantemente replicato in Kafka
  • Sopra un tale sistema è facile costruire un API CRUD
  • La gestione di più partizioni risulta un po' più complessa
  • È inoltre possibile aggiungere uno o più archivi di stato nella topologia del flusso per memorizzare dati ausiliari. Questa opzione può essere utilizzata per:
  • La memorizzazione a lungo termine dei dati necessari per i calcoli durante l'elaborazione di flussi
  • La memorizzazione a lungo termine dei dati che potrebbero essere utili durante la successiva inizializzazione dell'istanza di flusso
  • molto altro...

Grazie a questi e ad altri vantaggi, Kafka Streams è particolarmente adatto per supportare uno stato globale in un sistema distribuito come il nostro. Kafka Streams ha dimostrato di essere molto affidabile in produzione (da quando è stata distribuita non abbiamo praticamente perso messaggi) e siamo certi che le sue capacità non si limitano a questo!

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster