Comprensione dei broker di messaggi. Imparare i meccanismi della messaggistica con ActiveMQ e Kafka. Capitolo 3. Kafka

Continuazione della traduzione di un piccolo libro:
Comprensione dei broker di messaggi
autore: Jakub Korab, editore: O'Reilly Media, Inc., data di pubblicazione: giugno 2017, ISBN: 9781492049296.

Parte precedente tradotta: Comprensione dei broker di messaggi. Imparare i meccanismi della messaggistica con ActiveMQ e Kafka. capitolo 1 introduzione

CAPITOLO 3

Kafka

Kafka è stato sviluppato da LinkedIn per aggirare alcune delle limitazioni dei broker di messaggi tradizionali ed evitare di dover configurare più broker di messaggi per diverse interazioni punto-punto, come descritto in questo libro in "Scaling up and out" a pagina 28 Casi d'uso LinkedIn si è basato in gran parte sull'acquisizione unidirezionale di grandi quantità di dati, come i clic sulle pagine e i registri di accesso, pur consentendo a tali dati di essere utilizzati da più sistemi senza influire sulla produttività dei produttori o di altri consumatori. In effetti, il motivo per cui esiste Kafka è ottenere il tipo di architettura di messaggistica descritta da Universal Data Pipeline.

Dato questo obiettivo finale, sono sorte naturalmente altre esigenze. Kafka dovrebbe:

  • Sii estremamente veloce
  • Fornire più larghezza di banda quando si lavora con i messaggi
  • Supporta i modelli Publisher-Subscriber e Point-to-Point
  • Non rallentare con l'aggiunta di consumatori. Ad esempio, le prestazioni della coda e dell'argomento in ActiveMQ diminuiscono con l'aumentare del numero di consumatori sulla destinazione.
  • Essere scalabile orizzontalmente; se un broker che persiste i messaggi può farlo solo alla massima velocità del disco, allora ha senso andare oltre una singola istanza del broker per aumentare le prestazioni
  • Limita l'accesso all'archiviazione e al recupero dei messaggi

Per ottenere tutto ciò, Kafka ha adottato un'architettura che ha ridefinito i ruoli e le responsabilità dei clienti e dei broker di messaggistica. Il modello JMS è molto orientato al broker, in cui il broker è responsabile della distribuzione dei messaggi e i client devono solo preoccuparsi di inviare e ricevere messaggi. Kafka, d'altra parte, è incentrato sul cliente, con il cliente che assume molte delle caratteristiche di un broker tradizionale, come la distribuzione equa dei messaggi rilevanti ai consumatori, in cambio di un broker estremamente veloce e scalabile. Per le persone che hanno lavorato con i sistemi di messaggistica tradizionali, lavorare con Kafka richiede un fondamentale cambiamento di mentalità.
Questa direzione ingegneristica ha portato alla creazione di un'infrastruttura di messaggistica in grado di aumentare il throughput di molti ordini di grandezza rispetto a un broker convenzionale. Come vedremo, questo approccio comporta dei compromessi, il che significa che Kafka non è adatto a determinati tipi di carichi di lavoro e software installato.

Modello di destinazione unificato

Per soddisfare i requisiti sopra descritti, Kafka ha combinato la messaggistica di pubblicazione-sottoscrizione e point-to-point sotto un unico tipo di destinazione: argomento. Questo è fonte di confusione per le persone che hanno lavorato con i sistemi di messaggistica, dove la parola "argomento" si riferisce a un meccanismo di trasmissione da cui (dall'argomento) la lettura non è durevole. Gli argomenti di Kafka dovrebbero essere considerati un tipo di destinazione ibrida, come definito nell'introduzione a questo libro.

Per il resto di questo capitolo, a meno che non dichiariamo esplicitamente diversamente, il termine "argomento" si riferirà a un argomento di Kafka.

Per comprendere appieno come si comportano gli argomenti e quali garanzie forniscono, dobbiamo prima esaminare come vengono implementati in Kafka.
Ogni argomento in Kafka ha il suo registro.
I produttori che inviano messaggi a Kafka scrivono in questo registro e i consumatori leggono dal registro utilizzando puntatori che si spostano costantemente in avanti. Periodicamente, Kafka elimina le parti più vecchie del registro, indipendentemente dal fatto che i messaggi in quelle parti siano stati letti o meno. Una parte centrale del design di Kafka è che al broker non importa se i messaggi vengono letti o meno: questa è la responsabilità del cliente.

I termini "log" e "pointer" non compaiono in Documentazione di Kafka. Questi termini ben noti sono usati qui per aiutare la comprensione.

Questo modello è completamente diverso da ActiveMQ, in cui i messaggi di tutte le code vengono archiviati nello stesso registro e il broker contrassegna i messaggi come eliminati dopo che sono stati letti.
Andiamo ora a scavare un po' più a fondo ed esaminiamo l'argomento log in modo più dettagliato.
Il registro di Kafka è costituito da diverse partizioni (Figura 3-1). Kafka garantisce un ordine rigoroso in ogni partizione. Ciò significa che i messaggi scritti nella partizione in un certo ordine verranno letti nello stesso ordine. Ogni partizione è implementata come un file di log in sequenza che contiene sottoinsieme (sottoinsieme) di tutti i messaggi inviati all'argomento dai suoi produttori. L'argomento creato contiene, per impostazione predefinita, una partizione. L'idea delle partizioni è l'idea centrale di Kafka per il ridimensionamento orizzontale.

Comprensione dei broker di messaggi. Imparare i meccanismi della messaggistica con ActiveMQ e Kafka. Capitolo 3. Kafka
Figura 3-1. Partizioni di Kafka

Quando un produttore invia un messaggio a un argomento Kafka, decide a quale partizione inviare il messaggio. Vedremo questo in modo più dettagliato in seguito.

Lettura dei messaggi

Il client che vuole leggere i messaggi gestisce un puntatore denominato chiamato gruppo di consumatori, che indica compensare messaggi nella partizione. Un offset è una posizione incrementale che parte da 0 all'inizio di una partizione. Questo gruppo di consumatori, referenziato nell'API tramite l'id_gruppo definito dall'utente, corrisponde a un consumatore o sistema logico.

La maggior parte dei sistemi di messaggistica legge i dati dalla destinazione utilizzando più istanze e thread per elaborare i messaggi in parallelo. Pertanto, di solito ci saranno molte istanze di consumatori che condividono lo stesso gruppo di consumatori.

Il problema della lettura può essere rappresentato come segue:

  • L'argomento ha più partizioni
  • Più gruppi di consumatori possono utilizzare un argomento contemporaneamente
  • Un gruppo di consumatori può avere più istanze separate

Questo è un problema molti-a-molti non banale. Per capire come Kafka gestisce le relazioni tra gruppi di consumatori, istanze di consumatori e partizioni, diamo un'occhiata a una serie di scenari di lettura progressivamente più complessi.

Consumatori e gruppi di consumatori

Prendiamo come punto di partenza un argomento con una partizione (Figura 3-2).

Comprensione dei broker di messaggi. Imparare i meccanismi della messaggistica con ActiveMQ e Kafka. Capitolo 3. Kafka
Figura 3-2. Il consumatore legge dalla partizione

Quando un'istanza consumer si connette con il proprio group_id a questo argomento, le viene assegnata una partizione di lettura e un offset in quella partizione. La posizione di questo offset è configurata nel client come un puntatore alla posizione più recente (messaggio più recente) o alla prima posizione (messaggio più vecchio). Il consumatore richiede (sondaggi) i messaggi dall'argomento, il che fa sì che vengano letti in sequenza dal registro.
La posizione di offset viene regolarmente restituita a Kafka e archiviata come messaggi in un argomento interno _consumer_offset. I messaggi letti non vengono ancora eliminati, a differenza di un normale broker, e il client può riavvolgere l'offset per rielaborare i messaggi già visualizzati.

Quando un secondo consumer logico si connette utilizzando un group_id diverso, gestisce un secondo puntatore indipendente dal primo (Figura 3-3). Pertanto, un argomento Kafka si comporta come una coda in cui è presente un consumatore e come un normale argomento pubblicazione-sottoscrizione (pub-sub) a cui si iscrivono più consumatori, con l'ulteriore vantaggio che tutti i messaggi vengono archiviati e possono essere elaborati più volte.

Comprensione dei broker di messaggi. Imparare i meccanismi della messaggistica con ActiveMQ e Kafka. Capitolo 3. Kafka
Figura 3-3. Due consumatori in diversi gruppi di consumatori leggono dalla stessa partizione

Consumatori in un gruppo di consumatori

Quando un'istanza consumer legge i dati da una partizione, ha il pieno controllo del puntatore ed elabora i messaggi come descritto nella sezione precedente.
Se più istanze di consumatori sono state connesse con lo stesso group_id a un argomento con una partizione, allora l'istanza che si è connessa per ultima avrà il controllo sul puntatore e da quel momento in poi riceverà tutti i messaggi (Figura 3-4).

Comprensione dei broker di messaggi. Imparare i meccanismi della messaggistica con ActiveMQ e Kafka. Capitolo 3. Kafka
Figura 3-4. Due consumatori nello stesso gruppo di consumatori leggono dalla stessa partizione

Questa modalità di elaborazione, in cui il numero di istanze del consumatore supera il numero di partizioni, può essere considerata come una sorta di consumatore esclusivo. Questo può essere utile se hai bisogno di un clustering "attivo-passivo" (o "caldo-caldo") delle tue istanze consumer, sebbene eseguire più consumatori in parallelo ("attivo-attivo" o "caldo-caldo") sia molto più tipico di consumatori In standby.

Questo comportamento di distribuzione dei messaggi sopra descritto può essere sorprendente rispetto a come si comporta una normale coda JMS. In questo modello, i messaggi inviati alla coda verranno distribuiti uniformemente tra i due consumatori.

Molto spesso, quando creiamo più istanze di consumatori, lo facciamo per elaborare i messaggi in parallelo o per aumentare la velocità di lettura o per aumentare la stabilità del processo di lettura. Poiché solo un'istanza consumer alla volta può leggere i dati da una partizione, come si ottiene questo risultato in Kafka?

Un modo per farlo è utilizzare una singola istanza consumer per leggere tutti i messaggi e passarli al pool di thread. Mentre questo approccio aumenta il throughput di elaborazione, aumenta la complessità della logica del consumatore e non fa nulla per aumentare la robustezza del sistema di lettura. Se una copia del consumatore si interrompe a causa di un'interruzione di corrente o di un evento simile, la sottrazione si interrompe.

Il modo canonico per risolvere questo problema in Kafka è usare bОpiù partizioni.

Partizionamento

Le partizioni sono il meccanismo principale per la parallelizzazione della lettura e il ridimensionamento di un argomento oltre la larghezza di banda di una singola istanza del broker. Per capirlo meglio, consideriamo una situazione in cui è presente un argomento con due partizioni e un consumatore si iscrive a questo argomento (Figura 3-5).

Comprensione dei broker di messaggi. Imparare i meccanismi della messaggistica con ActiveMQ e Kafka. Capitolo 3. Kafka
Figura 3-5. Un consumatore legge da più partizioni

In questo scenario, al consumatore viene dato il controllo sui puntatori corrispondenti al suo group_id in entrambe le partizioni e inizia a leggere i messaggi da entrambe le partizioni.
Quando un consumatore aggiuntivo per lo stesso group_id viene aggiunto a questo argomento, Kafka rialloca una delle partizioni dal primo al secondo consumatore. Successivamente, ogni istanza del consumatore leggerà da una partizione dell'argomento (Figura 3-6).

Per garantire che i messaggi vengano elaborati in parallelo in 20 thread, sono necessarie almeno 20 partizioni. Se ci sono meno partizioni, rimarranno consumatori che non hanno nulla su cui lavorare, come descritto in precedenza nella discussione sui consumatori esclusivi.

Comprensione dei broker di messaggi. Imparare i meccanismi della messaggistica con ActiveMQ e Kafka. Capitolo 3. Kafka
Figura 3-6. Due consumatori nello stesso gruppo di consumatori leggono da partizioni diverse

Questo schema riduce notevolmente la complessità del broker Kafka rispetto alla distribuzione dei messaggi richiesta per mantenere la coda JMS. Qui non devi preoccuparti dei seguenti punti:

  • Quale consumatore deve ricevere il messaggio successivo, in base all'allocazione round-robin, alla capacità corrente dei buffer di prelettura o ai messaggi precedenti (come per i gruppi di messaggi JMS).
  • Quali messaggi vengono inviati a quali consumatori e se devono essere riconsegnati in caso di errore.

Tutto ciò che il broker Kafka deve fare è passare i messaggi in sequenza al consumatore quando quest'ultimo li richiede.

Tuttavia, i requisiti per la parallelizzazione della correzione di bozze e il rinvio dei messaggi non riusciti non vengono meno: la responsabilità per essi passa semplicemente dal broker al cliente. Ciò significa che devono essere presi in considerazione nel codice.

Invio di messaggi

È responsabilità del produttore di quel messaggio decidere a quale partizione inviare un messaggio. Per comprendere il meccanismo con cui ciò avviene, dobbiamo prima considerare cosa esattamente stiamo effettivamente inviando.

Mentre in JMS usiamo una struttura del messaggio con metadati (intestazioni e proprietà) e un corpo contenente il payload (payload), in Kafka il messaggio è coppia "chiave-valore". Il payload del messaggio viene inviato come valore. La chiave, invece, viene utilizzata principalmente per il partizionamento e deve contenere chiave specifica della logica aziendaleper mettere i messaggi correlati nella stessa partizione.

Nel capitolo 2, abbiamo discusso lo scenario delle scommesse online in cui gli eventi correlati devono essere elaborati in ordine da un singolo consumatore:

  1. L'account utente è configurato.
  2. Il denaro viene accreditato sul conto.
  3. Viene effettuata una scommessa che preleva denaro dal conto.

Se ogni evento è un messaggio inviato a un argomento, la chiave naturale sarà l'ID account.
Quando un messaggio viene inviato utilizzando l'API Kafka Producer, viene passato a una funzione di partizione che, dato il messaggio e lo stato corrente del cluster Kafka, restituisce l'ID della partizione a cui deve essere inviato il messaggio. Questa funzione è implementata in Java tramite l'interfaccia Partitioner.

Questa interfaccia ha questo aspetto:

interface Partitioner {
    int partition(String topic,
        Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster);
}

L'implementazione di Partitioner utilizza l'algoritmo di hashing generico predefinito sulla chiave per determinare la partizione o il round robin se non viene specificata alcuna chiave. Questo valore predefinito funziona bene nella maggior parte dei casi. Tuttavia, in futuro vorrai scrivere il tuo.

Scrivere la propria strategia di partizionamento

Diamo un'occhiata a un esempio in cui desideri inviare metadati insieme al payload del messaggio. Il payload nel nostro esempio è un'istruzione per effettuare un deposito sul conto di gioco. Un'istruzione è qualcosa di cui vorremmo avere la garanzia di non essere modificata durante la trasmissione e vogliamo essere sicuri che solo un sistema a monte affidabile possa avviare quell'istruzione. In questo caso, i sistemi di invio e ricezione concordano sull'uso di una firma per autenticare il messaggio.
In JMS normale, definiamo semplicemente una proprietà "firma del messaggio" e la aggiungiamo al messaggio. Tuttavia, Kafka non ci fornisce un meccanismo per passare i metadati, solo una chiave e un valore.

Poiché il valore è un payload del bonifico bancario di cui vogliamo preservare l'integrità, non abbiamo altra scelta che definire la struttura dei dati da utilizzare nella chiave. Supponendo di aver bisogno di un ID account per il partizionamento, poiché tutti i messaggi relativi a un account devono essere elaborati in ordine, otterremo la seguente struttura JSON:

{
  "signature": "541661622185851c248b41bf0cea7ad0",
  "accountId": "10007865234"
}

Poiché il valore della firma varia a seconda del payload, la strategia di hashing predefinita dell'interfaccia di Partitioner non raggrupperà in modo affidabile i messaggi correlati. Pertanto, dovremo scrivere la nostra strategia che analizzerà questa chiave e partizionerà il valore accountId.

Kafka include checksum per rilevare la corruzione dei messaggi nello store e dispone di un set completo di funzionalità di sicurezza. Anche così, a volte compaiono requisiti specifici del settore, come quello sopra.

La strategia di partizionamento dell'utente deve garantire che tutti i messaggi correlati finiscano nella stessa partizione. Anche se questo sembra semplice, il requisito può essere complicato dall'importanza di ordinare i post correlati e da quanto è fisso il numero di partizioni in un argomento.

Il numero di partizioni in un argomento può cambiare nel tempo, poiché possono essere aggiunte se il traffico supera le aspettative iniziali. Pertanto, le chiavi dei messaggi possono essere associate alla partizione a cui sono state originariamente inviate, implicando una parte di stato da condividere tra le istanze del produttore.

Un altro fattore da considerare è la distribuzione uniforme dei messaggi tra le partizioni. In genere, le chiavi non sono distribuite uniformemente tra i messaggi e le funzioni hash non garantiscono un'equa distribuzione dei messaggi per un piccolo insieme di chiavi.
È importante notare che, comunque tu scelga di dividere i messaggi, potrebbe essere necessario riutilizzare il separatore stesso.

Considera il requisito di replicare i dati tra i cluster Kafka in diverse località geografiche. A tale scopo, Kafka viene fornito con uno strumento a riga di comando chiamato MirrorMaker, che viene utilizzato per leggere i messaggi da un cluster e trasferirli a un altro.

MirrorMaker deve comprendere le chiavi dell'argomento replicato per mantenere l'ordine relativo tra i messaggi durante la replica tra cluster, poiché il numero di partizioni per quell'argomento potrebbe non essere lo stesso in due cluster.

Le strategie di partizionamento personalizzate sono relativamente rare, poiché l'hashing predefinito o il round robin funzionano bene nella maggior parte degli scenari. Tuttavia, se hai bisogno di solide garanzie di ordinamento o hai bisogno di estrarre metadati dai payload, allora il partizionamento è qualcosa che dovresti dare un'occhiata più da vicino.

I vantaggi in termini di scalabilità e prestazioni di Kafka derivano dallo spostamento di alcune delle responsabilità del broker tradizionale sul cliente. In questo caso, viene presa la decisione di distribuire i messaggi potenzialmente correlati tra diversi consumatori che lavorano in parallelo.

Anche i broker JMS devono soddisfare tali requisiti. È interessante notare che il meccanismo per l'invio di messaggi correlati allo stesso consumatore, implementato tramite JMS Message Groups (una variazione della strategia di bilanciamento del carico appiccicoso (SLB)), richiede anche al mittente di contrassegnare i messaggi come correlati. Nel caso di JMS, il broker è responsabile dell'invio di questo gruppo di messaggi correlati a un consumatore tra tanti e del trasferimento della proprietà del gruppo se il consumatore cade.

Accordi con i produttori

Il partizionamento non è l'unica cosa da considerare quando si inviano messaggi. Diamo un'occhiata ai metodi send() della classe Producer nell'API Java:

Future < RecordMetadata > send(ProducerRecord < K, V > record);
Future < RecordMetadata > send(ProducerRecord < K, V > record, Callback callback);

Va subito notato che entrambi i metodi restituiscono Future, il che indica che l'operazione di invio non viene eseguita immediatamente. Il risultato è che un messaggio (ProducerRecord) viene scritto nel buffer di invio per ogni partizione attiva e inviato al broker come thread in background nella libreria client di Kafka. Mentre questo rende le cose incredibilmente veloci, significa che un'applicazione inesperta può perdere messaggi se il suo processo viene interrotto.

Come sempre, c'è un modo per rendere l'operazione di invio più affidabile a scapito delle prestazioni. La dimensione di questo buffer può essere impostata su 0 e il thread dell'applicazione di invio sarà costretto ad attendere fino al completamento del trasferimento del messaggio al broker, come segue:

RecordMetadata metadata = producer.send(record).get();

Ulteriori informazioni sulla lettura dei messaggi

La lettura dei messaggi presenta ulteriori complessità su cui è necessario speculare. A differenza dell'API JMS, che può eseguire un listener di messaggi in risposta a un messaggio, il Consumatori Kafka solo sondaggi. Diamo un'occhiata più da vicino al metodo sondaggio()utilizzato a questo scopo:

ConsumerRecords < K, V > poll(long timeout);

Il valore restituito dal metodo è una struttura contenitore contenente più oggetti registro dei consumatori da potenzialmente diverse partizioni. registro dei consumatori è esso stesso un oggetto titolare per una coppia chiave-valore con metadati associati, come la partizione da cui deriva.

Come discusso nel Capitolo 2, dobbiamo tenere a mente cosa succede ai messaggi dopo che sono stati elaborati con successo o meno, per esempio, se il client non è in grado di elaborare il messaggio o se si interrompe. In JMS, questo veniva gestito tramite una modalità di riconoscimento. Il broker eliminerà il messaggio elaborato correttamente o riconsegnerà il messaggio non elaborato o falso (presupponendo che siano state utilizzate transazioni).
Kafka funziona in modo molto diverso. I messaggi non vengono cancellati nel broker dopo la correzione di bozze e ciò che accade in caso di errore è responsabilità del codice di correzione di bozze stesso.

Come abbiamo detto, il gruppo di consumatori è associato all'offset nel registro. La posizione di registro associata a questo offset corrisponde al successivo messaggio da emettere in risposta a sondaggio(). Il momento in cui questo offset aumenta è decisivo per la lettura.

Tornando al modello di lettura discusso in precedenza, l'elaborazione dei messaggi si compone di tre fasi:

  1. Recuperare un messaggio per la lettura.
  2. Elabora il messaggio.
  3. Conferma messaggio.

Il consumatore Kafka viene fornito con un'opzione di configurazione abilita.auto.commit. Questa è un'impostazione predefinita usata di frequente, come è comune con le impostazioni che contengono la parola "auto".

Prima di Kafka 0.10, un client che utilizzava questa opzione inviava l'offset dell'ultimo messaggio letto alla chiamata successiva sondaggio() dopo l'elaborazione. Ciò significava che tutti i messaggi che erano già stati recuperati potevano essere rielaborati se il client li aveva già elaborati ma era stato distrutto inaspettatamente prima della chiamata sondaggio(). Poiché il broker non mantiene alcuno stato su quante volte un messaggio è stato letto, il consumatore successivo che recupera quel messaggio non saprà che è successo qualcosa di brutto. Questo comportamento era pseudo-transazionale. L'offset è stato eseguito solo se il messaggio è stato elaborato correttamente, ma se il client è stato interrotto, il broker ha inviato nuovamente lo stesso messaggio a un altro client. Questo comportamento era coerente con la garanzia di consegna dei messaggi "almeno una volta«.

In Kafka 0.10, il codice client è stato modificato in modo che il commit venga attivato periodicamente dalla libreria client, come configurato auto.commit.intervallo.ms. Questo comportamento si trova tra le modalità JMS AUTO_ACKNOWLEDGE e DUPS_OK_ACKNOWLEDGE. Quando si utilizza l'autocommit, è possibile eseguire il commit dei messaggi indipendentemente dal fatto che siano stati effettivamente elaborati: ciò potrebbe accadere nel caso di un consumatore lento. Se un consumatore si interrompeva, i messaggi sarebbero recuperati dal consumatore successivo, a partire dalla posizione di commit, il che potrebbe risultare in un messaggio mancato. In questo caso, Kafka non ha perso i messaggi, semplicemente il codice di lettura non li ha elaborati.

Questa modalità ha la stessa promessa della versione 0.9: i messaggi possono essere elaborati, ma se fallisce, l'offset potrebbe non essere confermato, causando potenzialmente il raddoppio della consegna. Più messaggi recuperi durante l'esecuzione sondaggio(), più questo problema.

Come discusso in “Lettura di messaggi da una coda” a pagina 21, non esiste una consegna unica di un messaggio in un sistema di messaggistica quando vengono prese in considerazione le modalità di errore.

In Kafka, ci sono due modi per eseguire il commit (commit) di un offset (offset): automaticamente e manualmente. In entrambi i casi, i messaggi possono essere elaborati più volte se il messaggio è stato elaborato ma non è riuscito prima del commit. Puoi anche scegliere di non elaborare affatto il messaggio se il commit è avvenuto in background e il tuo codice è stato completato prima che potesse essere elaborato (magari in Kafka 0.9 e versioni precedenti).

È possibile controllare il processo di commit dell'offset manuale nell'API consumer di Kafka impostando il parametro abilita.auto.commit a false e chiamando esplicitamente uno dei seguenti metodi:

void commitSync();
void commitAsync();

Se si desidera elaborare il messaggio "almeno una volta", è necessario eseguire manualmente il commit dell'offset con commitSync()eseguendo questo comando subito dopo l'elaborazione dei messaggi.

Questi metodi non consentono di riconoscere i messaggi prima che vengano elaborati, ma non fanno nulla per eliminare potenziali ritardi di elaborazione pur dando l'impressione di essere transazionali. Non ci sono transazioni in Kafka. Il cliente non ha la possibilità di eseguire le seguenti operazioni:

  • Ripristina automaticamente un messaggio falso. I consumatori stessi devono gestire le eccezioni derivanti da payload problematici e interruzioni del back-end, poiché non possono fare affidamento sul broker per la riconsegna dei messaggi.
  • Invia messaggi a più argomenti in un'unica operazione atomica. Come vedremo tra poco, il controllo su diversi argomenti e partizioni può risiedere su diverse macchine nel cluster Kafka che non coordinano le transazioni quando vengono inviate. Al momento della stesura di questo articolo, è stato fatto del lavoro per renderlo possibile con il KIP-98.
  • Associare la lettura di un messaggio da un argomento con l'invio di un altro messaggio a un altro argomento. Ancora una volta, l'architettura di Kafka dipende da molte macchine indipendenti che funzionano come un unico bus e non viene fatto alcun tentativo per nasconderlo. Ad esempio, non ci sono componenti API che ti consentirebbero di collegarti consumatore и produttore in una transazione. In JMS, questo è fornito dall'oggetto Sessioneda cui vengono creati Produttori di messaggi и MessaggioConsumatori.

Se non possiamo fare affidamento sulle transazioni, come possiamo fornire una semantica più vicina a quella fornita dai tradizionali sistemi di messaggistica?

Se esiste la possibilità che l'offset del consumatore possa aumentare prima che il messaggio sia stato elaborato, ad esempio durante un arresto anomalo del consumatore, il consumatore non ha modo di sapere se il suo gruppo di consumatori ha perso il messaggio quando gli viene assegnata una partizione. Quindi una strategia consiste nel riavvolgere l'offset alla posizione precedente. L'API consumer di Kafka fornisce i seguenti metodi per questo:

void seek(TopicPartition partition, long offset);
void seekToBeginning(Collection < TopicPartition > partitions);

metodo cercare() può essere utilizzato con metodo
offsetsForTimes(Map timestampToSearch) per riavvolgere in uno stato in un punto specifico nel passato.

Implicitamente, l'utilizzo di questo approccio significa che è molto probabile che alcuni messaggi elaborati in precedenza vengano letti ed elaborati nuovamente. Per evitare ciò, possiamo utilizzare la lettura idempotente, come descritto nel Capitolo 4, per tenere traccia dei messaggi visualizzati in precedenza ed eliminare i duplicati.

In alternativa, il tuo codice consumatore può essere mantenuto semplice, purché la perdita o la duplicazione del messaggio sia accettabile. Quando esaminiamo i casi d'uso per i quali Kafka è comunemente utilizzato, come la gestione di eventi di registro, metriche, tracciamento dei clic, ecc., ci rendiamo conto che è improbabile che la perdita di singoli messaggi abbia un impatto significativo sulle applicazioni circostanti. In tali casi, i valori predefiniti sono perfettamente accettabili. Se invece la tua applicazione richiede l'invio di pagamenti, devi curare attentamente ogni singolo messaggio. Tutto si riduce al contesto.

Le osservazioni personali mostrano che all'aumentare dell'intensità dei messaggi, il valore di ogni singolo messaggio diminuisce. I messaggi di grandi dimensioni tendono ad essere preziosi se visualizzati in forma aggregata.

Alta disponibilità

L'approccio di Kafka all'alta disponibilità è molto diverso dall'approccio di ActiveMQ. Kafka è progettato attorno a cluster scale-out in cui tutte le istanze del broker ricevono e distribuiscono messaggi contemporaneamente.

Un cluster Kafka è costituito da più istanze broker in esecuzione su server diversi. Kafka è stato progettato per funzionare su un normale hardware autonomo, in cui ogni nodo ha il proprio spazio di archiviazione dedicato. L'uso di SAN (Network Attached Storage) non è consigliato perché più nodi di calcolo possono competere per il tempo.Ыe intervalli di memorizzazione e creare conflitti.

Kafka lo è sempre acceso sistema. Molti grandi utenti di Kafka non chiudono mai i propri cluster e il software si aggiorna sempre con un riavvio sequenziale. Ciò si ottiene garantendo la compatibilità con la versione precedente per i messaggi e le interazioni tra broker.

Broker connessi a un cluster di server Custode dello zoo, che funge da registro dei dati di configurazione e viene utilizzato per coordinare i ruoli di ciascun broker. Lo stesso ZooKeeper è un sistema distribuito che fornisce un'elevata disponibilità attraverso la replica delle informazioni stabilendo quorum.

Nel caso base, viene creato un argomento in un cluster Kafka con le seguenti proprietà:

  • Il numero di partizioni. Come discusso in precedenza, il valore esatto utilizzato qui dipende dal livello desiderato di lettura parallela.
  • Il fattore di replica (fattore) determina quante istanze del broker nel cluster devono contenere i log per questa partizione.

Utilizzando ZooKeepers per il coordinamento, Kafka tenta di distribuire in modo equo le nuove partizioni tra i broker nel cluster. Questo viene fatto da una singola istanza che funge da controller.

In fase di esecuzione per ogni partizione tematica Controller assegnare ruoli a un broker il capo (leader, maestro, presentatore) e seguaci (seguaci, schiavi, subordinati). Il broker, agendo come leader per questa partizione, è responsabile della ricezione di tutti i messaggi inviatigli dai produttori e della distribuzione dei messaggi ai consumatori. Quando i messaggi vengono inviati a una partizione argomento, vengono replicati su tutti i nodi broker che fungono da follower per quella partizione. Viene richiamato ogni nodo contenente i log per una partizione replica. Un broker può fungere da leader per alcune partizioni e da follower per altre.

Viene chiamato un follower contenente tutti i messaggi tenuti dal leader replica sincronizzata (una replica che si trova in uno stato sincronizzato, replica in-sync). Se un broker che funge da leader per una partizione diventa inattivo, qualsiasi broker aggiornato o sincronizzato per quella partizione può assumere il ruolo di leader. È un design incredibilmente sostenibile.

Parte della configurazione del produttore è il parametro ack, che determina quante repliche devono riconoscere (confermare) la ricezione di un messaggio prima che il thread dell'applicazione continui a inviare: 0, 1 o tutti. Se impostato su contro tutti i, quindi quando viene ricevuto un messaggio, il leader invierà una conferma al produttore non appena riceve conferme (riconoscimenti) del record da diversi segnali (incluso se stesso) definiti dall'impostazione dell'argomento repliche.min.insinc (predefinito 1). Se il messaggio non può essere replicato correttamente, il produttore genererà un'eccezione dell'applicazione (Non abbastanza repliche o NotEnoughReplicasAfterAppend).

Una configurazione tipica crea un argomento con un fattore di replica pari a 3 (1 leader, 2 follower per partizione) e il parametro repliche.min.insinc è impostato su 2. In questo caso, il cluster consentirà a uno dei broker che gestiscono la partizione dell'argomento di disattivarsi senza influire sulle applicazioni client.

Questo ci riporta al già familiare compromesso tra prestazioni e affidabilità. La replica avviene a scapito di tempi di attesa aggiuntivi per le conferme (riconoscimenti) da parte dei follower. Tuttavia, poiché viene eseguita in parallelo, la replica su almeno tre nodi ha le stesse prestazioni di due (ignorando l'aumento dell'utilizzo della larghezza di banda della rete).

Utilizzando questo schema di replica, Kafka evita abilmente la necessità di scrivere fisicamente ogni messaggio su disco con l'operazione sincronizza(). Ogni messaggio inviato dal producer verrà scritto nel log della partizione, ma come discusso nel Capitolo 2, la scrittura su un file viene inizialmente eseguita nel buffer del sistema operativo. Se questo messaggio viene replicato su un'altra istanza di Kafka ed è nella sua memoria, la perdita del leader non significa che il messaggio stesso è stato perso: può essere rilevato da una replica sincronizzata.
Rifiuto di eseguire l'operazione sincronizza() significa che Kafka può ricevere messaggi alla stessa velocità con cui può scriverli nella memoria. Al contrario, più a lungo puoi evitare di scaricare la memoria su disco, meglio è. Per questo motivo, non è raro che ai broker Kafka vengano assegnati 64 GB o più di memoria. Questo utilizzo della memoria significa che una singola istanza di Kafka può facilmente essere eseguita a velocità migliaia di volte superiori rispetto a un broker di messaggi tradizionale.

Kafka può anche essere configurato per applicare l'operazione sincronizza() ai pacchetti di messaggi. Poiché tutto in Kafka è orientato ai pacchetti, in realtà funziona abbastanza bene per molti casi d'uso ed è uno strumento utile per gli utenti che richiedono garanzie molto solide. Gran parte delle prestazioni pure di Kafka deriva dai messaggi che vengono inviati al broker come pacchetti e che questi messaggi vengono letti dal broker in blocchi sequenziali utilizzando copia zero operazioni (operazioni durante le quali non viene eseguita l'attività di copia dei dati da un'area di memoria a un'altra). Quest'ultimo è un grande guadagno in termini di prestazioni e risorse ed è possibile solo attraverso l'uso di una struttura di dati di registro sottostante che definisce lo schema di partizione.

Sono possibili prestazioni molto migliori in un cluster Kafka rispetto a un singolo broker Kafka, poiché le partizioni degli argomenti possono essere ridimensionate su molte macchine separate.

Risultati di

In questo capitolo, abbiamo visto come l'architettura Kafka reinventa la relazione tra client e broker per fornire una pipeline di messaggistica incredibilmente robusta, con un throughput molte volte superiore a quello di un broker di messaggi convenzionale. Abbiamo discusso la funzionalità che utilizza per raggiungere questo obiettivo e brevemente esaminato l'architettura delle applicazioni che forniscono questa funzionalità. Nel prossimo capitolo, esamineremo i problemi comuni che le applicazioni basate sulla messaggistica devono risolvere e discuteremo le strategie per affrontarli. Concluderemo il capitolo delineando come parlare delle tecnologie di messaggistica in generale in modo da poter valutare la loro idoneità per i tuoi casi d'uso.

Parte precedente tradotta: Comprensione dei broker di messaggi. Imparare i meccanismi della messaggistica con ActiveMQ e Kafka. Capitolo 1

Traduzione fatta: tele.gg/middle_java

To be continued ...

Solo gli utenti registrati possono partecipare al sondaggio. AccediPer favore.

Kafka è usato nella tua organizzazione?

  • No

  • Precedentemente utilizzato, ora no

  • Abbiamo in programma di utilizzare

38 utenti hanno votato. 8 utenti si sono astenuti.

Fonte: habr.com

Aggiungi un commento