Introdução ao KafkaStreams em Java

Introdução ao KafkaStreams em Java

1. Visão geral

Neste artigo, veremosKafkaStreams library.

KafkaStreams é projetado pelos criadores do Apache Kafka. O objetivo principal deste software é permitir que os programadores criem aplicativos de streaming eficientes e em tempo real que possam funcionar como microsserviços.

KafkaStreams nos permite consumir de tópicos Kafka, analisar ou transformar dados e, potencialmente, enviá-los para outro tópico Kafka.

Para demonstrarKafkaStreams,, vamos criar um aplicativo simples que lê frases de um tópico, conta ocorrências de palavras e imprime a contagem por palavra.

É importante observar que a bibliotecaKafkaStreams não é reativa e não tem suporte para operações assíncronas e tratamento de contrapressão.

2. Dependência do Maven

Para começar a escrever a lógica de processamento do Stream usandoKafkaStreams,, precisamos adicionar uma dependência akafka-streamsekafka-clients:


    org.apache.kafka
    kafka-streams
    1.0.0


    org.apache.kafka
    kafka-clients
    1.0.0

Também precisamos ter o Apache Kafka instalado e iniciado porque usaremos um tópico Kafka. Este tópico será a fonte de dados para o nosso trabalho de streaming.

Podemos baixar o Kafka e outras dependências necessárias emthe official website.

3. Configurando a entrada KafkaStreams

A primeira coisa que faremos é a definição do tópico Kafka de entrada.

Podemos usar a ferramentaConfluent que baixamos - ela contém um servidor Kafka. Ele também contém oskafka-console-producer que podemos usar para publicar mensagens para o Kafka.

Para começar, vamos executar nosso cluster Kafka:

./confluent start

Assim que o Kafka iniciar, podemos definir nossa fonte de dados e o nome de nosso aplicativo usandoAPPLICATION_ID_CONFIG:

String inputTopic = "inputTopic";
Properties streamsConfiguration = new Properties();
streamsConfiguration.put(
  StreamsConfig.APPLICATION_ID_CONFIG,
  "wordcount-live-test");

Um parâmetro de configuração crucial é oBOOTSTRAP_SERVER_CONFIG.. Este é o URL para nossa instância local do Kafka que acabamos de iniciar:

private String bootstrapServers = "localhost:9092";
streamsConfiguration.put(
  StreamsConfig.BOOTSTRAP_SERVERS_CONFIG,
  bootstrapServers);

Em seguida, precisamos passar o tipo de chave e o valor das mensagens que serão consumidas deinputTopic:

streamsConfiguration.put(
  StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG,
  Serdes.String().getClass().getName());
streamsConfiguration.put(
  StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG,
  Serdes.String().getClass().getName());

Stream processing is often stateful. When we want to save intermediate results, we need to specify the STATE_DIR_CONFIG parameter.

Em nosso teste, estamos usando um sistema de arquivos local:

streamsConfiguration.put(
  StreamsConfig.STATE_DIR_CONFIG,
  TestUtils.tempDirectory().getAbsolutePath());

4. Construindo uma Topologia de Streaming

Depois de definir nosso tópico de entrada, podemos criar uma Topologia de streaming - que é uma definição de como os eventos devem ser tratados e transformados.

Em nosso exemplo, gostaríamos de implementar um contador de palavras. Para cada frase enviada parainputTopic,, queremos dividi-la em palavras e calcular a ocorrência de cada palavra.

Podemos usar uma instância da classeKStreamsBuilder para começar a construir nossa topologia:

KStreamBuilder builder = new KStreamBuilder();
KStream textLines = builder.stream(inputTopic);
Pattern pattern = Pattern.compile("\\W+", Pattern.UNICODE_CHARACTER_CLASS);

KTable wordCounts = textLines
  .flatMapValues(value -> Arrays.asList(pattern.split(value.toLowerCase())))
  .groupBy((key, word) -> word)
  .count();

Para implementar a contagem de palavras, em primeiro lugar, precisamos dividir os valores usando a expressão regular.

O método split está retornando uma matriz. Estamos usandoflatMapValues() para achatá-lo. Caso contrário, acabaríamos com uma lista de matrizes e seria inconveniente escrever código usando essa estrutura.

Por fim, estamos agregando os valores de cada palavra e chamando ocount() que calculará as ocorrências de uma palavra específica.

5. Tratamento de resultados

Já calculamos a contagem de palavras de nossas mensagens de entrada. Now let’s print the results on the standard output using the foreach() method:

wordCounts
  .foreach((w, c) -> System.out.println("word: " + w + " -> " + c));

Na produção, geralmente esse trabalho de streaming pode publicar a saída em outro tópico do Kafka.

Poderíamos fazer isso usando oto() method:

String outputTopic = "outputTopic";
Serde stringSerde = Serdes.String();
Serde longSerde = Serdes.Long();
wordCounts.to(stringSerde, longSerde, outputTopic);

A classeSerde nos fornece serializadores pré-configurados para tipos Java que serão usados ​​para serializar objetos em uma matriz de bytes. A matriz de bytes será então enviada ao tópico Kafka.

Estamos usandoString como uma chave para o nosso tópico eLong como um valor para a contagem real. O métodoto() salvará os dados resultantes emoutputTopic.

6. Iniciando trabalho KafkaStream

Até esse ponto, criamos uma topologia que pode ser executada. No entanto, o trabalho ainda não começou.

Precisamos iniciar nosso trabalho explicitamente chamando o métodostart() na instânciaKafkaStreams:

KafkaStreams streams = new KafkaStreams(builder, streamsConfiguration);
streams.start();

Thread.sleep(30000);
streams.close();

Observe que estamos aguardando 30 segundos para o trabalho terminar. Em um cenário do mundo real, esse trabalho seria executado o tempo todo, processando eventos de Kafka à medida que eles chegassem.

Podemos testar nosso trabalho publicando alguns eventos em nosso tópico Kafka.

Vamos começar umkafka-console-producere enviar manualmente alguns eventos para o nossoinputTopic:

./kafka-console-producer --topic inputTopic --broker-list localhost:9092
>"this is a pony"
>"this is a horse and pony"

Dessa forma, publicamos dois eventos para Kafka. Nosso aplicativo consumirá esses eventos e imprimirá a seguinte saída:

word:  -> 1
word: this -> 1
word: is -> 1
word: a -> 1
word: pony -> 1
word:  -> 2
word: this -> 2
word: is -> 2
word: a -> 2
word: horse -> 1
word: and -> 1
word: pony -> 2

Podemos ver que quando a primeira mensagem chegou, a palavrapony ocorreu apenas uma vez. Mas quando enviamos a segunda mensagem, a palavrapony aconteceu pela segunda vez imprimindo: “word: pony → 2″.

6. Conclusão

Este artigo discute como criar um aplicativo de processamento de fluxo primário usando Apache Kafka como fonte de dados e a bibliotecaKafkaStreams como a biblioteca de processamento de fluxo.

Todos esses exemplos e trechos de código podem ser encontrados emGitHub project - este é um projeto Maven, portanto, deve ser fácil de importar e executar como está.