面试被问Spark Streaming消费Kafka数据的方式,你清楚吗?

Apache Kafka是一个高性能、分布式的消息队列系统。而Spark Streaming是基于Spark引擎的数据处理框架,提供实时数据处理解决方案。Spark Streaming可以从多个数据源进行数据读取,其中Kafka是其中之一。在数据处理过程中,为了保证实时性和可靠性,常常需要对数据进行实时处理和消费。因此,Spark Streaming提供了三种消费Kafka数据的方式,分别是Kafka Receiver API、Direct Approach和Kafka Integration。

本文将介绍这三种方式的原理、实现方式以及优缺点。

一、Kafka Receiver API

1. 原理

Kafka Receiver API 是 Spark Streaming 最早支持的一种消费 Kafka 数据的方式。该方式的原理是以 Spark Receiver 作为消费 Kafka 数据的代理,通过 Kafka Consumer API 从 Kafka 集群中拉取数据,并将拉取的数据存储在一个 Block 数据结构中。

在这个过程中,Kafka Receiver 会为每个 Block 分配一个唯一的 id,根据 Block 的大小决定何时将数据发送给 Spark 进行处理。在数据发送时,Spark Streaming会将 Block 中的数据存储在 Spark 中的缓存中,并进行处理。

2. 实现

在 Spark Streaming 中使用 Kafka Receiver API 消费 Kafka 数据,需要引入一个 KafkaUtils 对象。该对象的使用方法如下:

//引入 SparkStreaming 和 Kafka 需要的依赖

import org.apache.spark.streaming.kafka.KafkaUtils;

import org.apache.spark.streaming.api.java.JavaInputDStream;

import org.apache.spark.streaming.api.java.JavaStreamingContext;

import org.apache.spark.streaming.Durations;

//设置 SparkStreaming 应用名称、批次时间间隔、Kafka 参数等配置信息

String appName = "KafkaReceiverApp";

String brokerList = "localhost:9092";

String groupId = "group1";

String topic = "test";

int numThreads = 1;

//创建 JavaStreamingContext 对象

JavaStreamingContext jssc = new JavaStreamingContext(conf, Durations.seconds(5));

//创建 Kafka 数据流

Map<String, Integer> topicMap = new HashMap<String, Integer>();

topicMap.put(topic, numThreads);

JavaInputDStream<String> messages = KafkaUtils.createStream(jssc, brokerList, groupId, topicMap);

//对 Kafka 数据流进行处理

messages.print();

//启动 SparkStreaming 应用

jssc.start();

jssc.awaitTermination();

在上述代码中,KafkaUtils.createStream() 方法用于创建一个包含 Kafka 中数据的流,并指定了 Kafka 集群的地址、消费者组、所消费的主题以及消费线程的数量。

3. 优缺点

优点:

  • 支持高度容错的 Spark Receiver,以及断电自动恢复功能。
  • 提供了丰富的函数式 API,以及基于 DStream 的操作方式。
  • 有效地利用了 Spark 的缓存机制,为下游的处理提供了更好的性能。

缺点。

  • 在接收 Kafka 数据时,必须使用 Spark Receiver 进行中间接收,导致数据多次传输,存在较大的网络开销。
  • Spark Receiver 过多也会导致 Spark 堆栈的 OOM 异常。
  • 在处理数据时,如果某些 block 处理缓慢,则可能会导致 Spark Streaming 无法处理后续数据。

二、Direct Approach

1. 原理

在传统的Kafka Receiver API中,Spark Streaming中的每个Receiver会在单独的线程中运行,接收来自Kafka分区的数据并存储到Spark的内存中。这种方式的缺点在于:

  • 每个Receiver都需要创建一个独立的连接,会导致Kafka Broker的负载增加。
  • Receiver存储数据的内存可能会因为存储过多的数据而被耗尽,从而导致Spark应用程序崩溃。

为了避免这些问题,Spark Streaming提供了另一种直接连接到Kafka Broker的方式,称为Direct Approach。这种方式通过直接连接到Kafka Broker来消费数据,并将其作为RDD进行处理。

Direct Approach实现的关键在于两个部分:Kafka底层API(Consumer API)和Spark Streaming的Kafka消费API。Kafka的Consumer API允许用户创建一个消费者实例,并从Kafka集群中获取数据。Spark Streaming的Kafka消费API提供了一种从Kafka Topic中获取数据的方式,并将其转换为DStream,以供Spark Streaming处理。

Direct Approach的架构中包含多个Kafka Topic,每个Topic有一个或多个分区。Spark Streaming的应用程序在进行数据处理时会创建一个或多个Kafka消费者实例,每个消费者实例会消费一个或多个Topic中的分区数据。消费者实例接收到的每个分区中的数据会转换为一个RDD,所有RDD会合并到一个DStream中,供Spark Streaming进行处理。

2. 实现

Direct Approach的实现步骤如下:

创建Kafka消费者对象,用于与Kafka集群中的Topic进行交互。需要指定Kafka集群的地址、Kafka Topic的名称、消费者组的名称等信息。

Map<String, Object> kafkaParams = new HashMap<>();

kafkaParams.put("bootstrap.servers", "localhost:9092");

kafkaParams.put("group.id", "group1");

kafkaParams.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

kafkaParams.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

kafkaParams.put("auto.offset.reset", "latest");

kafkaParams.put("enable.auto.commit", false);

Collection<String> topics = Arrays.asList("topic1", "topic2");

JavaInputDStream<ConsumerRecord<String, String>> stream = KafkaUtils.createDirectStream(

jssc,

LocationStrategies.PreferConsistent(),

ConsumerStrategies.<String, String>Subscribe(topics, kafkaParams)

);

//根据Kafka消费者实例接收到的数据创建一个RDD。

JavaDStream<String> lines = stream.map(ConsumerRecord::value);

//对RDD进行转换和操作,生成最终的结果。

JavaDStream<String> words = lines.flatMap(x -> Arrays.asList(x.split(" ")).iterator());

JavaPairDStream<String, Integer> pairs = words.mapToPair(s -> new Tuple2<>(s, 1));

JavaPairDStream<String, Integer> wordCounts = pairs.reduceByKey((i1, i2) -> i1 + i2);

wordCounts.print();

//启动Spark Streaming应用程序并等待数据流输入。

jssc.start();

jssc.awaitTermination();

3. 优缺点

优点:

  • 降低延迟:通过直接连接Kafka分区和Spark executor,避免了数据的复制和网络传输,减少了延迟。
  • 更高的吞吐量:由于直接连接Kafka分区和Spark executor,不需要进行shuffle操作,因此有更高的吞吐量。
  • 提高可靠性:使用Direct Approach可以避免重复消费和数据丢失的情况。

缺点:

  • 需要维护offset:使用Direct Approach需要手动维护offset,如果有任务失败或重新调度,需要注意offset的正确性。
  • 不支持事务:由于使用Direct Approach需要手动维护offset,不支持Kafka的事务功能。
  • 需要更多的资源:由于直接连接Kafka分区和Spark executor,需要更多的资源来处理和维护连接,会在一定程度上增加集群的负担。
  • 不灵活:Direct Approach只能访问Kafka的一个特定分区,不支持多个分区的操作,不够灵活。

综上所述,Direct Approach适合对低延迟和高吞吐量要求较高的场景,但需要考虑手动维护offset和资源消耗的问题。如果对灵活性的要求比较高,可以考虑使用其他的方式。

三、Kafka integration

1. 原理

Kafka Integration是指通过在应用程序中使用Kafka客户端直接读取和写入Kafka消息队列中的数据。其原理如下:

  • 应用程序通过Kafka客户端API连接到Kafka集群。
  • 应用程序消费或生产消息,并将消息发送给Kafka集群。
  • Kafka集群将消息存储在Topic中,等待被其他的应用程序消费。

Kafka Integration主要应用于以下场景:

  • 实时数据流处理。
  • 分布式日志收集和处理。
  • 大数据分析和挖掘。

2. 实现

Kafka Integration可以通过Java、Scala、Python等编程语言的Kafka客户端API实现。下面是一个Java实现Kafka Integration的代码示例:

import org.apache.kafka.clients.producer.*;

import org.apache.kafka.clients.consumer.*;

import org.apache.kafka.common.serialization.*;

public class KafkaIntegrationDemo {

private static final String BOOTSTRAP_SERVERS = "localhost:9092";

private static final String TOPIC_NAME = "test-topic";

public static void main(String[] args) {

// 生产者发送消息

Properties producerProps = new Properties();

producerProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, BOOTSTRAP_SERVERS);

producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());

producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());

KafkaProducer<String, String> producer = new KafkaProducer<>(producerProps);

producer.send(new ProducerRecord<>(TOPIC_NAME, "key", "value"));

producer.close();

// 消费者接收消息

Properties consumerProps = new Properties();

consumerProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, BOOTSTRAP_SERVERS);

consumerProps.put(ConsumerConfig.GROUP_ID_CONFIG, "test-group");

consumerProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());

consumerProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());

KafkaConsumer<String, String> consumer = new KafkaConsumer<>(consumerProps);

consumer.subscribe(Collections.singletonList(TOPIC_NAME));

ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));

for (ConsumerRecord<String, String> record : records) {

System.out.println(String.format("key:%s, value:%s", record.key(), record.value()));

}

consumer.close();

}

}

3. 优缺点

Kafka Integration的优点:

  • 实时性好,能够实现高性能、低延迟的数据流处理。
  • 可以扩展到分布式集群中,具有高可伸缩性和高可用性。
  • 具备高吞吐量和高并发性,适用于大数据处理场景。

Kafka Integration的缺点:

  • 使用Kafka客户端API需要编写复杂的程序逻辑。
  • Kafka集群的配置和维护成本较高。
  • 必须保证数据一致性和安全性,否则可能导致数据丢失或泄露。

四、总结

本文介绍了Spark Streaming消费Kafka中的数据有三种方式:基于Direct方式的Kafka Integration API、基于Receiver方式的Kafka Receiver API、基于Structured Streaming的Kafka Integration API。其中,基于Direct方式的Kafka Integration API是最常用的方式,因为它可以减少数据传输的延迟,并且具有更高的吞吐量和更简单的故障恢复机制;而基于Receiver方式的Kafka Receiver API则已经被废弃,不再建议使用;而基于Structured Streaming的Kafka Integration API则主要适用于更高级别的流处理场景。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值