Spark Streaming 和kafka 集成指导(kafka 0.8.2.1 或以上版本)

本节介绍一下如何配置Spark Streaming 来接收kafka的数据。有两个方法:
1、老的方法 -使用Receivers 和kafka的高级API
2、新的方法( Spark 1.3 开始引入)-不适用Receivers。这两个方式拥有不同的编程模型,性能特征和语义保证,为了获得更多细节,继续往下读。对于目前的版本的spark。这两个方式都是稳定的。

方法1 基于Receiver的 方式

这个方法使用了一个Receiver 接收数据。这个Receiver 是用kafka 高级的 consumer的api实现的。对于所有的receiver,通过Receiver 接收的kafka的数据会被存储到Spark的executors,然后 Spark Streaming 启动jobs处理数据。
然而 默认配置下,这个方式在失败的情况下回丢失数据(参考 receiver reliability.
)。为了保证零数据丢失,你必须在Spark Streaming (introduced in Spark 1.2)额外的开启Write Ahead Logs。这会同步的把接受的到kafka的数据写入到分布式系统(比如 HDFS) ahead logs 中,因此 所有的数据都可以在失败的时候进行恢复。 参考 Deploying section
以获取更多的关于 Write Ahead Logs.的信息。

下面, 我们讨论下如何在你的streaming的应用中 使用这个方法。
1、Linking: 对于Scala/Java 应用使用SBT/MAven的项目定义,连接到你的streaming的应用使用如下的artifact。

groupId = org.apache.spark
artifactId = spark-streaming-kafka-0-8_2.11
version = 2.2.0

2、编程: 如下所示

 import org.apache.spark.streaming.kafka._
 val kafkaStream = KafkaUtils.createStream(streamingContext,
 [ZK quorum], [consumer group id], [per-topic number of Kafka partitions to consume])

你也可以通过使用 createStream 的参数指定key和value的类和以及它们响应的解码的类。参考See the API docsand the example.

需要记住的点

  • kafka中topic的分区和streaming中的RDD的分区是不一致的。因此增加在KafkaUtils.createStream()中增加topic的分区数只能增加使用消费这些topic的单台机器上的线程数。
  • 为了接受并行的数据可以使用多个receiver 。不同的分组和topics可以创建多个kafka的DStreams.
  • 如果你在一个可复制的文件系统内(比如hdfs)开启了Write Ahead Logs ,接收到的数据,接收到的数据已经在log中被复制了。因而,输入流的存储的等级是 StorageLevel.MEMORY_AND_DISK_SER。也就是说KafkaUtils.createStream(..., StorageLevel.MEMORY_AND_DISK_SER)).

3、 部署
和其他的Spark的应用一样,spark-submit 用来提交你的应用。然而 Scala/java 与python 在细节方面有稍微的不同。
对于Scala和Java的应用,如果你正在使用SBT或者maven管理项目,会把spark-streaming-kafka-0-8_2.11 和他的依赖打包到应用的JAR。需要确保spark-core_2.11 和spark-streaming_2.11 被标记为provided,因为这些在Spark的安装中就已经有了。然后使用spark-submit 提交你的应用(参见主编程指南中的部署部分)。
Python应用没有SBT和maven项目管理,spark-streaming-kafka-0-8_2.11 和它的依赖可以直接添加到Spark 要提交的包中(见应用提交指南)。

./bin/spark-submit --packages org.apache.spark:spark-streaming-kafka-0-8_2.11:2.2.0 ...

或者,你也可以从maven的仓库下载 Maven 的 spark-streaming-kafka-0-8-assembly ,把它加到你要提交 spark-submit 通过--jars的形式。

方法2 :Direct Approcach(没有接收器)

在Spark1.3中引入了这种新的接收器较少的“直接”方法,以确保更强的端到端保证。该方法不使用接收者接收数据,而是周期性地查询Kafka在每个主题+分区中的最新偏移量,并据此定义每批(batch)处理的偏移范围。当处理数据的作业被启动时,Kafka的简单消费者API用于读取Kafka定义的偏移范围(类似于文件系统中的读取文件)。注意这个功能被引入是Spark1.3 在Scala和java API,Spark 1.4 才提供Python API。

这种方法比基于接收者的方法(即方法1)具有以下优点。

  • 简化的并行性:无需创建多个输入Kfaka流并将它们合并。使用DirectStream, Spark Streaming 将创建和Kafka分区一样多的RDD分区进行消费,将所有kafka的数据并行读。所以Kafka和RDD的分区之间是一对一的映射,这样比较容易理解和调整。
  • 效率:在第一种方法中实现零数据丢失要求将数据存储在Write Ahead Log,中,从而进一步复制数据。
    这实际上是低效的,因为数据复制了两次,一次是Kafka,第二次是 Write Ahead Log。第二种方法消除了问题,因为没有接收器,因此不需要Write Ahead Log。只要Kafka有保存,消息就可以从Kafka中恢复。
  • Exactly-once 语义:第一种方法使用Kafka的高层API在ZooKeeper存储消费的偏移量。从传统上来说,这是从Kafka获取数据的方式。虽然这种方法(与write ahead logs相结合)可以确保零数据丢失(即至少一次语义),但有些记录在某些故障下可能会被消费 两次。这是因为Spark Streaming 接收的数据之间的不一致性以及Zookeeper中的偏移量。因而,在第二个方法中,我们使用简单的不使用Zookeeper 的Kafka的API。Spark Streaming用checkpoints跟踪偏移量 。这样消除了Spark Streaming 和Zookeeper/kafka的不一致性,所以无论是否失败,Spark Streaming 每个记录都只会被恰好接收一次。为了实现输出结果的exactly-once 的语义,你保存数据的操作必须要么是幂等的,要么是原子的(保存结果和偏移量)。(请参阅主编程指南中的输出操作的语义信息)。
    注意,这种方法的一个缺点是它不更新ZK中的偏移量, 因此Zookeeper-based Kafka 监控器不会显示进度。然而,你可以通过这个方式在每个batch中访问偏移量和更新Zookeeper (见下文)。
    接下来,我们将讨论如何在Streaming 的应用程序中使用此方法。

- 链接:这种方法只支持在scala/ java应用程序。你的SBT/Maven项目使用如下的artifact(见链接,在进一步信息的主要编程指南部分)。

groupId = org.apache.spark
artifactId = spark-streaming-kafka-0-8_2.11
version = 2.2.0

- 编程: 在streaming的应用的代码中,引入kafkautils,并创建如下输入dstream。

import org.apache.spark.streaming.kafka._

 val directKafkaStream = KafkaUtils.createDirectStream[
 [key class], [value class], [key decoder class], [value decoder class] ](
 streamingContext, [map of Kafka parameters], [set of topics to consume])

你也可以传入messagehandler到createdirectstream访问messageandmetadata包含有关当前消息元数据,并将它转换为任意类型。请参见API文档和示例。

在Kafka的参数中,您必须指定metadata.broker.list或bootstrap.servers。默认情况下,它将从每个Kafka分区的最新偏移量开始消费。如果你设置的配置auto.offset.reset 参数smallest,它将从最小的偏移处开始消费。
你也可以使用的其他变化kafkautils.createdirectstream,从任意的偏移开始消费。此外,如果您希望访问每个batch中消耗的Kafka偏移量,您可以执行以下操作。

// Hold a reference to the current offset ranges, so it can be used downstream
var offsetRanges = Array.empty[OffsetRange]

directKafkaStream.transform { rdd =>
offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
rdd
}.map {
       ...
}.foreachRDD { rdd =>
  for (o <- offsetRanges) {
   println(s"${o.topic} ${o.partition} ${o.fromOffset} ${o.untilOffset}")
 }
 ...
 }

你可能感兴趣的:(spark)