Spark Streaming从Kafka中接收数据的两种方式

spark streaming流式处理kafka中的数据,首先是把数据接收过来,然后转换为spark streaming中的数据结构Dstream。接收数据的方式有两种:1.利用Receiver接收数据;2.直接从kafka读取数据。

基于Receiver的方式(旧方法)

流程:
此方法使用Receiver接收数据。Receiver是使用Kafka高阶API接口实现的。与所有接收器一样,从Kafka通过Receiver接收的数据存储在Spark执行器中,然后由Spark Streaming启动的作业处理数据。
Spark Streaming从Kafka中接收数据的两种方式_第1张图片
问题:
在默认配置下,此方法可能会在失败时丢失数据。为确保零数据丢失,必须在Spark Streaming中另外启用预写日志(Write Ahead Logs)。这将同步保存所有收到的Kafka数据到分布式文件系统(例如HDFS)上,以便在发生故障时可以恢复所有数据。

注意点:
在Receiver的方式中,Kafka中的topic partition与Spark Streaming中生成的RDD partition无关。所以如果我们加大每个topic的partition数量,仅仅是增加线程来处理由单一Receiver消费的主题。但是这并没有增加Spark在处理数据上的并行度。
对于不同的Group和topic我们可以使用多个Receiver创建不同的Dstream来并行接收数据,之后可以利用union来统一成一个Dstream。
如果我们启用了Write Ahead Logs复制到文件系统如HDFS,那么storage level需要设置成 StorageLevel.MEMORY_AND_DISK_SER,也就是KafkaUtils.createStream(…, StorageLevel.MEMORY_AND_DISK_SER)

直接读取方式( Direct Stream方法)

流程:
这种方法不使用接收器(Receiver)来接收数据,而是定期向Kafka查询每个主题的每个分区中的最新偏移量(offsets),并相应地定义要在每个批次(batch)中处理的偏移量范围。当Spark Streaming启动处理数据的作业时,利用Kafka的低阶API读取Kafka定义的偏移范围的数据。
Spark Streaming从Kafka中接收数据的两种方式_第2张图片
优点:
这种方法相较于Receiver方式的优势在于:
简化的并行:在Receiver的方式中我们提到创建多个Receiver之后利用union来合并成一个Dstream的方式提高数据传输并行度。而在Direct方式中,Kafka中的partition与RDD中的partition是一一对应的并行读取Kafka数据,这种映射关系也更利于理解和优化。
高效:在Receiver的方式中,为了达到0数据丢失需要将数据存入Write Ahead Log中,这样在Kafka和日志中就保存了两份数据,浪费!而第二种方式不存在这个问题,只要我们Kafka的数据保留时间足够长,我们都能够从Kafka进行数据恢复。
精确一次:在Receiver的方式中,使用的是Kafka的高阶API接口从Zookeeper中获取offset值(偏移量),这也是传统的从Kafka中读取数据的方式,但由于Spark Streaming消费的数据和Zookeeper中记录的offset不同步,这种方式偶尔会造成数据重复消费。而第二种方式,直接使用了简单的低阶Kafka API,Offsets则利用Spark Streaming的checkpoints进行记录,消除了这种不一致性。

缺点:
Direct需要用户采用checkpoint或者第三方存储来维护offsets,而不像Receiver-based那样,通过ZooKeeper来维护Offsets,此提高了用户的开发成本

/**
  * Kafka 0.10的Spark Streaming集成(spark获取kafka数据的最新方式)
  */
object KafkaDirectStream {

  def main(args: Array[String]): Unit = {
    //创建SparkConf,如果将任务提交到集群中,那么要去掉.setMaster("local[2]")

    val conf = new SparkConf().setAppName("DirectStream").setMaster("local[2]")
    val sc = new SparkContext(conf)
    sc.setLogLevel("WARN")
    //创建一个StreamingContext,其里面包含了一个SparkContext
    val streamingContext = new StreamingContext(sc, Seconds(5))

    //配置kafka的参数
    /**
      * Kafka服务监听端口
      * 指定kafka输出key的数据类型及编码格式(默认为字符串类型编码格式为uft-8)
      * 指定kafka输出value的数据类型及编码格式(默认为字符串类型编码格式为uft-8)
      * 消费者ID,随意指定
      * 指定从latest(最新)还是smallest(最早)处开始读取数据
      * 如果true,consumer定期地往zookeeper写入每个分区的offset
      */
    val kafkaParams = Map[String, Object](

      "bootstrap.servers" -> "192.168.2.210:9092",    //kafka机器IP:端口
      "key.deserializer" -> classOf[StringDeserializer],
      "value.deserializer" -> classOf[StringDeserializer],
      "group.id" -> "g1",
      "auto.offset.reset" -> "latest",
      "partition.assignment.strategy" -> "org.apache.kafka.clients.consumer.RangeAssignor",
      "enable.auto.commit" -> (false: java.lang.Boolean)

    )

    //要监听的Topic,可以同时监听多个
    val topics = Array("test")

    //在Kafka中记录读取偏移量
    val stream = KafkaUtils.createDirectStream[String, String](
      streamingContext,
      //位置策略(可用的Executor上均匀分配分区)
      LocationStrategies.PreferConsistent,
      //消费策略(订阅固定的主题集合)
      ConsumerStrategies.Subscribe[String, String](topics, kafkaParams)
    )


    //迭代DStream中的RDD(KafkaRDD),将每一个时间点对应的RDD取出来
    stream.foreachRDD { rdd =>
      //获取该RDD对应的偏移量
      val offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
      //取出对应的数据
      rdd.foreach{ line =>
        println(line.key() + " " + line.value())
      }

      //异步更新偏移量到kafka中
      // some time later, after outputs have completed
      stream.asInstanceOf[CanCommitOffsets].commitAsync(offsetRanges)
    }
    streamingContext.start()
    streamingContext.awaitTermination()
  }
}

你可能感兴趣的:(kafka,spark)