Melhores Práticas para Escalar Cargas de Trabalho Baseadas em Kafka

O Apache Kafka é conhecido por sua capacidade de processar uma grande quantidade de eventos em tempo real. No entanto, para lidar com milhões de eventos, precisamos seguir certas melhores práticas ao implementar tanto os serviços produtores quanto os consumidores do Kafka.

Antes de começara usar o Kafka em seus projetos, vamos entender quando usar o Kafka:

  • Fluxos de eventos em alta volume. Quando sua aplicação/serviço gera um fluxo contínuo de eventos como eventos de atividade do usuário, eventos de cliques em sites, eventos de dados de sensores, eventos de registro ou atualizações do mercado de ações, a capacidade do Kafka de lidar com grandes volumes com baixa latência é muito útil.
  • Análises em tempo real. O Kafka é especialmente realmente útil na construção de pipelines de processamento de dados em tempo real, onde os dados precisam ser processados assim que chegam. Isso permite transmitir dados para mecanismos de análise como Kafka streams, Apache Spark ou Flink para análises/insights imediatos e processamento em stream ou em lote.
  • Desacoplamento de aplicações. Ao atuar como um hub central de mensagens, ele pode desacoplar diferentes partes de uma aplicação, permitindo o desenvolvimento e escalonamento independentes de serviços e incentivando o princípio de segregação responsável.
  • Integração de dados entre sistemas. Ao integrar sistemas distribuídos, o Kafka pode transferir eficientemente dados entre diferentes aplicações em equipes/projetos distintos, atuando como um corretor de dados confiável.

Diferenças-chave de outros sistemas de filas

Abaixo estão as diferenças do Apache Kafka em relação a sistemas como ActiveMQ, ZeroMQ e VerneMQ:

Armazenamento Persistente

O Kafka armazena eventos em um log distribuído, permitindo a capacidade de reproduzir dados a qualquer momento e garantindo a persistência de dados mesmo em caso de falhas de sistema/nó, ao contrário de algumas filas de mensagens tradicionais, que podem depender de armazenamento em memória como o Redis.

Particionamento

Os dados são particionados entre brokers/tópicos, possibilitando o processamento paralelo de grandes fluxos de dados e alta taxa de transferência. Isso ajuda as threads consumidoras a se conectarem a partições individuais, promovendo escalabilidade horizontal.

Grupos de Consumidores

Múltiplos consumidores podem se inscrever no mesmo tópico e ler de offsets diferentes dentro de uma partição, permitindo padrões de consumo duplicados para diferentes equipes consumirem e processarem os mesmos dados para diferentes finalidades. Alguns exemplos são:

  • Atividade do usuário consumida por equipes de ML para detectar atividades suspeitas
  • Equipe de recomendação para construir recomendações
  • Equipe de anúncios para gerar anúncios relevantes

Práticas recomendadas do produtor Kafka

Tamanho do lote e tempo de espera

Ao configurar tamanho_do_lote e linger.ms, você pode aumentar o throughput do seu produtor Kafka. tamanho_do_lote é o tamanho máximo do lote em bytes. O Kafka tentará agrupá-lo antes de enviá-lo aos produtores.

Linger.ms determina o tempo máximo em milissegundos que o produtor aguardará a adição de mensagens adicionais ao lote para processamento.

A configuração das definições de tamanho_do_lote e linger.ms ajuda significativamente no desempenho do sistema, controlando a quantidade de dados acumulados antes de enviá-los para os sistemas de processamento, permitindo melhor throughput e latências reduzidas ao lidar com grandes volumes de dados. Isso também pode introduzir ligeiros atrasos, dependendo dos valores escolhidos. Especialmente, um tamanho de lote grande com um linger.ms correto pode otimizar a eficiência da transferência de dados.

Compressão

Outra maneira de aumentar o throughput é habilitar a compressão através da configuração compression.type. O produtor pode comprimir os dados com gzip, snappy ou lz4 antes de enviá-los para os brokers. Para grandes volumes de dados, essa configuração ajuda na eficiência da compressão com eficiência de rede. Também economiza largura de banda e aumenta o throughput do sistema. Além disso, ao definir o serializador apropriado e o serializador de chave, podemos garantir que os dados sejam serializados em um formato compatível com seus consumidores.

Retentativas e Idempotência

Para garantir a confiabilidade do produtor Kafka, você deve habilitar as retentativas e a idempotência. Ao configurar retries, o produtor pode reenviar automaticamente qualquer lote de dados que não receber um ack do broker dentro de um número especificado de tentativas.

Acknowledgments

Esta configuração controla o nível de reconhecimento necessário do broker antes de considerar uma mensagem enviada com sucesso. Ao escolher o nível correto de acks, você pode controlar a confiabilidade de sua aplicação. Abaixo estão os valores aceitos para esta configuração.

  • 0 – o mais rápido, mas sem garantia de entrega da mensagem.
  • 1 – a mensagem é reconhecida assim que é gravada no líder do broker, fornecendo confiabilidade básica.
  • all – a mensagem é considerada entregue apenas quando todas as réplicas a reconhecem, garantindo alta durabilidade.

Ajuste de Configuração com Base na Carga de Trabalho

você deve começar a rastrear métricas como taxa de envio de mensagens, tamanho do lote e taxas de erro para identificar gargalos de desempenho. Verifique e ajuste regularmente as configurações do produtor com base nas modificações ou atualizações de recursos/dados.

Melhores Práticas do Consumidor Kafka

Grupos de Consumidores

Cada consumidor Kafka deve pertencer a um grupo de consumidores; um grupo de consumidores pode conter um ou mais consumidores. Ao criar mais consumidores no grupo, você pode escalar para ler de todas as partições, permitindo processar um enorme volume de dados. A configuração group.id ajuda a identificar o grupo de consumidores ao qual o consumidor pertence, permitindo o balanceamento de carga entre vários consumidores que consomem do mesmo tópico. A melhor prática é usar IDs de grupo significativos para identificar facilmente grupos de consumidores dentro da sua aplicação.

Comprometendo Offsets

Você pode controlar quando sua aplicação compromete offsets, o que pode ajudar a evitar a perda de dados. Existem duas maneiras de comprometer offsets: automática e manual. Para aplicações de alto rendimento, você deve considerar o compromisso manual para melhor controle.

  • auto.offset.reset – define o que fazer quando um consumidor começa a consumir um tópico sem offsets comprometidos (por exemplo, um novo tópico ou um consumidor ingressando em um grupo pela primeira vez). As opções incluem earliest (ler desde o início), latest (ler desde o final) ou none (lançar um erro). Escolha “earliest” para a maioria dos casos de uso para evitar a perda de dados quando um novo consumidor ingressa em um grupo.Controla como um consumidor começa a consumir dados, garantindo o comportamento adequado quando um consumidor é reiniciado ou adicionado a um grupo.
  • enable.auto.commitajuda a configurar para confirmar automaticamente os offsets periodicamente. Geralmente, definimos o valor como false para a maioria dos cenários de produção em que não precisamos de alta confiabilidade e confirmamos manualmente os offsets dentro da lógica de aplicativo para garantir um processamento exato uma vez.Fornece controle para gerenciar confirmações de offsets, permitindo mais controle sobre o processamento de dados.
  • auto.commit.interval.ms interval em milissegundos no qual os deslocamentos são automaticamente confirmados se enable.auto.commit estiver definido como true. Modifique com base no tempo de processamento do seu aplicativo para evitar perda de dados devido a falhas inesperadas.

Tamanho do Fetch e Máximo de Registros de Poll

Esta configuração ajuda a controlar o número de registros recuperados em cada solicitação, configurando o fetch.min.bytes e max.poll.records. Aumentar esse valor pode ajudar a melhorar o rendimento de suas aplicações, reduzindo o uso da CPU e o número de chamadas feitas aos corretores.

  • fetch.min.bytes – o mínimo de bytes a serem buscados de um corretor em uma única solicitação de poll. Defina um valor pequeno para evitar chamadas de rede desnecessárias, mas não muito pequeno para evitar poluição excessiva. Isso ajuda a otimizar a eficiência da rede ao evitar solicitações pequenas e frequentes.
  • fetch.max.bytes – the maximum número de bytes a serem buscados de um broker em uma única solicitação de polling. Ajuste com base na memória disponível para evitar sobrecarregar os trabalhadores consumidores. Isso reduz a quantidade de dados recuperados em um único polling, evitando problemas de memória.
  • max.poll.interval.ms – o tempo máximo para esperar que uma solicitação de polling retorne dados antes de expirar. Defina um bom tempo limite para evitar travamentos/lentidão do consumidor se os dados não estiverem disponíveis. Isso ajuda a evitar que os consumidores fiquem presos esperando por mensagens por muito tempo. (Às vezes, os pods do k8s podem reiniciar se as sondas de vitalidade forem afetadas).

Atribuição de Partição

Esta é a estratégia usada para atribuir partições (partition.assignment.strategy) para consumidores dentro de um grupo (por exemplo, range, roundrobin). Use range para a maioria dos cenários para distribuir as partições de forma equitativa entre os consumidores. Isso permite uma distribuição equilibrada da carga entre os consumidores em um grupo.

Aqui estão algumas considerações importantes antes de usar o Kafka:

  • Complexidade. Implementar o Kafka requer um entendimento mais profundo de conceitos de sistemas distribuídos como particionamento e gerenciamento de offset, devido às suas características avançadas e configurações.
  • Monitoramento e gerenciamento. Implementar o monitoramento e gerenciamento de clusters do Kafka é importante para garantir alta disponibilidade e desempenho.
  • Segurança. Implementar práticas de segurança robustas para proteger dados sensíveis que fluem pelos tópicos do Kafka também é importante.

Implementar essas melhores práticas pode ajudar a escalar suas aplicações baseadas em Kafka para lidar com milhões/bilhões de eventos. No entanto, é importante lembrar que a configuração ideal pode variar com base nos requisitos específicos de sua aplicação.

Source:
https://dzone.com/articles/best-practices-for-scaling-kafka-based-workloads