1 概述

上一篇博客我们学习了Spark Streaming + Kafka(0.10.0)版本的,这次我们通过两个版本的对比进行一个更深刻的认识。对于Kafka broker version 0.8.2.1可以支持高版本,而0.10.0版本以后并不支持以前的旧版本。

旧版本中有两种方法 - 使用Receivers和Kafka的高级API的旧方法,以及不使用Receivers的新方法(在Spark 1.3中引入)。 它们具有不同的编程模型,性能特征和语义保证, 就目前的Spark版本而言,这两种方法都被认为是稳定的API。
注意:从Spark 2.3.0开始,已弃用Kafka 0.8支持。

2 基于Receiver的方法

这种方法使用Receiver来接收数据。 Receiver是使用Kafka高级消费者API实现的。 与所有接收方一样,通过Receiver从Kafka接收的数据存储在Spark执行程序中,然后由Spark Streaming启动的作业处理数据。
但是,在默认配置下,这种方法可能会在失败时丢失数据。为确保零数据丢失,您必须另外启用Spark Streaming中的预写日志(Write Ahead Logs 机制,在Spark 1.2中引入),同时保存所有收到的Kafka 数据写入分布式文件系统(例如HDFS)的预先写入日志中,以便在发生故障时恢复所有数据。

  • Maven
groupId = org.apache.spark
 artifactId = spark-streaming-kafka-0-8_2.11
 version = 2.3.0
  • 代码
import org.apache.spark.SparkConf
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.streaming.{Seconds, StreamingContext}

object KafkaWordCountApp {
/*
通过外部进行传参,对传入的参数进行判断,显示错误信息
*/
  def main(args: Array[String]) {
    if(args.length != 4) {
      System.err.println("Usage: KafkaWordCountApp <zkQuorum> <groupId> <topics> <numThreads>")
      System.exit(1)
    }

    // 参数
    val Array(zkQuorum,groupId, topics, numThreads) = args

    val sparkConf = new SparkConf().setAppName("SocketWordCount").setMaster("local[2]")
    val ssc = new StreamingContext(sparkConf, Seconds(10))

    //可以传入多个topic streaming_topic,streaming_topic2,streaming_topic3
    val topicMap = topics.split(",").map((_,numThreads.toInt)).toMap
    val messages = KafkaUtils.createStream(ssc, zkQuorum, groupId, topicMap)

    //wc
    messages.map(_._2) // 是我们的value
      .flatMap(_.split(",")).map((_,1))
      .reduceByKey(_+_)
      .print

    ssc.start()
    ssc.awaitTermination()
  }
}

重点:
1. Kafka中的topic的分区与Spark Streaming中生成的RDD分区不相关。因此,增加KafkaUtils.createStream()中topic特定分区的数量只会增加单个接收器中使用哪些topic的线程数。在处理数据时不会增加Spark的并行性。
2. 多个Kafka输入DStream可以用不同的组和topic创建,用于使用多个接收器并行接收数据。
3. 这种方法可能会出现数据丢失问题,在driver端挂掉,Executor重启的时候必然会丢数据,在1.2时增加了一个Write Ahead Logs的机制(在写到Receiver时,写到一个日志中,通常为HDFS),这个机制可以避免数据丢失,但是写到HDFS或者文件系统必然会降低吞吐量。同时启动Write Ahead Logs时,存储结构设置为StorageLevel.MEMORY_AND_DISK_SER,不需要多副本,因为HDFS就是多副本机制.
4. 这时候对于kafka来说是至少一次的语义。

3 基于Direct Approach方式

这种新方式不需要Recevier已在Spark 1.3中引入,可以确保更强大的端到端保证。 此方法不是使用接收器接收数据,而是定期查询Kafka每个topic+partition中的最新偏移量,并相应地定义要在每批中处理的偏移量范围。 当处理数据的作业启动时,使用简单的Kafka API用于从Kafka中读取定义的偏移范围(类似于从文件系统读取文件)。 请注意,此功能是在Spark 1.3中为Scala和Java API引入的,适用于Python API的Spark 1.4。

3.1 和方法一进行对比

简化的并行性(Simplified Parallelism):无需创建多个输入Kafka流并将其合并。 使用directStream,Spark Streaming将创建与使用Kafka分区一样多的RDD分区,这些分区将全部从Kafka并行读取数据(两个分区相关联)。 因此,kafka和RDD分区之间有一对一的映射关系,相对于第一种kafka的分区和rdd分区之间没有关系,需要通过创建多个接收器并行接收数据,这更易于理解和调整。

效率(Efficiency):在第一种方法中实现零数据丢失需要将数据存储在预写日志中(WAL机制实现),这会进一步复制数据。 这实际上是效率低下的,因为数据被有效地复制了两次 - 一次是由Kafka进行的,而另一次是通过预先写入日志(Write Ahead Log)。 因为没有接收器,所以第二种方法消除了这个问题,因此不需要预先写入日志。 只要您拥有足够的Kafka retention(参数设置),就可以从kafka恢复信息。

kafka服务端与客户端怎么匹配版本_kafka

完全一次语义(Exactly-once semantics):第一种方法使用Kafka的高级API在Zookeeper中存储消耗的偏移量。传统上这是从Kafka消费数据的方式。尽管这种方法(结合提前写入日志)可以确保零数据丢失(即至少一次语义),但在某些故障情况下,有一些记录可能会消耗两次的机会很小。发生这种情况是因为Spark Streaming可靠接收的数据与Zookeeper跟踪的偏移之间不一致。因此,在第二种方法中,我们不在使用Zookeeper的简单Kafka API。在其检查点内,Spark Streaming跟踪偏移量。这消除了Spark Streaming和Zookeeper / Kafka之间的不一致性,因此Spark Streaming每次记录都会在发生故障时有效地收到一次。为了实现输出结果的一次语义,将数据保存到外部数据存储区的输出操作必须是幂等的,或者是保存结果和偏移量的原子事务(请参阅主程序中输出操作的语义指南获取更多信息)。

注意:当offset(偏移量)还没有写入kafka,driver就挂了,可能会导致重复写入,也就是会产生至少一次的语义。可以把偏移量交给sparkStreaming的checkpoint来维护,这样就会达到只有一次的效果。
缺点:在ssc中维护偏移量,那么kafka界面则无法显示,所以可以将偏移量重新写入到zk中,