Construindo um pipeline de dados com Kafka, Spark Streaming e Cassandra
1. Visão geral
Apache Kafka é uma plataforma escalável, de alto desempenho e baixa latência queallows reading and writing streams of data like a messaging system. We can start with Kafka in Java com bastante facilidade.
Spark Streaming faz parte da plataformaApache Spark queenables scalable, high throughput, fault tolerant processing of data streams. Embora escrito em Scala,Spark offers Java APIs to work with.
Apache Cassandra é umdistributed and wide-column NoSQL data store. More details on Cassandra está disponível em nosso artigo anterior.
Neste tutorial, vamos combiná-los para criar umhighly scalable and fault tolerant data pipeline for a real-time data stream.
2. Instalações
Para começar, precisaremos do Kafka, Spark e Cassandra instalados localmente em nossa máquina para executar o aplicativo. Veremos como desenvolver um pipeline de dados usando essas plataformas à medida que prosseguirmos.
No entanto, deixaremos todas as configurações padrão, incluindo portas para todas as instalações, o que ajudará a fazer com que o tutorial funcione sem problemas.
2.1. Kafka
Instalar o Kafka em nossa máquina local é bastante simples e pode ser encontrado como parte dethe official documentation. Estaremos usando a versão 2.1.0 do Kafka.
Além disso,Kafka requires Apache Zookeeper to run, mas para o propósito deste tutorial, vamos aproveitar a instância do Zookeeper de nó único empacotada com Kafka.
Assim que conseguirmos iniciar o Zookeeper e o Kafka localmente seguindo o guia oficial, podemos prosseguir com a criação de nosso tópico, denominado “mensagens”:
$KAFKA_HOME$\bin\windows\kafka-topics.bat --create \
--zookeeper localhost:2181 \
--replication-factor 1 --partitions 1 \
--topic messages
Observe que o script acima é para a plataforma Windows, mas também existem scripts semelhantes disponíveis para plataformas do tipo Unix.
2.2. Faísca
Spark usa bibliotecas de cliente do Hadoop para HDFS e YARN. Consequentemente,it can be very tricky to assemble the compatible versions of all of these. No entanto,the official download of Spark vem pré-empacotado com versões populares do Hadoop. Para este tutorial, usaremos o pacote da versão 2.3.0 “pré-construído para Apache Hadoop 2.7 e posterior”.
Depois que o pacote certo do Spark é descompactado, os scripts disponíveis podem ser usados para enviar aplicativos. Veremos isso mais tarde, quando desenvolvermos nosso aplicativo no Spring Boot.
2.3. Cassandra
O DataStax disponibiliza uma edição da comunidade do Cassandra para diferentes plataformas, incluindo Windows. Podemos baixar e instalar isso em nossa máquina local com muita facilidadefollowing the official documentation. Estaremos usando a versão 3.9.0.
Depois de instalar e iniciar o Cassandra em nossa máquina local, podemos prosseguir para a criação de nosso keyspace e tabela. Isso pode ser feito usando o CQL Shell que acompanha nossa instalação:
CREATE KEYSPACE vocabulary
WITH REPLICATION = {
'class' : 'SimpleStrategy',
'replication_factor' : 1
};
USE vocabulary;
CREATE TABLE words (word text PRIMARY KEY, count int);
Observe que criamos um namespace chamadovocabulary e uma tabela nele chamadawords com duas colunas,word ecount.
3. Dependências
Podemos integrar as dependências Kafka e Spark em nosso aplicativo através do Maven. Extrairemos essas dependências do Maven Central:
E podemos adicioná-los ao nosso pom de acordo:
org.apache.spark
spark-core_2.11
2.3.0
provided
org.apache.spark
spark-sql_2.11
2.3.0
provided
org.apache.spark
spark-streaming_2.11
2.3.0
provided
org.apache.spark
spark-streaming-kafka-0-10_2.11
2.3.0
com.datastax.spark
spark-cassandra-connector_2.11
2.3.0
com.datastax.spark
spark-cassandra-connector-java_2.11
1.5.2
Note that some these dependencies are marked as provided in scope. Isso ocorre porque eles serão disponibilizados pela instalação do Spark, onde enviaremos o aplicativo para execução usando spark-submit.
4. Spark Streaming - Estratégias de integração Kafka
Neste ponto, vale a pena falar brevemente sobre as estratégias de integração para Spark e Kafka.
Kafka introduced new consumer API between versions 0.8 and 0.10. Portanto, os pacotes Spark Streaming correspondentes estão disponíveis para ambas as versões do broker. É importante escolher o pacote certo dependendo do corretor disponível e dos recursos desejados.
4.1. Spark Streaming Kafka 0.8
A versão 0.8 é a API de integração estávelwith options of using the Receiver-based or the Direct Approach. Não entraremos em detalhes dessas abordagens quewe can find in the official documentation. Um ponto importante a ser observado aqui é que este pacote é compatível com o Kafka Broker versões 0.8.2.1 ou superior.
4.2. Spark Streaming Kafka 0.10
Atualmente, ele está em um estado experimental e é compatível apenas com as versões 0.10.0 ou Kafka Broker. Este pacoteoffers the Direct Approach only, now making use of the new Kafka consumer API. Podemos encontrar mais detalhes sobre estein the official documentation. É importante ressaltar que énot backward compatible with older Kafka Broker versions.
Observe que, para este tutorial, usaremos o pacote 0.10. A dependência mencionada na seção anterior refere-se apenas a isso.
5. Desenvolvendo um pipeline de dados
Criaremos um aplicativo simples em Java usando Spark que se integrará ao tópico Kafka que criamos anteriormente. O aplicativo lerá as mensagens postadas e contará a frequência das palavras em todas as mensagens. Isso será atualizado na tabela Cassandra que criamos anteriormente.
Vamos visualizar rapidamente como os dados fluirão:
5.1. ObtendoJavaStreamingContext
Em primeiro lugar, começaremos inicializando oJavaStreamingContext which is the entry point for all Spark Streaming applications:
SparkConf sparkConf = new SparkConf();
sparkConf.setAppName("WordCountingApp");
sparkConf.set("spark.cassandra.connection.host", "127.0.0.1");
JavaStreamingContext streamingContext = new JavaStreamingContext(
sparkConf, Durations.seconds(1));
5.2. ObtendoDStream de Kafka
Agora, podemos nos conectar ao tópico Kafka doJavaStreamingContext:
Map kafkaParams = new HashMap<>();
kafkaParams.put("bootstrap.servers", "localhost:9092");
kafkaParams.put("key.deserializer", StringDeserializer.class);
kafkaParams.put("value.deserializer", StringDeserializer.class);
kafkaParams.put("group.id", "use_a_separate_group_id_for_each_stream");
kafkaParams.put("auto.offset.reset", "latest");
kafkaParams.put("enable.auto.commit", false);
Collection topics = Arrays.asList("messages");
JavaInputDStream> messages =
KafkaUtils.createDirectStream(
streamingContext,
LocationStrategies.PreferConsistent(),
ConsumerStrategies. Subscribe(topics, kafkaParams));
Observe que fornecemos desserializadores para chave e valor aqui. For common data types like String, the deserializer is available by default. No entanto, se quisermos recuperar tipos de dados personalizados, teremos que fornecer desserializadores personalizados.
Aqui, obtivemosJavaInputDStream, que é uma implementação de fluxos discretizados ouDStreams, the basic abstraction provided by Spark Streaming. Internamente, o DStreams nada mais é do que uma série contínua de RDDs.
5.3. Processamento obtidoDStream
Agora vamos realizar uma série de operações noJavaInputDStream para obter as frequências das palavras nas mensagens:
JavaPairDStream results = messages
.mapToPair(
record -> new Tuple2<>(record.key(), record.value())
);
JavaDStream lines = results
.map(
tuple2 -> tuple2._2()
);
JavaDStream words = lines
.flatMap(
x -> Arrays.asList(x.split("\\s+")).iterator()
);
JavaPairDStream wordCounts = words
.mapToPair(
s -> new Tuple2<>(s, 1)
).reduceByKey(
(i1, i2) -> i1 + i2
);
5.4. PersistindoDStream processado em Cassandra
Finalmente, podemos iterar sobre osJavaPairDStream processados para inseri-los em nossa tabela do Cassandra:
wordCounts.foreachRDD(
javaRdd -> {
Map wordCountMap = javaRdd.collectAsMap();
for (String key : wordCountMap.keySet()) {
List wordList = Arrays.asList(new Word(key, wordCountMap.get(key)));
JavaRDD rdd = streamingContext.sparkContext().parallelize(wordList);
javaFunctions(rdd).writerBuilder(
"vocabulary", "words", mapToRow(Word.class)).saveToCassandra();
}
}
);
5.5. Executando o aplicativo
Como este é um aplicativo de processamento de fluxo, queremos manter isso em execução:
streamingContext.start();
streamingContext.awaitTermination();
6. Alavancagem de pontos de verificação
Em um aplicativo de processamento de fluxo,it’s often useful to retain state between batches of data being processed.
Por exemplo, em nossa tentativa anterior, só podemos armazenar a frequência atual das palavras. E se quisermos armazenar a frequência cumulativa? Spark Streaming makes it possible through a concept called checkpoints.
Agora vamos modificar o pipeline que criamos anteriormente para aproveitar os pontos de verificação:
Observe que usaremos pontos de verificação apenas para a sessão de processamento de dados. Isso não fornece tolerância a falhas. However, checkpointing can be used for fault tolerance as well.
Existem algumas mudanças que teremos que fazer em nosso aplicativo para alavancar os pontos de verificação. Isso inclui fornecer aoJavaStreamingContext uma localização de ponto de verificação:
streamingContext.checkpoint("./.checkpoint");
Aqui, estamos usando o sistema de arquivos local para armazenar pontos de verificação. No entanto, para maior robustez, isso deve ser armazenado em um local como HDFS, S3 ou Kafka. Mais sobre isso está disponível emthe official documentation.
Em seguida, teremos que buscar o ponto de verificação e criar uma contagem cumulativa de palavras enquanto processamos cada partição usando uma função de mapeamento:
JavaMapWithStateDStream> cumulativeWordCounts = wordCounts
.mapWithState(
StateSpec.function(
(word, one, state) -> {
int sum = one.orElse(0) + (state.exists() ? state.get() : 0);
Tuple2 output = new Tuple2<>(word, sum);
state.update(sum);
return output;
}
)
);
Depois de obter a contagem acumulada de palavras, podemos prosseguir para iterar e salvá-las no Cassandra como antes.
Observe que enquantodata checkpointing is useful for stateful processing, it comes with a latency cost. Portanto, é necessário usar isso sabiamente, juntamente com um intervalo de ponto de verificação ideal.
7. Noções básicas sobre compensações
Se lembrarmos de alguns dos parâmetros Kafka que definimos anteriormente:
kafkaParams.put("auto.offset.reset", "latest");
kafkaParams.put("enable.auto.commit", false);
Basicamente, isso significa quewe don’t want to auto-commit for the offset and would like to pick the latest offset every time a consumer group is initialized. Conseqüentemente, nosso aplicativo só poderá consumir mensagens postadas durante o período em que estiver em execução.
Se quisermos consumir todas as mensagens postadas independentemente de o aplicativo estar em execução ou não, e também quisermos acompanhar as mensagens já postadas,we’ll have to configure the offset appropriately along with saving the offset state, embora isso esteja um pouco fora do escopo deste tutorial.
This is also a way in which Spark Streaming offers a particular level of guarantee like “exactly once”. Isso basicamente significa que cada mensagem postada no tópico Kafka será processada apenas uma vez pelo Spark Streaming.
8. Implantando aplicativo
Podemosdeploy our application using the Spark-submit script que vem pré-embalado com a instalação do Spark:
$SPARK_HOME$\bin\spark-submit \
--class com.example.data.pipeline.WordCountingAppWithCheckpoint \
--master local[2]
\target\spark-streaming-app-0.0.1-SNAPSHOT-jar-with-dependencies.jar
Observe que o jar que criamos usando Maven deve conter as dependências que não estão marcadas comoprovided no escopo.
Depois de enviar esse aplicativo e publicar algumas mensagens no tópico Kafka que criamos anteriormente, devemos ver as contagens acumuladas de palavras sendo postadas na tabela Cassandra que criamos anteriormente.
9. Conclusão
Para resumir, neste tutorial, aprendemos como criar um pipeline de dados simples usando Kafka, Spark Streaming e Cassandra. Também aprendemos como aproveitar os pontos de verificação no Spark Streaming para manter o estado entre os lotes.
Como sempre, o código dos exemplos está disponívelover on GitHub.