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á.