Spark Streaming与Kafka集成编程

Spark Streaming是运行在Spark引擎之上实时处理工具,提供可扩展、高吞吐量、具有容错能力实时数据流处理功能,通过把接收的数据流里的数据分隔到一系列的批次,然后由Spark引擎处理生成结果


Spark Streaming提供了高级别的抽象DStream用来表示连续的数据流,DStream其实就是RDDs的序列:


Spark Streaming与Kafka集成因为Kafka在版本0.8与0.10之间引入的新的消费者API,因此有二套相应的相互独立的Spark Streaming与Kafka集成包可供选择使用,

二者的特征比较如下:


Kafka broker version 0.8.2.1 or higher:

针对这个版本,有二种实现:有接收器线程(Receivers)基于Kafka高等级消费者API和无接收器线程(Receivers)基于Kafka简单消费者API
1. Receiver-based Approach
这种实现用基于Kafka高等级消费者API实现的接收器从Kafka接收数据,接收的数据存储在执行器进程中,Spark Streaming启动任务(jobs)处理数据,这种实现在失败情况下会有数据丢失,可通过开启WAL(Write Ahead Logs)同步保存接收的数据到HDFS来保障数据不丢失,
编程实现:
SBT/Maven Linking:

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

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

//控制input DStreams的数量
val numInputDStreams = 5
val kafkaDStreams = (1 to numInputDStreams).map { _ => KafkaUtils.createStream(...) }

//控制每个input DStream上的消费者线程的数量
val ssc: StreamingContext = ??? // ignore for now
val kafkaParams: Map[String, String] = Map("group.id" -> "terran", ...)

val consumerThreadsPerInputDstream = 3
val topics = Map("zerg.hydra" -> consumerThreadsPerInputDstream)
val stream = KafkaUtils.createStream(ssc, kafkaParams, topics, ...)

要点:
1.Kafka的topic分区数与Spark Streaming产生的RDD分区数没有关联关系,所以通过函数KafkaUtils.createStream()增加指定topic的并行数仅仅增加了消费单个topic的接收器接收的数据的线程数,没有增加Spark程序处理数据的并行度,可以通过DStream的repartition方法来设置processing tasks的数量:

val ssc: StreamingContext = ???
val kafkaParams: Map[String, String] = Map("group.id" -> "test", ...)
val readParallelism = 5
val topics = Map("topic1" -> 1)

val kafkaDStreams = (1 to readParallelism).map { _ =>
    KafkaUtils.createStream(ssc, kafkaParams, topics, ...)
  }
  
//collection of five *input* DStreams = handled by five receivers/tasks
val unionDStream = ssc.union(kafkaDStreams) // often unnecessary, just showcasing how to do it
//single DStream
val processingParallelism = 20
val processingDStream = unionDStream.repartition(processingParallelism)// single DStream but now with 20 partitions

Kafka将数据存储在话题中,每个话题都包含了一些可配置数量的分区。话题的分区数量对于性能来说非常重要,而这个值一般是消费者parallelism的最大数量:如果一个话题拥有N个分区,那么你的应用程序最大程度上只能进行N个线程的并行

2.可以通过设置不同的消费组与topic用多个接收器并行接收数据来生成多个Kafka输入DStreams 

3.如果开启WAL(Write Ahead Logs)需设置存储级别为:StorageLevel.MEMORY_AND_DISK_SER(KafkaUtils.createStream(..., StorageLevel.MEMORY_AND_DISK_SER))

架构:



2. Direct Approach (No Receivers)
通过周期性地查询Kafka获取每个topic+partition最新的消息偏移值(offsets)以确定每一个批次处理的偏移值的范围,处理消息的Spark Streaming任务基于Kafka低等级消费者API读取指定范围内的消息数据(类似于从文件系统中读文件)

这种方式的优点:
1.简单的并行度,不需要创建多个输入流然后联合它们,通过directStream,Spark Streaming将创建与kafka相同分区数的RDD分区数来消费数据,它将并行地从kafka读取数据,因此在kafka与rdd分区数据之间有一对一的映射关系
2.高效,Receiver-based Approach为了保障数据不丢失需开启WAL,因为数据要复制二次所以不是很高效,Direct Approach数据不需要复制到Write Ahead Log因此比较高效
3.精确一次的语义,Receiver-based Approach通过Zookeeper来存储消费的offsets,在失败情况下,已被Spark Streaming消费的数据与Zookeeper追踪的offsets可能存在不一致,导致某些数据可能被消费二次,Direct Approach的offsets通过Spark Streaming自身的检查点机制来追踪,消除了不一致,因此能实现精确一次的语义

这种方式的缺点是基于Zookeeper的Kafka监控工具(比如:KafkaOffsetMonitor)不能监控到数据消费的过程

SBT/Maven Linking:

groupId = org.apache.spark
 artifactId = spark-streaming-kafka-0-8_2.11
 version = 2.0.1
代码:
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])

架构:



Kafka broker version 0.10.0 or higher

Spark Streaming同Kafka 0.10的集成同Kafka0.8版本的Direct Stream approach相似,它提供了简单的并行度,Spark分区数与Kafka分区数实现1:1的对应关系,同时能访问消息偏移与元数据,然而,因为它用新的Kafka消费API代替了Kafka老的简单API,因此用法上有显著不同

SBT/Maven Linking:

 groupId = org.apache.spark
 artifactId = spark-streaming-kafka-0-8_2.11
 version = 2.0.1
代码:
//Creating a Direct Stream
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.streaming.kafka010._
import org.apache.spark.streaming.kafka010.LocationStrategies.PreferConsistent
import org.apache.spark.streaming.kafka010.ConsumerStrategies.Subscribe

val kafkaParams = Map[String, Object](
  "bootstrap.servers" -> "localhost:9092,anotherhost:9092",
  "key.deserializer" -> classOf[StringDeserializer],
  "value.deserializer" -> classOf[StringDeserializer],
  "group.id" -> "example",
  "auto.offset.reset" -> "latest",
  "enable.auto.commit" -> (false: java.lang.Boolean)
)

val topics = Array("topicA", "topicB")
val stream = KafkaUtils.createDirectStream[String, String](
  streamingContext,
  PreferConsistent,
  Subscribe[String, String](topics, kafkaParams)
)

stream.map(record => (record.key, record.value))//流里的每条消息是一个ConsumerRecord对象

本地策略(LocationStrategies)
新的Kafka消费者API会提前消费一些数据放入缓存,Spark将在进程中(executors)中缓存消费者线程而不是每一个批次重新创建它们来提高性能,另外优先调度本地分区给合适的消费者,多数情况下,你应该用LocationStrategies.PreferConsistent,它将在可用的执行器进程中均匀地分发数据分区,如果执行器进程与Kafka brokers位于同一台机器则用PreferBrokers,如果在分区之间存在明显的数据倾斜则用PreferFixed,它允许你在主机与分区之是指定显示的映射


获取Offsets

stream.foreachRDD { rdd =>
  val offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
  rdd.foreachPartition { iter =>
    val o: OffsetRange = offsetRanges(TaskContext.get.partitionId)
    println(s"${o.topic} ${o.partition} ${o.fromOffset} ${o.untilOffset}")
  }
}

存储Offsets
有三个选择来存储Offsets:检查点机制、Kafka本身存储、实现自己的数据存储(详情请查阅Spark官方文档)

参考:  
http://spark.apache.org/docs/latest/streaming-kafka-integration.html
http://www.csdn.net/article/2015-01-01/2823384-kafka-spark-streaming-integration-example-tutorial



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值