Comprensión dos corredores de mensaxes. Aprende a mecánica da mensaxería con ActiveMQ e Kafka. Capítulo 3. Kafka

Continuación da tradución dun pequeno libro:
Comprensión dos corredores de mensaxes
autor: Jakub Korab, editor: O'Reilly Media, Inc., data de publicación: xuño de 2017, ISBN: 9781492049296.

Parte traducida anterior: Comprensión dos corredores de mensaxes. Aprende a mecánica da mensaxería con ActiveMQ e Kafka. Capítulo 1 Introdución

CAPÍTULO 3

Kafka

Kafka foi desenvolvido en LinkedIn para sortear algunhas das limitacións dos correctores de mensaxes tradicionais e evitar ter que configurar varios corredores de mensaxes para diferentes interaccións punto a punto, que se describe neste libro en "Ampliación e ampliación" na páxina 28. . Casos de uso LinkedIn confiou en gran medida na inxestión unidireccional de cantidades moi grandes de datos, como clics nas páxinas e rexistros de acceso, aínda que permitiu que eses datos sexan utilizados por varios sistemas sen afectar a produtividade dos produtores ou doutros consumidores. De feito, a razón pola que existe Kafka é para obter o tipo de arquitectura de mensaxería que describe a Universal Data Pipeline.

Ante este obxectivo final, xurdiron naturalmente outros requisitos. Kafka debería:

  • Sexa extremadamente rápido
  • Proporciona máis ancho de banda ao traballar con mensaxes
  • Admite modelos Editor-Subscribente e Punto a Punto
  • Non abras o ritmo ao engadir consumidores. Por exemplo, o rendemento tanto da cola como do tema en ActiveMQ degrada a medida que crece o número de consumidores no destino.
  • Ser escalable horizontalmente; se un corredor que persiste mensaxes só pode facelo á velocidade máxima do disco, entón ten sentido ir máis aló dunha única instancia de corredor para aumentar o rendemento
  • Limita o acceso ao almacenamento e a recuperación de mensaxes

Para conseguir todo isto, Kafka adoptou unha arquitectura que redefiniu os roles e responsabilidades dos clientes e dos corredores de mensaxería. O modelo JMS está moi orientado ao corredor, onde o corredor é o responsable de distribuír mensaxes e os clientes só teñen que preocuparse de enviar e recibir mensaxes. Kafka, pola súa banda, está centrado no cliente, co cliente que asume moitas das características dun corredor tradicional, como a distribución xusta de mensaxes relevantes aos consumidores, a cambio dun corredor extremadamente rápido e escalable. Para as persoas que traballaron con sistemas de mensaxería tradicionais, traballar con Kafka require un cambio fundamental de opinión.
Esta dirección de enxeñería levou á creación dunha infraestrutura de mensaxería capaz de aumentar o rendemento en moitas ordes de magnitude en comparación cun corredor convencional. Como veremos, este enfoque inclúe compensacións, o que significa que Kafka non é axeitado para certos tipos de cargas de traballo e software instalado.

Modelo de destino unificado

Para cumprir os requisitos descritos anteriormente, Kafka combinou a publicación-subscrición e a mensaxería punto a punto nun mesmo tipo de destino: tema. Isto é confuso para as persoas que traballaron con sistemas de mensaxería, onde a palabra "tema" refírese a un mecanismo de difusión desde o cal (desde o tema) a lectura non é duradeira. Os temas de Kafka deberían considerarse un tipo de destino híbrido, como se define na introdución deste libro.

Para o resto deste capítulo, a non ser que expresemos o contrario, o termo "tema" referirase a un tema de Kafka.

Para comprender completamente como se comportan os temas e que garantías proporcionan, primeiro temos que ver como se implementan en Kafka.
Cada tema en Kafka ten o seu propio rexistro.
Os produtores que envían mensaxes a Kafka escriben neste rexistro e os consumidores len desde o rexistro usando punteiros que avanzan constantemente. Periódicamente, Kafka elimina as partes máis antigas do rexistro, se as mensaxes desas partes foron lidas ou non. Unha parte central do deseño de Kafka é que ao corredor non lle importa se se len as mensaxes ou non; esa é responsabilidade do cliente.

Os termos "log" e "punteiro" non aparecen en Documentación Kafka. Estes termos coñecidos utilízanse aquí para facilitar a comprensión.

Este modelo é completamente diferente de ActiveMQ, onde as mensaxes de todas as filas almacénanse no mesmo rexistro e o corredor marca as mensaxes como eliminadas despois de ser lidas.
Agora afondemos un pouco máis e vexamos o rexistro do tema con máis detalle.
O rexistro de Kafka consta de varias particións (Imaxe 3-1). Kafka garante unha orde estrita en cada partición. Isto significa que as mensaxes escritas na partición nunha determinada orde leranse na mesma orde. Cada partición está implementada como un ficheiro de rexistro continuo que contén un subconxunto (subconxunto) de todas as mensaxes enviadas ao tema polos seus produtores. O tema creado contén, por defecto, unha partición. A idea de particións é a idea central de Kafka para a escala horizontal.

Comprensión dos corredores de mensaxes. Aprende a mecánica da mensaxería con ActiveMQ e Kafka. Capítulo 3. Kafka
Figura 3-1. Particións Kafka

Cando un produtor envía unha mensaxe a un tema de Kafka, decide a que partición enviar a mensaxe. Veremos isto con máis detalle máis adiante.

Lectura de mensaxes

O cliente que quere ler as mensaxes xestiona un punteiro chamado chamado grupo de consumidores, que apunta a compensar mensaxes na partición. Un desplazamento é unha posición incremental que comeza en 0 ao comezo dunha partición. Este grupo de consumidores, ao que se fai referencia na API mediante o group_id definido polo usuario, corresponde un consumidor ou sistema lóxico.

A maioría dos sistemas de mensaxería len datos do destino usando varias instancias e fíos para procesar mensaxes en paralelo. Así, normalmente haberá moitas instancias de consumidores que comparten o mesmo grupo de consumidores.

O problema da lectura pódese representar do seguinte xeito:

  • O tema ten varias particións
  • Varios grupos de consumidores poden usar un tema ao mesmo tempo
  • Un grupo de consumidores pode ter varias instancias separadas

Este é un problema de moitos a moitos non trivial. Para comprender como xestiona Kafka as relacións entre grupos de consumidores, instancias de consumidores e particións, vexamos unha serie de escenarios de lectura progresivamente máis complexos.

Consumidores e grupos de consumidores

Tomemos como punto de partida un tema cunha partición (Imaxe 3-2).

Comprensión dos corredores de mensaxes. Aprende a mecánica da mensaxería con ActiveMQ e Kafka. Capítulo 3. Kafka
Figura 3-2. O consumidor le dende a partición

Cando unha instancia de consumidor se conecta co seu propio group_id a este tema, asígnaselle unha partición de lectura e unha compensación nesa partición. A posición deste desprazamento configúrase no cliente como un punteiro á posición máis recente (mensaxe máis recente) ou posición máis antiga (mensaxe máis antiga). O consumidor solicita (enquisas) mensaxes do tema, o que fai que se lean secuencialmente desde o rexistro.
A posición de compensación envíase regularmente a Kafka e gárdase como mensaxes nun tema interno _compensacións_de_consumidores. As mensaxes lidas aínda non se eliminan, a diferenza dun corredor normal, e o cliente pode rebobinar a compensación para volver procesar as mensaxes xa vistas.

Cando un segundo consumidor lóxico se conecta usando un group_id diferente, xestiona un segundo punteiro que é independente do primeiro (Imaxe 3-3). Así, un tema de Kafka actúa como unha cola onde hai un consumidor e como un tema normal de publicación-subscrición (pub-sub) ao que se subscriben varios consumidores, co beneficio engadido de que todas as mensaxes se almacenan e poden procesarse varias veces.

Comprensión dos corredores de mensaxes. Aprende a mecánica da mensaxería con ActiveMQ e Kafka. Capítulo 3. Kafka
Figura 3-3. Dous consumidores de diferentes grupos de consumidores len dende a mesma partición

Consumidores nun grupo de consumidores

Cando unha instancia de consumidor le datos dunha partición, ten o control total do punteiro e procesa as mensaxes como se describe na sección anterior.
Se varias instancias de consumidores se conectaron co mesmo group_id a un tema cunha partición, entón a última instancia que se conectou terá control sobre o punteiro e a partir dese momento recibirá todas as mensaxes (Imaxe 3-4).

Comprensión dos corredores de mensaxes. Aprende a mecánica da mensaxería con ActiveMQ e Kafka. Capítulo 3. Kafka
Figura 3-4. Dous consumidores do mesmo grupo de consumidores len dende a mesma partición

Este modo de procesamento, no que o número de instancias do consumidor supera o número de particións, pódese pensar como unha especie de consumidor exclusivo. Isto pode ser útil se necesitas agrupación "activo-pasivo" (ou "quente-quente") das túas instancias de consumidor, aínda que executar varios consumidores en paralelo ("activo-activo" ou "quente-quente") é moito máis típico que consumidores.En espera.

Este comportamento de distribución de mensaxes descrito anteriormente pode ser sorprendente en comparación co comportamento dunha cola JMS normal. Neste modelo, as mensaxes enviadas á cola repartiranse uniformemente entre os dous consumidores.

Na maioría das veces, cando creamos varias instancias de consumidores, facémolo tanto para procesar mensaxes en paralelo, como para aumentar a velocidade de lectura ou para aumentar a estabilidade do proceso de lectura. Dado que só unha instancia de consumidor pode ler os datos dunha partición á vez, como se consegue isto en Kafka?

Unha forma de facelo é usar unha única instancia de consumidor para ler todas as mensaxes e pasalas ao grupo de fíos. Aínda que este enfoque aumenta o rendemento de procesamento, aumenta a complexidade da lóxica do consumidor e non fai nada para aumentar a robustez do sistema de lectura. Se unha copia do consumidor cae debido a un fallo de alimentación ou un evento similar, a resta detense.

A forma canónica de resolver este problema en Kafka é usar bОmáis particións.

Partición

As particións son o mecanismo principal para paralelizar a lectura e escalar un tema máis aló do ancho de banda dunha única instancia de intermediario. Para entendelo mellor, consideremos unha situación na que hai un tema con dúas particións e un consumidor subscríbese a este tema (Imaxe 3-5).

Comprensión dos corredores de mensaxes. Aprende a mecánica da mensaxería con ActiveMQ e Kafka. Capítulo 3. Kafka
Figura 3-5. Un consumidor le dende varias particións

Neste escenario, o consumidor recibe o control dos punteiros correspondentes ao seu group_id en ambas as particións e comeza a ler as mensaxes de ambas particións.
Cando se engade un consumidor adicional para o mesmo group_id a este tema, Kafka reasigna unha das particións do primeiro ao segundo consumidor. Despois diso, cada instancia do consumidor lerá desde unha partición do tema (Imaxe 3-6).

Para garantir que as mensaxes se procesan en paralelo en 20 fíos, necesitas polo menos 20 particións. Se hai menos particións, quedará con consumidores que non teñen nada para traballar, como se describiu anteriormente na discusión dos consumidores exclusivos.

Comprensión dos corredores de mensaxes. Aprende a mecánica da mensaxería con ActiveMQ e Kafka. Capítulo 3. Kafka
Figura 3-6. Dous consumidores do mesmo grupo de consumidores len desde diferentes particións

Este esquema reduce moito a complexidade do intermediario de Kafka en comparación coa distribución de mensaxes necesaria para manter a cola JMS. Aquí non tes que preocuparte polos seguintes puntos:

  • Que consumidor debe recibir a seguinte mensaxe, en función da asignación de round-robin, da capacidade actual dos búfers de captación previa ou das mensaxes anteriores (como para os grupos de mensaxes JMS).
  • Que mensaxes se envían a que consumidores e se deben ser entregadas de novo en caso de falla.

O único que ten que facer o corredor de Kafka é pasar mensaxes secuencialmente ao consumidor cando este as solicite.

Non obstante, os requisitos para paralelizar a corrección de probas e o reenvío de mensaxes erradas non desaparecen: a responsabilidade delas simplemente pasa do corredor ao cliente. Isto significa que deben terse en conta no teu código.

Enviando mensaxes

É responsabilidade do produtor desa mensaxe decidir a que partición enviar unha mensaxe. Para comprender o mecanismo polo cal se fai isto, primeiro necesitamos considerar o que realmente estamos enviando.

Mentres que en JMS usamos unha estrutura de mensaxes con metadatos (encabezados e propiedades) e un corpo que contén a carga útil (carga útil), en Kafka a mensaxe é par "clave-valor". A carga útil da mensaxe envíase como un valor. A chave, pola contra, úsase principalmente para particionar e debe conter clave específica da lóxica empresarialpara poñer mensaxes relacionadas na mesma partición.

No capítulo 2, discutimos o escenario de apostas en liña onde os eventos relacionados deben ser procesados ​​por un único consumidor:

  1. A conta de usuario está configurada.
  2. O diñeiro está acreditado na conta.
  3. Faise unha aposta que retira diñeiro da conta.

Se cada evento é unha mensaxe publicada nun tema, entón a clave natural sería o ID da conta.
Cando se envía unha mensaxe mediante a API de Kafka Producer, pásase a unha función de partición que, dada a mensaxe e o estado actual do clúster de Kafka, devolve o ID da partición á que se debe enviar a mensaxe. Esta función está implementada en Java a través da interface Partitioner.

Esta interface ten o seguinte aspecto:

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

A implementación de Partitioner usa o algoritmo de hash de propósito xeral predeterminado sobre a clave para determinar a partición, ou round-robin se non se especifica ningunha chave. Este valor predeterminado funciona ben na maioría dos casos. Non obstante, no futuro quererás escribir o teu.

Escribir a súa propia estratexia de partición

Vexamos un exemplo onde queres enviar metadatos xunto coa carga útil da mensaxe. A carga útil do noso exemplo é unha instrución para facer un depósito na conta do xogo. Unha instrución é algo que nos gustaría garantir que non se modificase na transmisión e queremos estar seguros de que só un sistema de confianza pode iniciar esa instrución. Neste caso, os sistemas de envío e recepción acordan o uso dunha sinatura para autenticar a mensaxe.
En JMS normal, simplemente definimos unha propiedade de "sinatura de mensaxe" e engadímola á mensaxe. Non obstante, Kafka non nos proporciona un mecanismo para pasar metadatos, só unha clave e un valor.

Dado que o valor é unha carga útil de transferencia bancaria cuxa integridade queremos preservar, non nos queda máis remedio que definir a estrutura de datos a usar na clave. Asumindo que necesitamos un ID de conta para particionar, xa que todas as mensaxes relacionadas cunha conta deben procesarse en orde, presentaremos a seguinte estrutura JSON:

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

Dado que o valor da sinatura variará dependendo da carga útil, a estratexia de hash predeterminada da interface do Partitioner non agrupará de forma fiable as mensaxes relacionadas. Polo tanto, necesitaremos escribir a nosa propia estratexia que analizará esta chave e particione o valor accountId.

Kafka inclúe sumas de verificación para detectar a corrupción das mensaxes na tenda e ten un conxunto completo de funcións de seguridade. Aínda así, ás veces aparecen requisitos específicos do sector, como o anterior.

A estratexia de partición do usuario debe garantir que todas as mensaxes relacionadas acaban na mesma partición. Aínda que isto pareza sinxelo, o requisito pode complicarse pola importancia de ordenar as publicacións relacionadas e polo fixo que é o número de particións dun tema.

O número de particións dun tema pode cambiar co paso do tempo, xa que se poden engadir se o tráfico supera as expectativas iniciais. Así, as claves de mensaxe pódense asociar á partición á que foron enviadas orixinalmente, o que implica que se compartirá unha parte de estado entre as instancias do produtor.

Outro factor a considerar é a distribución uniforme das mensaxes entre particións. Normalmente, as claves non se distribúen uniformemente entre as mensaxes e as funcións hash non garanten unha distribución xusta das mensaxes para un pequeno conxunto de claves.
É importante ter en conta que, de calquera xeito que elixas dividir as mensaxes, é posible que o propio separador teña que ser reutilizado.

Considere o requisito de replicar datos entre clústeres de Kafka en diferentes localizacións xeográficas. Para este fin, Kafka inclúe unha ferramenta de liña de comandos chamada MirrorMaker, que se usa para ler mensaxes dun clúster e transferilos a outro.

MirrorMaker debe comprender as claves do tema replicado para manter a orde relativa entre mensaxes ao replicar entre clústeres, xa que o número de particións para ese tema pode non ser o mesmo en dous clústeres.

As estratexias de partición personalizadas son relativamente raras, xa que o hash predeterminado ou o round robin funcionan ben na maioría dos escenarios. Non obstante, se precisas fortes garantías de pedido ou necesitas extraer metadatos das cargas útiles, a partición é algo que deberías analizar máis de cerca.

Os beneficios de escalabilidade e rendemento de Kafka veñen de trasladar algunhas das responsabilidades do corredor tradicional ao cliente. Neste caso, tómase a decisión de distribuír mensaxes potencialmente relacionadas entre varios consumidores que traballan en paralelo.

Os corredores de JMS tamén deben xestionar tales requisitos. Curiosamente, o mecanismo para enviar mensaxes relacionadas ao mesmo consumidor, implementado a través de JMS Message Groups (unha variación da estratexia de equilibrio de carga pegajosa (SLB)), tamén require que o remitente marque as mensaxes como relacionadas. No caso de JMS, o corredor é o responsable de enviar este grupo de mensaxes relacionadas a un consumidor de entre moitos e de transferir a propiedade do grupo se o consumidor cae.

Acordos de produtores

A partición non é o único que hai que ter en conta ao enviar mensaxes. Vexamos os métodos send() da clase Producer na API de Java:

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

Debe notarse inmediatamente que ambos métodos devolven Futuro, o que indica que a operación de envío non se realiza inmediatamente. O resultado é que se escribe unha mensaxe (ProducerRecord) no búfer de envío para cada partición activa e se envía ao corredor como un fío de fondo na biblioteca do cliente de Kafka. Aínda que isto fai que as cousas sexan incriblemente rápidas, significa que unha aplicación sen experiencia pode perder mensaxes se o seu proceso se detén.

Como sempre, hai unha forma de facer que a operación de envío sexa máis fiable a costa do rendemento. O tamaño deste búfer pódese establecer en 0, e o fío da aplicación de envío verase obrigado a esperar ata que se complete a transferencia de mensaxes ao corredor, como segue:

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

Máis información sobre a lectura de mensaxes

A lectura de mensaxes ten complexidades adicionais sobre as que hai que especular. A diferenza da API de JMS, que pode executar un escoitador de mensaxes en resposta a unha mensaxe, o Consumidor Kafka só enquisa. Vexamos máis de cerca o método enquisa ()utilizado para este fin:

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

O valor de retorno do método é unha estrutura de contedores que contén varios obxectos rexistro do consumidor de varias particións potencialmente. rexistro do consumidor é en si mesmo un obxecto titular para un par clave-valor con metadatos asociados, como a partición da que se deriva.

Como comentamos no capítulo 2, debemos ter en conta o que ocorre coas mensaxes despois de que se procesaron correctamente ou sen éxito, por exemplo, se o cliente non pode procesar a mensaxe ou se aborta. En JMS, isto foi xestionado a través dun modo de recoñecemento. O corredor eliminará a mensaxe procesada con éxito ou volverá a entregar a mensaxe en bruto ou falsa (supoñendo que se utilizaron transaccións).
Kafka funciona de forma moi diferente. As mensaxes non se eliminan no corredor despois da corrección, e o que ocorre en caso de falla é responsabilidade do propio código de corrección.

Como dixemos, o grupo de consumidores está asociado co desplazamento no rexistro. A posición do rexistro asociada a esta compensación corresponde á seguinte mensaxe que se emitirá en resposta enquisa (). O momento no que aumenta esta compensación é decisivo para a lectura.

Volvendo ao modelo de lectura comentado anteriormente, o procesamento da mensaxe consta de tres etapas:

  1. Recuperar unha mensaxe para ler.
  2. Procesar a mensaxe.
  3. Confirmar mensaxe.

O consumidor de Kafka vén cunha opción de configuración habilitar.auto.commit. Esta é unha configuración predeterminada de uso frecuente, como é común coas opcións que conteñen a palabra "automático".

Antes de Kafka 0.10, un cliente que usaba esta opción enviaría a compensación da última mensaxe lida na seguinte chamada enquisa () despois do procesamento. Isto significaba que as mensaxes que xa foran recuperadas poderían ser reprocesadas se o cliente xa as procesara pero se destruíse inesperadamente antes de chamar. enquisa (). Como o corredor non mantén ningún estado sobre cantas veces se leu unha mensaxe, o próximo consumidor que recupere esa mensaxe non saberá que pasou nada malo. Este comportamento foi pseudo-transaccional. A compensación só se comprometeu se a mensaxe foi procesada con éxito, pero se o cliente abortaba, o corredor enviaría a mesma mensaxe de novo a outro cliente. Este comportamento era coherente coa garantía de entrega de mensaxes "polo menos unha vez«.

En Kafka 0.10, o código do cliente foi modificado para que a biblioteca do cliente desencadee periodicamente a confirmación, tal e como está configurado. auto.commit.interval.ms. Este comportamento está nalgún lugar entre os modos JMS AUTO_ACKNOWLEDGE e DUPS_OK_ACKNOWLEDGE. Ao usar a confirmación automática, as mensaxes poderían confirmarse independentemente de que se procesaron realmente; isto podería ocorrer no caso dun consumidor lento. Se un consumidor abortaba, as mensaxes serían recuperadas polo seguinte consumidor, comezando na posición comprometida, o que podería producir unha mensaxe perdida. Neste caso, Kafka non perdeu as mensaxes, o código de lectura simplemente non as procesou.

Este modo ten a mesma promesa que na versión 0.9: pódense procesar as mensaxes, pero se falla, é posible que non se comprometa a compensación, o que pode provocar que a entrega se duplique. Cantas máis mensaxes obtén ao executar enquisa (), canto máis este problema.

Como se comenta en “Lectura de mensaxes desde unha cola” na páxina 21, non existe unha única entrega dunha mensaxe nun sistema de mensaxería cando se teñen en conta os modos de falla.

En Kafka, hai dúas formas de confirmar (commit) unha compensación (offset): automaticamente e manualmente. En ambos os casos, as mensaxes pódense procesar varias veces se a mensaxe foi procesada pero fallou antes da confirmación. Tamén podes optar por non procesar a mensaxe en absoluto se a confirmación ocorreu en segundo plano e o teu código completouse antes de que se puidese procesar (quizais en Kafka 0.9 e anteriores).

Podes controlar o proceso de commit de compensación manual na API de consumidores de Kafka configurando o parámetro habilitar.auto.commit a false e chamando explícitamente a un dos seguintes métodos:

void commitSync();
void commitAsync();

Se queres procesar a mensaxe "polo menos unha vez", debes confirmar a compensación manualmente commitSync()executando este comando inmediatamente despois de procesar as mensaxes.

Estes métodos non permiten que as mensaxes sexan recoñecidas antes de ser procesadas, pero non fan nada para eliminar posibles atrasos de procesamento ao tempo que dan a impresión de ser transaccionais. Non hai transaccións en Kafka. O cliente non ten a capacidade de facer o seguinte:

  • Revertir automaticamente unha mensaxe falsa. Os propios consumidores deben xestionar as excepcións derivadas de cargas útiles problemáticas e interrupcións do back-end, xa que non poden confiar no corredor para volver entregar as mensaxes.
  • Enviar mensaxes a varios temas nunha operación atómica. Como veremos en breve, o control sobre diferentes temas e particións pode residir en diferentes máquinas do clúster de Kafka que non coordinan transaccións cando se envían. No momento de escribir este documento, xa se traballou para facelo posible co KIP-98.
  • Asocia a lectura dunha mensaxe dun tema co envío doutra mensaxe a outro tema. De novo, a arquitectura de Kafka depende de que moitas máquinas independentes funcionen como un só bus e non se intenta ocultar isto. Por exemplo, non hai compoñentes da API que che permitan vincular consumidor и Produtor nunha transacción. En JMS, isto é proporcionado polo obxecto sesióna partir das que se crean Produtores de mensaxes и MensaxeConsumidores.

Se non podemos confiar nas transaccións, como podemos proporcionar unha semántica máis próxima á proporcionada polos sistemas de mensaxería tradicionais?

Se existe a posibilidade de que a compensación do consumidor aumente antes de que se procese a mensaxe, como durante un fallo do consumidor, entón o consumidor non ten forma de saber se o seu grupo de consumidores perdeu a mensaxe cando se lle asigna unha partición. Polo tanto, unha estratexia é rebobinar a compensación á posición anterior. A API de consumo de Kafka ofrece os seguintes métodos para iso:

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

Método buscar () pódese usar co método
offsetsForTimes (Mapa timestampsToSearch) para rebobinar a un estado nalgún momento específico do pasado.

Implícitamente, usar este enfoque significa que é moi probable que algunhas mensaxes que foron procesadas previamente sexan lidas e procesadas de novo. Para evitar isto, podemos utilizar a lectura idempotente, como se describe no capítulo 4, para facer un seguimento das mensaxes vistas previamente e eliminar as duplicadas.

Alternativamente, o seu código de consumidor pódese manter sinxelo, sempre que se permita a perda ou a duplicación de mensaxes. Cando analizamos casos de uso para os que se usa habitualmente Kafka, como o manexo de eventos de rexistro, métricas, seguimento de clics, etc., decatámonos de que é improbable que a perda de mensaxes individuais teña un impacto significativo nas aplicacións circundantes. Nestes casos, os valores predeterminados son perfectamente aceptables. Por outra banda, se a túa aplicación precisa enviar pagos, debes coidar coidadosamente cada mensaxe individual. Todo se reduce ao contexto.

As observacións persoais mostran que a medida que aumenta a intensidade das mensaxes, o valor de cada mensaxe individual diminúe. As mensaxes grandes adoitan ser valiosas cando se ven nunha forma agregada.

Alta Dispoñibilidade

O enfoque de Kafka para a alta dispoñibilidade é moi diferente do enfoque de ActiveMQ. Kafka está deseñado en torno a clústeres escalables onde todas as instancias do corredor reciben e distribúen mensaxes ao mesmo tempo.

Un clúster de Kafka consta de varias instancias de corredor que se executan en diferentes servidores. Kafka foi deseñado para funcionar en hardware autónomo común, onde cada nodo ten o seu propio almacenamento dedicado. Non se recomenda o uso de almacenamento conectado á rede (SAN) porque varios nodos de cálculo poden competir polo tempo.Ыe intervalos de almacenamento e crear conflitos.

Kafka é sempre aceso sistema. Moitos grandes usuarios de Kafka nunca pechan os seus clústeres e o software sempre se actualiza cun reinicio secuencial. Isto conséguese garantindo a compatibilidade coa versión anterior para as mensaxes e as interaccións entre corredores.

Corretores conectados a un clúster de servidores ZooKeeper, que actúa como un rexistro de datos de configuración e úsase para coordinar os roles de cada corredor. O propio ZooKeeper é un sistema distribuído que ofrece alta dispoñibilidade mediante a replicación da información mediante o establecemento quórum.

No caso base, créase un tema nun clúster de Kafka coas seguintes propiedades:

  • O número de particións. Como se comentou anteriormente, o valor exacto usado aquí depende do nivel desexado de lectura paralela.
  • O factor de replicación (factor) determina cantas instancias de corretor no clúster deben conter rexistros para esta partición.

Usando ZooKeepers para a coordinación, Kafka intenta distribuír de forma xusta novas particións entre os corredores do clúster. Isto faise por unha única instancia que actúa como controlador.

En tempo de execución para cada partición de tema Controlador asignar roles a un corredor líder (líder, mestre, presentador) e seguidores (seguidores, escravos, subordinados). O corredor, actuando como líder desta partición, é o responsable de recibir todas as mensaxes que lle envían os produtores e de distribuír as mensaxes aos consumidores. Cando as mensaxes se envían a unha partición de tema, replícanse a todos os nodos intermediarios que actúan como seguidores desa partición. Chámase cada nodo que contén rexistros dunha partición réplica. Un corredor pode actuar como líder para algunhas particións e como seguidor para outras.

Chámase un seguidor que contén todas as mensaxes que ten o líder réplica sincronizada (unha réplica que está nun estado sincronizado, réplica sincronizada). Se cae un corredor que actúa como líder dunha partición, calquera corredor que estea actualizado ou sincronizado para esa partición pode asumir o papel de líder. É un deseño incriblemente sostible.

Parte da configuración do produtor é o parámetro acos, que determina cantas réplicas deben confirmar (acusar) a recepción dunha mensaxe antes de que o fío da aplicación continúe enviando: 0, 1 ou todas. Se se establece en todo, a continuación, cando se recibe unha mensaxe, o líder enviará unha confirmación ao produtor en canto reciba confirmacións (recoñecementos) do rexistro de varias pistas (incluíndo a si mesmo) definidas pola configuración do tema. min.insync.réplicas (predeterminado 1). Se a mensaxe non se pode replicar con éxito, entón o produtor lanzará unha excepción da aplicación (Non EnoughReplicas ou Not EnoughReplicasAfterAppend).

Unha configuración típica crea un tema cun factor de replicación de 3 (1 líder, 2 seguidores por partición) e o parámetro min.insync.réplicas está definido como 2. Neste caso, o clúster permitirá que un dos corredores que xestionan a partición do tema baixe sen afectar ás aplicacións cliente.

Isto devólvenos ao xa familiar compromiso entre rendemento e fiabilidade. A replicación prodúcese a costa dun tempo de espera adicional para as confirmacións (recoñecementos) dos seguidores. Aínda que, debido a que se executa en paralelo, a replicación en polo menos tres nodos ten o mesmo rendemento que dous (ignorando o aumento do uso do ancho de banda da rede).

Ao usar este esquema de replicación, Kafka evita intelixentemente a necesidade de escribir fisicamente cada mensaxe no disco coa operación sincronizar(). Cada mensaxe enviada polo produtor escribirase no rexistro da partición, pero como se comenta no capítulo 2, a escritura nun ficheiro realízase inicialmente no búfer do sistema operativo. Se esta mensaxe replícase a outra instancia de Kafka e está na súa memoria, a perda do líder non significa que a mensaxe en si se perdeu; pode ser tomada por unha réplica sincronizada.
Negativa a realizar a operación sincronizar() significa que Kafka pode recibir mensaxes tan rápido como pode escribilas na memoria. Pola contra, canto máis tempo poida evitar o lavado de memoria no disco, mellor. Por este motivo, non é raro que os corredores de Kafka teñan asignados 64 GB ou máis de memoria. Este uso da memoria significa que unha única instancia de Kafka pode executarse facilmente a velocidades miles de veces máis rápidas que un corrector de mensaxes tradicional.

Kafka tamén se pode configurar para aplicar a operación sincronizar() aos paquetes de mensaxes. Xa que todo en Kafka está orientado a paquetes, en realidade funciona bastante ben para moitos casos de uso e é unha ferramenta útil para os usuarios que requiren garantías moi fortes. Gran parte do rendemento puro de Kafka provén das mensaxes que se envían ao corredor como paquetes e que estas mensaxes se len desde o corredor en bloques secuenciais usando copia cero operacións (operacións durante as que non se realiza a tarefa de copiar datos dunha área de memoria a outra). Este último é un gran rendemento e ganancia de recursos e só é posible mediante o uso dunha estrutura de datos de rexistro subxacente que define o esquema de partición.

É posible un rendemento moito mellor nun clúster de Kafka que cun único corredor de Kafka, porque as particións de temas poden escalar en moitas máquinas separadas.

Resultados de

Neste capítulo, analizamos como a arquitectura de Kafka reimaxina a relación entre clientes e corredores para proporcionar unha canalización de mensaxes incriblemente robusta, cun rendemento moitas veces maior que o dun corredor de mensaxes convencional. Discutimos a funcionalidade que utiliza para conseguilo e analizamos brevemente a arquitectura das aplicacións que proporcionan esta funcionalidade. No seguinte capítulo, analizaremos os problemas comúns que as aplicacións baseadas en mensaxería necesitan para resolver e discutiremos estratexias para tratalos. Remataremos o capítulo describindo como falar sobre tecnoloxías de mensaxería en xeral para que poidas avaliar a súa idoneidade para os teus casos de uso.

Parte traducida anterior: Comprensión dos corredores de mensaxes. Aprende a mecánica da mensaxería con ActiveMQ e Kafka. Capítulo 1

Tradución feita: tele.gg/middle_java

Continuar ...

Só os usuarios rexistrados poden participar na enquisa. Rexístrate, por favor.

Utilízase Kafka na súa organización?

  • Si

  • Non

  • Usado anteriormente, agora non

  • Pensamos usar

Votaron 38 usuarios. 8 usuarios abstivéronse.

Fonte: www.habr.com

Engadir un comentario