tópicos de Kafka com fluxos de Kafka
nas arquiteturas de processamento de dados de hoje, o Apache Kafka é frequentemente usado na fase de entrada. Normalmente, este passo é usado para enriquecer e filtrar os dados da mensagem recebida; no entanto, não é possível fazer consultas interativas até uma fase posterior no pipeline de processamento de dados. Isso ocorre porque, embora cada mensagem em um tópico Kafka é persistida por padrão, nenhum mecanismo ainda está disponível que permite uma rápida pesquisa de uma mensagem específica em um tópico.
no entanto, poder consultar novos dados nesta fase inicial do gasoduto evitaria os atrasos dos gasodutos de processamento tradicionais que normalmente incluem fases de pré-processamento de lotes de longa duração e daria aos utilizadores finais acesso quase instantâneo aos dados recebidos.
para construir aplicações de processamento de dados com Kafka, a biblioteca de fluxos de Kafka, que é mantida como parte do projeto Kafka, é comumente usada para definir transformações e análises de dados. Uma característica importante dos fluxos de Kafka são as lojas de Estado, oferecendo uma abstração de uma rápida Loja de valor-Chave local que pode ser lida e escrita para quando processar mensagens com fluxos de Kafka. Estas lojas de valores-chave podem ser continuamente preenchidas com novas mensagens de um tópico Kafka, definindo um processador de fluxo apropriado, de modo que agora é possível recuperar rapidamente mensagens do tópico subjacente.
Building on top of this Kafka Streams functionality, we create a unified REST API that provides a single querying endpoint for a given Kafka topic.
em resumo, combinando processadores Kafka com lojas de estado e um Servidor HTTP pode efetivamente transformar qualquer tópico do Kafka em uma loja de valores chave de leitura rápida.
Kafka Streams é construída como uma biblioteca que pode ser incorporada em uma aplicação Java ou Scala. Ele permite aos desenvolvedores definir processadores de fluxo que executam transformações ou agregações de dados em mensagens Kafka, garantindo que cada mensagem de entrada é processada exatamente uma vez. Usando o Kafka Streams DSL, que é inspirado pela API Java Stream, processadores stream e lojas state podem ser flexivelmente acorrentados.
além disso, ao iniciar várias instâncias de uma aplicação baseada em fluxos Kafka, os processos formam automaticamente um cluster de processamento de balanceamento de carga, altamente disponível sem depender de sistemas externos que não o Kafka.para ilustrar a arquitetura de uma aplicação Kafka Streams que emprega lojas do estado, imagine o seguinte cenário: como operador ferroviário, sempre que um cliente marca uma viagem no nosso site, uma nova mensagem que consiste no ID do cliente e um timestamp é inserida num tópico do Kafka. Especificamente, um ou mais produtores de Kafka inserir estas mensagens em um tópico com nove partições. Como o ID do cliente é escolhido como a chave para cada mensagem, os dados pertencentes a um determinado cliente serão sempre inseridos na mesma partição do tópico. Implicitamente, os produtores de Kafka utilizam o DefaultPartitioner
para atribuir mensagens a partições.
agora, suponha que temos uma aplicação Kafka Streams que lê mensagens deste tópico e as persiste em uma loja do estado. Uma vez que a chave de cada mensagem consiste apenas no ID do cliente, o valor correspondente na loja do estado será sempre o timestamp da última reserva do cliente. A fim de alcançar o máximo grau de processamento paralelo, podemos iniciar até nove instâncias da aplicação Kafka Streams. Neste caso, cada instância de aplicação será atribuída exatamente uma das partições do tópico. Para cada partição de entrada, os fluxos de Kafka criam uma loja de Estado separada, que por sua vez só detém os dados dos clientes pertencentes a essa partição.
a arquitetura de Aplicação resultante é ilustrada no diagrama abaixo.
é possível usar em memória ou reservas de Estado persistentes na aplicação. As operações em lojas de Estado de memória são ainda mais rápidas em comparação com a variante persistente, que internamente usa uma loja de RocksDB. Por outro lado, as lojas de Estado persistentes podem ser restauradas mais rapidamente no caso de uma aplicação Kafka Streams ter falhado e precisar reiniciar. Além disso, o volume de dados por Loja não é limitado pela quantidade de memória principal ao usar stores de estado persistente.
no nosso cenário, não é necessário ter um tópico changelog que registre operações de escrita para as reservas do Estado: Todos os dados necessários para recuperar uma loja do estado podem ser obtidos a partir do tópico de entrada original.
adicionando um ponto final de descanso aos processadores de fluxo
com a arquitetura apresentada até agora, temos nove lojas estaduais que podem ser usadas para recuperar a última data de reserva dos clientes pertencentes à respectiva partição tópico de entrada.
Agora, a fim de tornar esta informação acessível a partir de fora dos processadores Kafka Streams, precisamos expor um ponto final de serviço em cada uma das instâncias de aplicação do processador stream e responder aos pedidos de entrada da loja de estado interno que é gerenciado pelos Streams Kafka.
Como um requisito adicional, não podemos esperar que o pedido de aplicação para saber qual instância Kafka Streams é atualmente responsável pelo processamento dos dados de um determinado cliente. Consequentemente, cada endpoint de serviço é responsável por redirecionar a consulta para a instância de aplicação correta se os dados do cliente não estão disponíveis localmente.
escolhemos implementar o endpoint de serviço como uma API de descanso, que tem o benefício de ser acessível a partir de qualquer cliente que suporta HTTP, e permite adicionar em um balancer de carga transparente muito facilmente.
o KafkaStreams
objecto, que está disponível em todas as aplicações Kafka Streams, fornece acesso apenas para leitura a todas as lojas do estado local e também pode determinar a instância de Aplicação responsável por um dado ID de cliente. Quando utilizar este objecto para construir o nosso serviço REST, a arquitetura de procura da seguinte forma:
resumindo, a nossa arquitectura proposta faz uso de tópicos do Kafka para armazenar de forma fiável os dados das mensagens em repouso e mantém uma segunda representação dos dados nas lojas do Estado para suportar consultas rápidas.
garantindo a escalabilidade da aplicação
para obter uma aplicação escalável, precisamos garantir que a carga de processamento seja igualmente balanceada em todas as instâncias da aplicação de fluxos de Kafka. A carga de um processador de fluxo individual depende da quantidade de dados e consultas que ele tem que lidar.
mais especificamente, é importante escolher um esquema de particionamento para o tópico Kafka de modo que a carga de mensagens e consultas recebidas seja bem equilibrada em todas as partições e, consequentemente, também em todos os processadores de fluxo.
Se as reservas no estado de memória devem ser usadas, o número de partições no tópico deve ser grande o suficiente para que cada processador de fluxo seja capaz de manter o volume de dados de uma partição na memória principal. Note que em caso de falha, um processador de fluxo pode até precisar de manter duas partições na memória principal.
para contabilizar as falhas do processador de fluxo, o número de réplicas de espera pode ser configurado usando a configuração num.standby.replicas
nos fluxos de Kafka, o que garante que processadores de fluxo adicionais também assinam mensagens de uma dada partição do processador. Em caso de falha, esses processadores podem assumir rapidamente as consultas de resposta, em vez de começar a reconstruir a loja do estado apenas depois de uma falha já ocorreu.
finalmente, o número desejado de processadores stream tem que se ajustar ao hardware disponível. Para cada instância de aplicação de processadores stream, deve ser reservado pelo menos um núcleo de CPU. Em sistemas multicores, é possível aumentar o número de threads de fluxo por instância de aplicação, o que reduz as despesas incorridas iniciando uma aplicação Java separada por núcleo de CPU.