Kafka 0.10 与 Spark Streaming 流集成在设计上与0.8 Direct Stream 方法类似。它提供了简单的并行性,Kafka分区和Spark分区之间的1:1对应,以及对偏移量和元数据的访问。然而,由于新的集成使用了新的  Kafka consumer API 而不是简单的API,所以在使用方面有显著的差异。这个版本的集成被标记为实验性的,因此API有可能发生变化。

链接:

 对于使用 SBT/maven 定义的 Scala/Java 项目,使用如下的依赖:

groupId = org.apache.spark
artifactId = spark-streaming-kafka-0-10_2.11
version = 2.2.1

不要手动的添加org.apache.kafka 依赖(比如, kafka-clients), spark-streaming-kafka-0-10  已经有了合适的过渡的依赖,不同版本之间的不兼容问题是很难处理的。

Creating a Direct Stream

Note that the namespace for the import includes the version, org.apache.spark.streaming.kafka010

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" -> "use_a_separate_group_id_for_each_stream",
  "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

参考kafka的消费者的配置文档可以看到更多的参数。如果你的Spark 批量的执行时间 比默认的 Kafka的 心跳 会话超时时间大,需要适当的增加 heartbeat.interval.ms 和session.timeout.ms,对于超过5分钟的,batch处理,需要需改broker上的 group.max.session.timeout.ms。注意,例子中设置enable.auto.commit 为 false,详细讨论参考下面的 Offset 存储。

LocationStrategies

新的Kafka consumer API  将会提前获取消息到缓存中。因而这个对于性能是非常重要的,因为 Spark的集成 在executors 保持缓存的consumers 而不是在每个batch上重新创建,并且优先在consumer的本机上调度分区。

在大多数情况下,你应该使用 Locationstrategies.PreferConsistent,如上所示。这将会使得分区平均分不到executors上。如果你的executors 和你的 kafka brokers 在在同一个主机上,使用 PreferBrokers,这种方式 会优先调度 Kafka leader 上的分区。如果你在不同的分区上有一个严重的倾斜,使用PreferFixed。这种方式使得你可以指定一个明确的分区和主机的映射关系(任何未指定的分区将使用一致的位置)。

用户缓存的默认最大大小为64。  如果你希望处理超过 kafka分区数(64* executors 的数量),你可以通过修改

spark.streaming.kafka.consumer.cache.maxCapacity。

如果你希望关闭 Kafka consumers的缓存,你可以设置spark.streaming.kafka.consumer.cache.enabled 为false。

关闭需要解决 SPARK-19185描述的问题。这个属性将会在以后的版本中删除, SPARK-19185。

缓存的key是 按照 topicpartition和group.id,所以每一个createDirectStream需要一个单独的group.id

ConsumerStrategies

新的Kafka consumer API  对特定的topics 有不同的方式,其中的一些需要大量的object实例化设置,消费策略提供了一个抽象,使得Spark 能够获得在checkpoint重启的时候正确配置。

上述的ConsumerStrategies.Subscribe,能够订阅一个固定的topics的集合。SubscribePattern 能够根据你感兴趣的topics进行匹配。需要注意的是,不同于 0.8的集成, 使用subscribe or SubscribePattern 可以支持在运行的streaming中增加分区。Assign 可以指定一个固定的分区集合。这三种策略都有重载构造函数,允许您指定特定分区的起始偏移量。如果你有特定的消费需求,设置不符合上面的选项,consumerstrategy是一个公共类,您可以扩展。

Creating an RDD

如果你有一个适用于batch处理的场景,你可以为一个特定的 offset 范围创建一个 RDD。

// Import dependencies and create kafka params as in Create Direct Stream above

val offsetRanges = Array(
  // topic, partition, inclusive starting offset, exclusive ending offset
  OffsetRange("test", 0, 0, 100),
  OffsetRange("test", 1, 0, 100)
)

val rdd = KafkaUtils.createRDD[String, String](sparkContext, kafkaParams, offsetRanges, PreferConsistent)

需要注意的是,你不能使用

PreferBrokers,因为没有Stream,就没有一个driver上的消费者呢能够自动的查找broker的元数据。如果需要的话,使用PreferFixed来实现元数据的查找。

Obtaining 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}")
  }
}

注意:

HasOffsetRanges 的转化只会在createDirectStream结果的第一个方法中执行成功,后续方法中则不行。要知道,RDD分区和Kafka分区的一对一的映射关系在任何的shuffer和repartion(比如,reduceBykey 或者 window())操作后都无法保持。

Storing Offsets 保存偏移量

Kafka 在失败的时的传输语义 取决于如何和什么时候 存储偏移量。 Spark 输出的操作是 at-least-once。因此,如果想要达到 exactly-once的语义要求,你要么存储在一个幂等操作后存储offsets,或者在一个原子的事物内存储便宜量。为了增加可好性 ,如何存储offsets,这里你有三个选择:checkPoints,Kafka 本身,自己的数据存储

Checkpoints

Kafka itself

Kafka 有一个offset 提交的api, 能够存储特定kafkatopic的offsets。默认情况下,新的消费者会定期自动提交offsets,