spark 连接kafka API 各参数详细讲解

  • 一 Spark连接Kafka的两种方式比较
  • 二 0.8,0.10以及更高版本的Kafka
  •     如果spark的批次时间batchTime超过了kafka的心跳时间(30s),需要增加hearbeat.interval.ms以及session.timeout.ms。加入batchTime是5min,那么就需要调整group.max.session.timeout.ms。
  • 2.1 KafkaUtils.creatDirectStream参数
  • 2.1.1 ssc,StreamingContext
  • 2.1.2 locationStrategy: LocationStrategy
  • 2.1.4 kafkaParams
  • 2.2 根据指定offset区间获取RDD
  • 2.3 偏移量操作
  • 三 0.8之前版本 


一 Spark连接Kafka的两种方式比较

  •     Kafka consumer传统消费者(老方式)需要连接zookeeper,简称Receiver方式,是高级的消费API,自动更新偏移量,支持WAL,但是效率比较低。
  •     新的方式(高效的方式)不需要连接Zookeeper,但是需要自己维护偏移量,简称直连方式,直接连载broker上,但是需要手动维护偏移量,以迭代器的方式边接收数据边处理,效率较高。    

    consumer的一个group下可以有多个消费者,同时消费但是不会出现重复消费数据的情况。Kafka0.8,0.10只支持直连方式

二 0.8,0.10以及更高版本的Kafka

    直连方式->

和基于Receiver方式相比,这种方式主要有一些几个优点: 

  • 简化并行。我们不需要创建多个 Kafka 输入流,然后 union 他们。而使用 directStream,Spark Streaming 将会创建和 Kafka 分区一样的 RDD 分区个数,而且会从 Kafka 并行地读取数据,也就是说Spark 分区将会和 Kafka 分区有一一对应的关系,这对我们来说很容易理解和使用; 
  • 高效。第一种实现零数据丢失是通过将数据预先保存在 WAL 中,这将会复制一遍数据,这种方式实际上很不高效,因为这导致了数据被拷贝两次:一次是被 Kafka 复制;另一次是写到 WAL 中。但是 Direct API 方法因为没有 Receiver,从而消除了这个问题,所以不需要 WAL 日志; 
  • 恰好一次语义(Exactly-once semantics)。通过使用 Kafka 高层次的 API 把偏移量写入 Zookeeper 中,这是读取 Kafka 中数据的传统方法。虽然这种方法可以保证零数据丢失,但是还是存在一些情况导致数据会丢失,因为在失败情况下通过 Spark Streaming 读取偏移量和 Zookeeper 中存储的偏移量可能不一致。而 Direct API 方法是通过 Kafka 低层次的 API,并没有使用到 Zookeeper,偏移量仅仅被 Spark Streaming 保存在 Checkpoint 中。这就消除了 Spark Streaming 和 Zookeeper 中偏移量的不一致,而且可以保证每个记录仅仅被 Spark Streaming 读取一次,即使是出现故障。但是本方法唯一的坏处就是没有更新 Zookeeper 中的偏移量,所以基于 Zookeeper 的 Kafka 监控工具将会无法显示消费的状况。然而你可以通过 Spark 提供的 API 手动地将偏移量写入到 Zookeeper 中。也没有办法运行监控工具。

    连接:需要添加spark-streaming-kafka-0-10_2.11的包

1.     <dependency>
2.       <groupId>org.apache.spark</groupId>
3.       <artifactId>spark-streaming-kafka-0-10_2.11</artifactId>
4.       <version>${spark.version}</version>
5.     </dependency>

    不需要手动添加kafka-clients等的包,已经包含了相关依赖,不同版本会有不同的兼容性。        

1. import org.apache.kafka.common.serialization.StringDeserializer
2. import org.apache.spark.streaming.{Seconds,StreamingContext,kafka010}
3. import org.apache.spark.streaming.kafka010.ConsumerStrategies.Subscribe
4. import org.apache.spark.streaming.kafka010.KafkaUtils
5. import org.apache.spark.streaming.kafka010.LocationStrategies.PreferConsistent
6.  
7.  
8. val ssc=new StreamingContext("local[4]"),"StatStreamingApp",Seconds(5))
9. val KafkaParams=Map[String,Object](
10.     "bootstrap.servers"->"broker1:9092,broker2:9092",
11.     "key.deserializer"->classOf[StringDeserializer],
12.     "value.deserilazier"->classOf[StringDeserializer],
13.     "group.id"->"groupName",
14.     "auto.offset.reset"->"latest",
15.     "enable.auto.commit"->(false:java.lang.Boolean)    )
16. val topics=List("topic1,topic2")
17. val lines=KafkaUtils.createDirectStream[String,String](
18.     ssc,PreferConsistent,Subscribe[String,String](topics,kakfaParmas))
19.     .map(_.value())或.map(_._2)
20. ssc.start()
21. ssc.awaitTermination()

    如果spark的批次时间batchTime超过了kafka的心跳时间(30s),需要增加hearbeat.interval.ms以及session.timeout.ms。加入batchTime是5min,那么就需要调整group.max.session.timeout.ms。

    消费者缓存默认为最大64条,如果希望处理超过(64*executor数量)kafka的分区,可以调节spark.streaming.kafka.consumer.cache.maxCapacity 这个参数。另外,可以调节spark.streaming.kafka.consumer.cache.enable=false来禁止缓存,可以解决Spark-19185的bug。

2.1 KafkaUtils.creatDirectStream参数

    需要传入三个参数

2.1.1 ssc,StreamingContext

2.1.2 locationStrategy: LocationStrategy

    表示kafka分区的分配策略,上述的例子中直接传入了org.apache.spark.streaming.kafka010.LocationStrategies.PreferConsistent,这个类,有四种选项:

  • PreferBrokers:只有executors和kafkaBroker在同一个结点时候才能使用
  • PreferConsistent:一般使用该项,将会一致接收所有分区的数据到executors上。
  • PreferFixed(hostMap:Map[TopicPartition,String]):如果负载不均衡的情况下,可以通过该选项将制定分区的数据分发到制定的主机上。如果map中没有任何指定,那么将会使用PreferConsistent选项。
  • PreferFiexed(hostMap:ju.Map[TopicPartition,String]):与上一条类似,但是传入的是java的Map。ju代表import java.{ util => ju }, 是java.util.

2.1.3 consumerStrategy: ConsumerStrategy[K, V]

    表示消费者的一些配置参数,需要使用org.apache.spark.streaming.kafka010.ConsumerStrategies.Subscribe类来进行包装,一般只需要传入topics和kafkaParams即可,但是也有一些重载方法可以使用。

  • Subscribe[K,V](topics:Iterable[jl.String],kafkaParams:collection.Map[String,Object]):ConsumerStrategy[K,V]
  • Subscribe[K,V](topics:Iterable[String],kafkaParams:collection.Map[String,Object],offsets:collection.Map[TopicPartition,Long]):ConsumerStrategy[K,V]

可以传入一个OffsetMap,指定不同的分区应该从什么位置开始读取数据。默认使用第一个就可以了。

2.1.4 kafkaParams

    这个参数是在consumerStrategy中需要传入的,是一个Map,可以包括的参数有:

链接:http://kafka.apache.org/documentation.html#newconsumerconfigs

其中bootstrap.servers是必须被设置的

2.2 根据指定offset区间获取RDD

1. import scala.collection.JavaConversions._
2. import java.{util=>ju}
3. //OffsetRange(topic名称,partition下标,offset开始下标,offset结束下标)
4.     val offsetRanges=Array(OffsetRange("kylin_streaming_topic",0,0,100))
5.     //将scalaMap转为java.util.Map,createRDD只支持javaMap的配置参数
6.     val juMap:ju.Map[String,Object]=kafkaParams
7.     val rdd=KafkaUtils.createRDD(ssc.sparkContext,juMap,offsetRanges,PreferConsistent)
8.     rdd.foreach(println)

    上述代码实现了获取指定topic中第一个partition从0到100offset的数据。并且是以RDD方式接受而不是DStream。

2.3 偏移量操作

2.3.1 获取偏移

1. stream.foreachRDD{rdd=>{

2.     val offsetRanges=rdd.asInstanceOf[HasOffsetRanges].offsetRanges
3.     rdd.foreachPartition{iter=>
4.         val o:OffsetRange=offsetRanges(TaskContext.get.partitionId)
5.         println(s"${o.topic} ${o.partition} ${o.fromOffset} ${o.untilOffset}")
6.     }
7. }

    注意:HashOffsetRanges仅在spark计算链条的开始才能类型转换成功,kafka分区和spark分区一一对应的关系在shuffle之后就会丧失,比如reduceByKey()或者window()。

2.3.2 提交偏移量

    一般情况下,我们都会将enable.auto.commit设置为false,不让spark自动提交偏移量,因为在获取kafka数据之后,我们不能确保数据已经被成功处理并且被没输出或者存储了。可以使用commitAsync来提交偏移量。

1. stream.foreachRDD{rdd=>{

2.  val offsetRanges=rdd.asInstanceOf[HasOffsetRanges].offsetRanges
3.  stream.asInstanceOf[CancommitOffsets].commitAsync(offsetRanges)
4. }
5. }

2.4 补充

    消费者默认的最大缓存为64k,如果希望处理大于(64*executor)的数量的kafka分区,可以手动配置spark.streaming.kafka.consumer.cache.maxCapacity.

    可以使用spark.streaming.kafka.consumer.cache.enabled=false。

三 0.8之前版本 

    之前的版本支持createDirecStream和createDStream方式,使用KafkaUtils.createDstream来创建dstream,使用receivers来接收数据,利用的是Kafka高层次消费者api,对于所有的receivers接收到的数据将会保存到Spark executors中,然后通过SparkStreaming启动job来处理这些数据,默认可能会出现丢失,可以使用WAL预写日志,将入职存储到HDFS来解决。

1.  val ssc=new StreamingContext("local[4]"),"StatStreamingApp",Seconds(5))
2.  
3.  val zkQuorum="master:2181,node1:2181,node2:2181"
4.  val groupId="groupName"
5.  val topicMap=Map("topic1"->1,"topic2"->2)
6.  val lines=KafkaUtils.createStream(ssc,zkQuorum,groupId,topicMap)
7.  line.map(_._2).count().print()
8.  
9.  ssc.start()
10. ssc.awaitTermination()