Entendre els intermediaris de missatges. Aprendre la mecànica de la missatgeria amb ActiveMQ i Kafka. Capítol 3. Kafka

Continuació de la traducció d'un petit llibre:
Entendre els corredors de missatges
autor: Jakub Korab, editor: O'Reilly Media, Inc., data de publicació: juny de 2017, ISBN: 9781492049296.

Part traduïda anterior: Entendre els intermediaris de missatges. Aprendre la mecànica de la missatgeria amb ActiveMQ i Kafka. Capítol 1 Introducció

CAPÍTOL 3

Kafka

Kafka es va desenvolupar a LinkedIn per evitar algunes de les limitacions dels intermediaris de missatges tradicionals i evitar haver de configurar diversos intermediaris de missatges per a diferents interaccions punt a punt, que es descriu en aquest llibre a "Ampliació i ampliació" a la pàgina 28. . Casos d'ús LinkedIn s'ha basat en gran mesura en la ingestió unidireccional de quantitats molt grans de dades, com ara clics a les pàgines i registres d'accés, tot i que permet que aquestes dades siguin utilitzades per diversos sistemes sense afectar la productivitat dels productors o d'altres consumidors. De fet, la raó per la qual existeix Kafka és per obtenir el tipus d'arquitectura de missatgeria que descriu l'Universal Data Pipeline.

Tenint en compte aquest objectiu final, naturalment van sorgir altres requisits. Kafka hauria de:

  • Sigues extremadament ràpid
  • Proporcioneu més amplada de banda quan treballeu amb missatges
  • Suport als models Editor-Subscriptor i Punt a punt
  • No freneu amb l'addició de consumidors. Per exemple, el rendiment tant de la cua com del tema a ActiveMQ es degrada a mesura que augmenta el nombre de consumidors a la destinació.
  • Ser escalable horitzontalment; si un agent que persisteix missatges només ho pot fer a la velocitat màxima del disc, té sentit anar més enllà d'una única instància d'agent per augmentar el rendiment.
  • Limiteu l'accés a l'emmagatzematge i la recuperació de missatges

Per aconseguir tot això, Kafka va adoptar una arquitectura que va redefinir els rols i les responsabilitats dels clients i els corredors de missatgeria. El model JMS està molt orientat al broker, on el broker s'encarrega de distribuir missatges i els clients només s'han de preocupar d'enviar i rebre missatges. Kafka, d'altra banda, està centrat en el client, ja que el client assumeix moltes de les característiques d'un corredor tradicional, com ara la distribució justa de missatges rellevants als consumidors, a canvi d'un corredor extremadament ràpid i escalable. Per a les persones que han treballat amb sistemes de missatgeria tradicionals, treballar amb Kafka requereix un canvi d'opinió fonamental.
Aquesta direcció d'enginyeria ha donat lloc a la creació d'una infraestructura de missatgeria capaç d'augmentar el rendiment en molts ordres de magnitud en comparació amb un corredor convencional. Com veurem, aquest enfocament inclou compensacions, la qual cosa significa que Kafka no és adequat per a determinats tipus de càrregues de treball i programari instal·lat.

Model de destinació unificada

Per complir amb els requisits descrits anteriorment, Kafka ha combinat la publicació-subscripció i la missatgeria punt a punt en un mateix tipus de destinació - tema. Això és confús per a les persones que han treballat amb sistemes de missatgeria, on la paraula "tema" fa referència a un mecanisme de difusió des del qual (des del tema) la lectura no és duradora. Els temes de Kafka s'han de considerar un tipus de destinació híbrida, tal com es defineix a la introducció d'aquest llibre.

Per a la resta d'aquest capítol, tret que indiquim el contrari explícitament, el terme "tema" es referirà a un tema de Kafka.

Per entendre completament com es comporten els temes i quines garanties ofereixen, primer hem de veure com s'implementen a Kafka.
Cada tema a Kafka té el seu propi registre.
Els productors que envien missatges a Kafka escriuen en aquest registre i els consumidors llegeixen del registre utilitzant punters que avancen constantment. Periòdicament, Kafka elimina les parts més antigues del registre, tant si els missatges d'aquestes parts s'han llegit com si no. Una part central del disseny de Kafka és que al corredor no li importa si els missatges es llegeixen o no, això és responsabilitat del client.

Els termes "registre" i "punter" no apareixen a Documentació Kafka. Aquests termes coneguts s'utilitzen aquí per facilitar la comprensió.

Aquest model és completament diferent d'ActiveMQ, on els missatges de totes les cues s'emmagatzemen al mateix registre i l'agent marca els missatges com a suprimits després d'haver-los llegit.
Ara aprofundim una mica més i mirem el registre del tema amb més detall.
El registre de Kafka consta de diverses particions (Figura 3 1-). Kafka garanteix un ordre estricte a cada partició. Això vol dir que els missatges escrits a la partició en un ordre determinat es llegiran en el mateix ordre. Cada partició s'implementa com un fitxer de registre continuat que conté subconjunt (subconjunt) de tots els missatges enviats al tema pels seus productors. El tema creat conté, per defecte, una partició. La idea de les particions és la idea central de Kafka per a l'escala horitzontal.

Entendre els intermediaris de missatges. Aprendre la mecànica de la missatgeria amb ActiveMQ i Kafka. Capítol 3. Kafka
Figura 3-1. Envans Kafka

Quan un productor envia un missatge a un tema de Kafka, decideix a quina partició enviar el missatge. Veurem això amb més detall més endavant.

Lectura de missatges

El client que vol llegir els missatges gestiona un punter anomenat grup de consumidors, que apunta compensació missatges a la partició. Un desplaçament és una posició incremental que comença a 0 a l'inici d'una partició. Aquest grup de consumidors, al qual es fa referència a l'API mitjançant el group_id definit per l'usuari, correspon un consumidor o sistema lògic.

La majoria dels sistemes de missatgeria llegeixen dades des de la destinació utilitzant diverses instàncies i fils per processar missatges en paral·lel. Per tant, normalment hi haurà moltes instàncies de consumidors compartint el mateix grup de consumidors.

El problema de la lectura es pot representar de la següent manera:

  • El tema té diverses particions
  • Diversos grups de consumidors poden utilitzar un tema al mateix temps
  • Un grup de consumidors pot tenir diverses instàncies separades

Aquest és un problema de molts a molts no trivial. Per entendre com gestiona Kafka les relacions entre grups de consumidors, instàncies de consumidors i particions, mirem una sèrie d'escenaris de lectura progressivament més complexos.

Consumidors i grups de consumidors

Prenem com a punt de partida un tema amb una partició (Figura 3 2-).

Entendre els intermediaris de missatges. Aprendre la mecànica de la missatgeria amb ActiveMQ i Kafka. Capítol 3. Kafka
Figura 3-2. El consumidor llegeix des de la partició

Quan una instància de consumidor es connecta amb el seu propi group_id a aquest tema, se li assigna una partició de lectura i un desplaçament en aquesta partició. La posició d'aquest desplaçament es pot configurar al client com un punter a la posició més recent (missatge més recent) o la posició més antiga (missatge més antic). El consumidor sol·licita (enquestes) missatges del tema, la qual cosa fa que es llegeixin seqüencialment del registre.
La posició de compensació es torna a enviar regularment a Kafka i s'emmagatzema com a missatges en un tema intern _compensacions_de_consumidor. Els missatges llegits encara no s'esborren, a diferència d'un corredor normal, i el client pot rebobinar el desplaçament per tornar a processar els missatges ja vists.

Quan un segon consumidor lògic es connecta mitjançant un group_id diferent, gestiona un segon punter que és independent del primer (Figura 3 3-). Així, un tema de Kafka actua com una cua on hi ha un consumidor i com un tema normal de publicació-subscripció (pub-sub) al qual es subscriuen diversos consumidors, amb l'avantatge afegit que tots els missatges s'emmagatzemen i es poden processar diverses vegades.

Entendre els intermediaris de missatges. Aprendre la mecànica de la missatgeria amb ActiveMQ i Kafka. Capítol 3. Kafka
Figura 3-3. Dos consumidors de diferents grups de consumidors llegeixen des de la mateixa partició

Consumidors d'un grup de consumidors

Quan una instància de consumidor llegeix dades d'una partició, té el control total del punter i processa els missatges tal com es descriu a la secció anterior.
Si es van connectar diverses instàncies de consumidors amb el mateix group_id a un tema amb una partició, llavors la instància que s'ha connectat per darrera tindrà el control del punter i a partir d'aquest moment rebrà tots els missatges (Figura 3 4-).

Entendre els intermediaris de missatges. Aprendre la mecànica de la missatgeria amb ActiveMQ i Kafka. Capítol 3. Kafka
Figura 3-4. Dos consumidors del mateix grup de consumidors llegeixen des de la mateixa partició

Aquest mode de processament, en què el nombre d'instàncies de consumidor supera el nombre de particions, es pot considerar com una mena de consumidor exclusiu. Això pot ser útil si necessiteu un agrupament "actiu-passiu" (o "calent-calent") de les vostres instàncies de consumidor, tot i que executar diversos consumidors en paral·lel ("actiu-actiu" o "calent-calent") és molt més típic que consumidors En espera.

Aquest comportament de distribució de missatges descrit anteriorment pot ser sorprenent en comparació amb com es comporta una cua JMS normal. En aquest model, els missatges enviats a la cua es distribuiran uniformement entre els dos consumidors.

Molt sovint, quan creem múltiples instàncies de consumidors, ho fem per processar missatges en paral·lel, per augmentar la velocitat de lectura o per augmentar l'estabilitat del procés de lectura. Com que només una instància de consumidor pot llegir dades d'una partició alhora, com s'aconsegueix això a Kafka?

Una manera de fer-ho és utilitzar una única instància de consumidor per llegir tots els missatges i passar-los al grup de fils. Tot i que aquest enfocament augmenta el rendiment del processament, augmenta la complexitat de la lògica del consumidor i no fa res per augmentar la robustesa del sistema de lectura. Si una còpia del consumidor cau a causa d'una fallada de corrent o un esdeveniment similar, la resta s'atura.

La manera canònica de resoldre aquest problema a Kafka és utilitzar bОmés particions.

Particionament

Les particions són el mecanisme principal per paral·lelitzar la lectura i escalar un tema més enllà de l'ample de banda d'una única instància de corredor. Per entendre-ho millor, considerem una situació en què hi ha un tema amb dues particions i un consumidor es subscriu a aquest tema (Figura 3 5-).

Entendre els intermediaris de missatges. Aprendre la mecànica de la missatgeria amb ActiveMQ i Kafka. Capítol 3. Kafka
Figura 3-5. Un consumidor llegeix des de diverses particions

En aquest escenari, el consumidor té control sobre els punters corresponents al seu group_id en ambdues particions i comença a llegir missatges d'ambdues particions.
Quan s'afegeix un consumidor addicional per al mateix group_id a aquest tema, Kafka reassigna una de les particions del primer al segon consumidor. Després d'això, cada instància del consumidor llegirà des d'una partició del tema (Figura 3 6-).

Per garantir que els missatges es processin en paral·lel en 20 fils, necessiteu almenys 20 particions. Si hi ha menys particions, us quedareu amb consumidors que no tenen res per treballar, tal com es va descriure anteriorment a la discussió dels consumidors exclusius.

Entendre els intermediaris de missatges. Aprendre la mecànica de la missatgeria amb ActiveMQ i Kafka. Capítol 3. Kafka
Figura 3-6. Dos consumidors del mateix grup de consumidors llegeixen des de diferents particions

Aquest esquema redueix molt la complexitat del corredor de Kafka en comparació amb la distribució de missatges necessària per mantenir la cua JMS. Aquí no us haureu de preocupar pels següents punts:

  • Quin consumidor hauria de rebre el següent missatge, en funció de l'assignació round-robin, la capacitat actual dels búfers de recuperació prèvia o missatges anteriors (com per als grups de missatges JMS).
  • Quins missatges s'envien a quins consumidors i si s'han de tornar a lliurar en cas de fallada.

L'únic que ha de fer el corredor de Kafka és passar missatges seqüencialment al consumidor quan aquest els sol·liciti.

Tanmateix, els requisits per a paral·lelitzar la correcció de proves i reenviar missatges fallits no desapareixen: la responsabilitat simplement passa del corredor al client. Això vol dir que s'han de tenir en compte al vostre codi.

Enviament de missatges

És responsabilitat del productor d'aquest missatge decidir a quina partició enviar un missatge. Per entendre el mecanisme pel qual es fa això, primer hem de considerar què estem enviant realment.

Mentre que a JMS fem servir una estructura de missatge amb metadades (capçaleres i propietats) i un cos que conté la càrrega útil (càrrega útil), a Kafka el missatge és parella "clau-valor". La càrrega útil del missatge s'envia com a valor. La clau, en canvi, s'utilitza principalment per a la partició i ha de contenir clau específica de la lògica empresarialper posar missatges relacionats a la mateixa partició.

Al capítol 2, vam parlar de l'escenari d'apostes en línia on els esdeveniments relacionats han de ser processats per un sol consumidor:

  1. El compte d'usuari està configurat.
  2. Els diners s'acrediten al compte.
  3. Es fa una aposta que retira diners del compte.

Si cada esdeveniment és un missatge publicat a un tema, la clau natural seria l'identificador del compte.
Quan s'envia un missatge mitjançant l'API Kafka Producer, es passa a una funció de partició que, donat el missatge i l'estat actual del clúster de Kafka, retorna l'ID de la partició a la qual s'ha d'enviar el missatge. Aquesta característica s'implementa a Java mitjançant la interfície Partitioner.

Aquesta interfície té aquest aspecte:

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

La implementació de Partitioner utilitza l'algoritme de resum de propòsit general predeterminat sobre la clau per determinar la partició, o el round-robin si no s'especifica cap clau. Aquest valor predeterminat funciona bé en la majoria dels casos. Tanmateix, en el futur voldreu escriure el vostre.

Escriure la teva pròpia estratègia de partició

Vegem un exemple on voleu enviar metadades juntament amb la càrrega útil del missatge. La càrrega útil del nostre exemple és una instrucció per fer un dipòsit al compte del joc. Una instrucció és una cosa que ens agradaria que se'ns garantissin que no es modificarien en la transmissió i volem estar segurs que només un sistema amunt de confiança pot iniciar aquesta instrucció. En aquest cas, els sistemes d'enviament i de recepció acorden l'ús d'una signatura per autenticar el missatge.
En JMS normal, simplement definim una propietat de "signatura del missatge" i l'afegim al missatge. Tanmateix, Kafka no ens proporciona un mecanisme per passar metadades, només una clau i un valor.

Com que el valor és una càrrega útil de transferència bancària la integritat de la qual volem preservar, no tenim més remei que definir l'estructura de dades que s'utilitzarà a la clau. Suposant que necessitem un ID de compte per a la partició, ja que tots els missatges relacionats amb un compte s'han de processar en ordre, obtindrem l'estructura JSON següent:

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

Com que el valor de la signatura variarà en funció de la càrrega útil, l'estratègia hash predeterminada de la interfície del particionador no agruparà de manera fiable els missatges relacionats. Per tant, haurem d'escriure la nostra pròpia estratègia que analitzarà aquesta clau i particioni el valor accountId.

Kafka inclou sumes de control per detectar la corrupció dels missatges a la botiga i té un conjunt complet de funcions de seguretat. Tot i així, de vegades apareixen requisits específics de la indústria, com l'anterior.

L'estratègia de partició de l'usuari ha de garantir que tots els missatges relacionats acabin a la mateixa partició. Tot i que això sembla senzill, el requisit es pot complicar per la importància d'ordenar les publicacions relacionades i la manera de fixar el nombre de particions d'un tema.

El nombre de particions d'un tema pot canviar amb el temps, ja que es poden afegir si el trànsit va més enllà de les expectatives inicials. Així, les claus de missatges es poden associar amb la partició a la qual es van enviar originalment, la qual cosa implica que es compartirà una part d'estat entre les instàncies del productor.

Un altre factor a tenir en compte és la distribució uniforme dels missatges entre particions. Normalment, les claus no es distribueixen uniformement entre els missatges i les funcions hash no garanteixen una distribució justa dels missatges per a un petit conjunt de claus.
És important tenir en compte que, com sigui que trieu dividir els missatges, és possible que s'hagi de reutilitzar el separador.

Considereu el requisit de replicar dades entre clústers de Kafka en diferents ubicacions geogràfiques. Amb aquesta finalitat, Kafka ve amb una eina de línia d'ordres anomenada MirrorMaker, que s'utilitza per llegir missatges d'un clúster i transferir-los a un altre.

MirrorMaker ha d'entendre les claus del tema replicat per tal de mantenir l'ordre relatiu entre missatges quan es replica entre clústers, ja que el nombre de particions per a aquest tema pot no ser el mateix en dos clústers.

Les estratègies de partició personalitzades són relativament rares, ja que el hash predeterminat o el round robin funciona bé en la majoria dels escenaris. Tanmateix, si necessiteu garanties de comanda sòlides o necessiteu extreure metadades de les càrregues útils, la partició és una cosa que hauríeu de mirar més de prop.

Els beneficis d'escalabilitat i rendiment de Kafka provenen del canvi d'algunes de les responsabilitats del corredor tradicional al client. En aquest cas, es pren la decisió de distribuir missatges potencialment relacionats entre diversos consumidors que treballen en paral·lel.

Els corredors de JMS també han de fer front a aquests requisits. Curiosament, el mecanisme per enviar missatges relacionats al mateix consumidor, implementat mitjançant JMS Message Groups (una variació de l'estratègia d'equilibri de càrrega enganxosa (SLB)), també requereix que el remitent marqui els missatges com a relacionats. En el cas de JMS, l'agent és responsable d'enviar aquest grup de missatges relacionats a un consumidor entre molts i transferir la propietat del grup si el consumidor cau.

Acords de productors

El particionament no és l'únic que cal tenir en compte a l'hora d'enviar missatges. Fem una ullada als mètodes send() de la classe Producer a l'API de Java:

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

S'ha de tenir en compte immediatament que tots dos mètodes retornen Future, que indica que l'operació d'enviament no es realitza immediatament. El resultat és que s'escriu un missatge (ProducerRecord) a la memòria intermèdia d'enviament per a cada partició activa i s'envia al corredor com a fil de fons a la biblioteca del client Kafka. Tot i que això fa que les coses siguin increïblement ràpides, vol dir que una aplicació sense experiència pot perdre missatges si el seu procés s'atura.

Com sempre, hi ha una manera de fer que l'operació d'enviament sigui més fiable a costa del rendiment. La mida d'aquesta memòria intermèdia es pot establir en 0 i el fil de l'aplicació d'enviament es veurà obligat a esperar fins que s'hagi completat la transferència del missatge al corredor, de la manera següent:

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

Més informació sobre la lectura de missatges

La lectura de missatges té complexitats addicionals sobre les quals cal especular. A diferència de l'API JMS, que pot executar un oient de missatges en resposta a un missatge, el Consumidor Kafka només enquestes. Fem una ullada més de prop al mètode enquesta()utilitzat per a aquest propòsit:

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

El valor de retorn del mètode és una estructura de contenidor que conté diversos objectes registre del consumidor de diverses particions potencialment. registre del consumidor és en si mateix un objecte titular per a un parell clau-valor amb metadades associades, com ara la partició de la qual es deriva.

Com s'ha comentat al capítol 2, hem de tenir en compte què passa amb els missatges després d'haver estat processats amb èxit o sense èxit, per exemple, si el client no pot processar el missatge o si avorta. A JMS, això es va gestionar mitjançant un mode de reconeixement. El corredor suprimirà el missatge processat amb èxit o tornarà a lliurar el missatge en brut o fals (suposant que s'han utilitzat transaccions).
Kafka funciona de manera molt diferent. Els missatges no s'eliminen al corredor després de la correcció, i el que passa en cas d'error és responsabilitat del propi codi de correcció.

Com hem dit, el grup de consumidors està associat al desplaçament del registre. La posició de registre associada a aquest desplaçament correspon al següent missatge que s'emetrà en resposta enquesta(). El moment en què augmenta aquesta compensació és decisiu per a la lectura.

Tornant al model de lectura comentat anteriorment, el processament de missatges consta de tres etapes:

  1. Recuperar un missatge per llegir.
  2. Processar el missatge.
  3. Confirmeu el missatge.

El consumidor de Kafka inclou una opció de configuració enable.auto.commit. Aquesta és una configuració predeterminada que s'utilitza amb freqüència, com és habitual amb les opcions que contenen la paraula "automàtic".

Abans de Kafka 0.10, un client que feia servir aquesta opció enviava el desplaçament de l'últim missatge llegit a la trucada següent enquesta() després del processament. Això significava que els missatges que ja s'havien obtingut es podrien tornar a processar si el client ja els havia processat però s'havia destruït inesperadament abans de trucar. enquesta(). Com que el corredor no manté cap estat sobre quantes vegades s'ha llegit un missatge, el següent consumidor que recuperi aquest missatge no sabrà que ha passat alguna cosa dolenta. Aquest comportament era pseudo-transaccional. El desplaçament només es va confirmar si el missatge es va processar correctament, però si el client avortava, l'agent tornaria a enviar el mateix missatge a un altre client. Aquest comportament era coherent amb la garantia de lliurament de missatges "al menys un cop".

A Kafka 0.10, el codi del client s'ha canviat de manera que el commit s'activa periòdicament per la biblioteca del client, tal com s'ha configurat auto.commit.interval.ms. Aquest comportament es troba entre els modes JMS AUTO_ACKNOWLEDGE i DUPS_OK_ACKNOWLEDGE. Quan s'utilitza l'autocommit, els missatges es podrien confirmar independentment de si s'han processat realment; això podria passar en el cas d'un consumidor lent. Si un consumidor avortava, els missatges els recuperaria el següent consumidor, començant per la posició compromesa, la qual cosa podria donar lloc a un missatge perdut. En aquest cas, Kafka no va perdre els missatges, el codi de lectura simplement no els va processar.

Aquest mode té les mateixes perspectives que a la versió 0.9: els missatges es poden processar, però si falla, és possible que no es comprometi el desplaçament, la qual cosa podria provocar que es dupliqui el lliurament. Com més missatges obtingueu en executar enquesta(), més aquest problema.

Tal com s'explica a “Llegir missatges des d'una cua” a la pàgina 21, no existeix l'entrega única d'un missatge en un sistema de missatgeria quan es tenen en compte els modes d'error.

A Kafka, hi ha dues maneres de confirmar (commetre) un desplaçament (offset): automàticament i manualment. En ambdós casos, els missatges es poden processar diverses vegades si el missatge s'ha processat però ha fallat abans de la confirmació. També podeu optar per no processar el missatge en absolut si la confirmació es va produir en segon pla i el vostre codi s'ha completat abans que es pogués processar (potser a Kafka 0.9 i anteriors).

Podeu controlar el procés de confirmació de compensació manual a l'API del consumidor de Kafka establint el paràmetre enable.auto.commit a false i cridar explícitament un dels mètodes següents:

void commitSync();
void commitAsync();

Si voleu processar el missatge "almenys una vegada", heu de confirmar el desplaçament manualment commitSync()executant aquesta ordre immediatament després de processar els missatges.

Aquests mètodes no permeten reconèixer els missatges abans de processar-los, però no fan res per eliminar possibles retards en el processament alhora que donen l'aspecte de transaccional. No hi ha transaccions a Kafka. El client no té la capacitat de fer el següent:

  • Desfer automàticament un missatge falsificat. Els mateixos consumidors han de gestionar les excepcions derivades de càrregues útils problemàtiques i interrupcions del backend, ja que no poden confiar en l'agent per tornar a lliurar els missatges.
  • Envieu missatges a diversos temes en una operació atòmica. Com veurem aviat, el control sobre diferents temes i particions pot residir en diferents màquines del clúster de Kafka que no coordinen les transaccions quan s'envien. En el moment d'escriure aquest article, s'ha treballat per fer-ho possible amb el KIP-98.
  • Associa la lectura d'un missatge d'un tema amb l'enviament d'un altre missatge a un altre tema. De nou, l'arquitectura de Kafka depèn de moltes màquines independents que funcionen com un bus i no es fa cap intent d'amagar-ho. Per exemple, no hi ha components d'API que us permetin enllaçar consumidor и Productor en una transacció. A JMS, això el proporciona l'objecte sessióa partir dels quals es creen Productors de missatges и MissatgeConsumidors.

Si no podem confiar en les transaccions, com podem oferir una semàntica més propera a la que proporcionen els sistemes de missatgeria tradicionals?

Si hi ha la possibilitat que la compensació del consumidor augmenti abans que el missatge s'hagi processat, com ara durant un accident de consum, el consumidor no té manera de saber si el seu grup de consumidors ha perdut el missatge quan se li assigna una partició. Per tant, una estratègia és rebobinar el desplaçament a la posició anterior. L'API del consumidor de Kafka proporciona els mètodes següents per a això:

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

Mètode buscar() es pot utilitzar amb el mètode
offsetsForTimes (Mapa timestampsToSearch) per rebobinar a un estat en algun moment concret del passat.

Implícitament, utilitzar aquest enfocament significa que és molt probable que alguns missatges que s'han processat anteriorment es llegeixin i tornin a processar. Per evitar-ho, podem utilitzar la lectura idempotent, tal com es descriu al capítol 4, per fer un seguiment dels missatges vists prèviament i eliminar els duplicats.

Alternativament, el vostre codi de consumidor es pot mantenir senzill, sempre que la pèrdua o la duplicació del missatge sigui acceptable. Quan mirem els casos d'ús per als quals s'utilitza habitualment Kafka, com ara la gestió d'esdeveniments de registre, mètriques, seguiment de clics, etc., ens adonem que és poc probable que la pèrdua de missatges individuals tingui un impacte significatiu en les aplicacions circumdants. En aquests casos, els valors per defecte són perfectament acceptables. D'altra banda, si la vostra aplicació necessita enviar pagaments, heu de tenir cura de cada missatge individualment. Tot es redueix al context.

Les observacions personals mostren que a mesura que augmenta la intensitat dels missatges, el valor de cada missatge individual disminueix. Els missatges grans solen ser valuosos quan es veuen de forma agregada.

Alta disponibilitat

L'enfocament de Kafka a l'alta disponibilitat és molt diferent de l'enfocament d'ActiveMQ. Kafka està dissenyat al voltant de clústers escalables on totes les instàncies del corredor reben i distribueixen missatges al mateix temps.

Un clúster de Kafka consta de diverses instàncies de corredor que s'executen en diferents servidors. Kafka va ser dissenyat per funcionar amb maquinari autònom normal, on cada node té el seu propi emmagatzematge dedicat. No es recomana l'ús d'emmagatzematge connectat a la xarxa (SAN) perquè diversos nodes de càlcul poden competir pel temps.Ыe intervals d'emmagatzematge i crear conflictes.

Kafka ho és sempre encés sistema. Molts usuaris grans de Kafka mai tanquen els seus clústers i el programari sempre s'actualitza amb un reinici seqüencial. Això s'aconsegueix garantint la compatibilitat amb la versió anterior per als missatges i les interaccions entre corredors.

Agents connectats a un clúster de servidors ZooKeeper, que actua com a registre de dades de configuració i s'utilitza per coordinar les funcions de cada corredor. El mateix ZooKeeper és un sistema distribuït que proporciona alta disponibilitat mitjançant la replicació de la informació mitjançant l'establiment quòrum.

En el cas base, es crea un tema en un clúster de Kafka amb les propietats següents:

  • El nombre de particions. Com s'ha comentat anteriorment, el valor exacte utilitzat aquí depèn del nivell desitjat de lectura paral·lela.
  • El factor de replicació (factor) determina quantes instàncies del corredor al clúster han de contenir registres per a aquesta partició.

Utilitzant ZooKeepers per a la coordinació, Kafka intenta distribuir de manera justa les noves particions entre els corredors del clúster. Això ho fa una única instància que actua com a controlador.

En temps d'execució per a cada partició de tema Controlador assignar rols a un corredor líder (líder, mestre, presentador) i seguidors (seguidors, esclaus, subordinats). El corredor, que actua com a líder d'aquesta partició, s'encarrega de rebre tots els missatges que li envien els productors i de distribuir els missatges als consumidors. Quan els missatges s'envien a una partició de tema, es repliquen a tots els nodes del corredor que actuen com a seguidors d'aquesta partició. Es crida a cada node que conté registres per a una partició rèplica. Un corredor pot actuar com a líder per a algunes particions i com a seguidor per a altres.

Es crida a un seguidor que conté tots els missatges que té el líder rèplica sincronitzada (una rèplica que es troba en un estat sincronitzat, una rèplica en sincronització). Si un agent que actua com a líder d'una partició cau, qualsevol agent que estigui actualitzat o sincronitzat per a aquesta partició pot assumir el rol de líder. És un disseny increïblement sostenible.

Part de la configuració del productor és el paràmetre acks, que determina quantes rèpliques han de reconèixer (acusar) la recepció d'un missatge abans que el fil de l'aplicació continuï enviant: 0, 1 o totes. Si s'estableix a tots, aleshores, quan es rep un missatge, el líder enviarà una confirmació al productor tan bon punt rebi confirmacions (reconeixements) del registre de diverses indicacions (inclòs ell mateix) definides per la configuració del tema min.insync.replicas (per defecte 1). Si el missatge no es pot replicar correctament, el productor llançarà una excepció d'aplicació (Not EnoughReplicas o Not EnoughReplicasAfterAppend).

Una configuració típica crea un tema amb un factor de replicació de 3 (1 líder, 2 seguidors per partició) i el paràmetre min.insync.replicas s'estableix en 2. En aquest cas, el clúster permetrà que un dels intermediaris que gestionen la partició del tema baixi sense afectar les aplicacions del client.

Això ens porta de nou al compromís ja conegut entre rendiment i fiabilitat. La replicació es produeix a costa del temps d'espera addicional per a les confirmacions (agraïments) dels seguidors. Tot i que, com que s'executa en paral·lel, la replicació a almenys tres nodes té el mateix rendiment que dos (ignorant l'augment de l'ús de l'ample de banda de la xarxa).

Mitjançant aquest esquema de replicació, Kafka evita intel·ligentment la necessitat d'escriure físicament cada missatge al disc amb l'operació. sincronitzar(). Cada missatge enviat pel productor s'escriurà al registre de la partició, però com s'ha comentat al capítol 2, l'escriptura en un fitxer es fa inicialment a la memòria intermèdia del sistema operatiu. Si aquest missatge es replica a una altra instància de Kafka i es troba a la seva memòria, la pèrdua del líder no vol dir que s'hagi perdut el missatge en si, sinó que pot ser assumit per una rèplica sincronitzada.
Negativa a realitzar l'operació sincronitzar() significa que Kafka pot rebre missatges tan ràpid com pot escriure'ls a la memòria. Per contra, com més temps pugueu evitar buidar la memòria al disc, millor. Per aquest motiu, no és estrany que els corredors de Kafka tinguin assignats 64 GB o més de memòria. Aquest ús de la memòria significa que una única instància de Kafka es pot executar fàcilment a una velocitat milers de vegades més ràpida que un corredor de missatges tradicional.

Kafka també es pot configurar per aplicar l'operació sincronitzar() als paquets de missatges. Com que tot a Kafka està orientat a paquets, en realitat funciona força bé per a molts casos d'ús i és una eina útil per als usuaris que requereixen garanties molt fortes. Gran part del rendiment pur de Kafka prové dels missatges que s'envien al corredor com a paquets i que aquests missatges es llegeixen des del corredor en blocs seqüencials mitjançant còpia zero operacions (operacions durant les quals no es realitza la tasca de copiar dades d'una àrea de memòria a una altra). Aquest últim és un gran rendiment i guany de recursos i només és possible mitjançant l'ús d'una estructura de dades de registre subjacent que defineix l'esquema de partició.

És possible un rendiment molt millor en un clúster de Kafka que amb un únic intermediari de Kafka, perquè les particions de tema es poden escalar en moltes màquines separades.

Resultats de

En aquest capítol, vam analitzar com l'arquitectura Kafka reimagina la relació entre clients i corredors per proporcionar una canalització de missatgeria increïblement robusta, amb un rendiment moltes vegades més gran que el d'un corredor de missatges convencional. Hem comentat la funcionalitat que utilitza per aconseguir-ho i hem analitzat breument l'arquitectura de les aplicacions que proporcionen aquesta funcionalitat. En el següent capítol, analitzarem els problemes comuns que les aplicacions basades en missatgeria necessiten per resoldre i discutir les estratègies per tractar-los. Acabarem el capítol explicant com parlar de les tecnologies de missatgeria en general perquè pugueu avaluar-ne la idoneïtat per als vostres casos d'ús.

Part traduïda anterior: Entendre els intermediaris de missatges. Aprendre la mecànica de la missatgeria amb ActiveMQ i Kafka. Capítol 1

Traducció feta: tele.gg/middle_java

Continuar ...

Només els usuaris registrats poden participar en l'enquesta. Inicia sessiósi us plau.

S'utilitza Kafka a la vostra organització?

  • No

  • S'utilitzava abans, ara no

  • Tenim previst utilitzar

Han votat 38 usuaris. 8 usuaris es van abstenir.

Font: www.habr.com

Afegeix comentari