sparkstreaming和kafka0.10版本整合

sparkstreaming和kafka0.10版本整合

标签(空格分隔): 未分类

  • sparkstreaming和kafka010版本整合
  • Maven依赖
  • 创建directstream
  • LocationStrategies
  • ConsumerStrategies
  • 创建RDD
  • 获取Offsets
  • 存储offset
    • 在checkpoint中存储
    • 在kafka中存储

参考链接
sparkstreaming集成kafka0.10版本和集成0.8版本在direct stream上方法是一致的。提供了简单的并行度,即1:1于kafka的partition和spark的partition,直接访问offset和元数据。但是,因为最新的集成使用了kafka新的消费者api,而没有使用simple api,所以在使用时又比较大的区别。这个版本的集成是实验性质的,所以api可能会变化。

Maven依赖

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

不要手动的给org.apache.kafka artifacts (e.g. kafka-clients)添加依赖,spark-streaming-kafka-0-10已经通过透传机制有了合适的依赖,版本不匹配的依赖可能会产生很多兼容性问题而且不好调试

创建directstream

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))

流中得每个item都是一个consumerrecored。对于可能用到的kafka参数,可以看看kafka消费者配置文档。如果spark的bath时间大于默认的kafka心跳session超时时间(30s),需要分别增加heartbeat.interval.ms和session.timeout.ms appropriately。如果batch时间大于5分钟,就需要修改broker上得group.max.session.timeout.ms配置。注意代码中把enable.auto.commit设置为false的原因请参考下边的offset存储。

LocationStrategies

新的kafka消费者api会从buffer中预取消息。因此为了性能考虑,spark集成时在executor上会cache消费者(而不是每个batch都重新创建一个消费者),而且会选择具有合适consumer的host进行调度。

大多数情况下,需要使用上边的LocationStrategies.PreferConsistent,这回把partition平均分配到可用的executor上。如果executor和kafka的broker在同一台机器上,就要使用PreferBrokers,这样which will prefer to schedule partitions on the Kafka leader for that partition。最后,如果在partition中又严重的倾斜,使用PreferFixed,这样就可以指定某些partition映射到特定主机上(any unspecified partitions will use a consistent location)

consumer的cache的默认最大值是64。如果想处理多于(64 * number of executors)的kafka partition,可以修改spark.streaming.kafka.consumer.cache.maxCapacity这个参数

如果把kafka的消费者cache关掉,可以设置spark.streaming.kafka.consumer.cache.enabled为false。把cache关掉就会遇到SPARK-19185。如果这个问题在SPARK-19185被解决到,这个配置项在后期版本中就会被废弃掉。

cache的key是topicpartition和 group.id,所以每次调用createDirectStream时需要使用不同的group.id。????

ConsumerStrategies

新的kafka消费者api有很多不同的方法来指定topic,其中某些方法需要很多post-object-instantiation的建立。消费者策略提供和一种抽象,这种抽象允许spark即使从checkpouint中重启也能获取到正确配置的消费者。

ConsumerStrategies.Subscribe允许用户提交固定数量的topic集合,SubscribePattern则允许用户使用正则来指定感兴趣的topic。注意,不像在0.8中得集成那样,使用Subscribe或者SubscribePattern对正在运行的stream增加partition会有相应(不是很理解)。最后,Assign允许用户指定固定集合的partition。以上三种策略都有重载的构造方法允许用户给特定的partition指定消费的起始offset。

如果用户以上的三种方式都无法满足用户建立特定类型消费者的需求,可以扩展ConsumerStrategy这个基类来自己实现。

创建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侧的consumer来自动寻找broker元数据。如有必要,在用户自己的元数据查找中使用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}")
  }
}

注意的是:类型转换成HasOffsetRanges只有在createDirectStream的第一个方法被调用的时候才能成功,不是后续的方法链。当执行了shuffle或者repartition后,rdd的partition和kafka的partition的一对一的关系就不存在了

存储offset

kafka发送语义在失败后依赖于offset合适以什么样的方式存储。spark output操作at-least-once的,所以如果想实现exactly-once语义,要么在一个幂等得输出后存储offset,要么和输出一起以原子的事务方式存储offset。在这里讲的集成方法中,按照可靠性递增排序,用户有3个方法来存储offset。

在checkpoint中存储

如果开启了checkpoint,offset就会被存储在checkpoint中。这种方法比较方便,但是也有缺陷,输出操作必须是幂等的,因为用户会得到重复的输出结果;并且事务不是可选的(必选的?)。而且,如果用户的代码变化了,就不能从checkpoint中恢复。如果要升级,用户可以通过让旧代码和新代码同时运行的方式来缓解这个问题(因为输出无论如何都要是幂等的,所以这两种代码不会冲突(这块需要好好理解一下))。但是对于无计划的失败场景(这种场景需要代码变化),除非用户有其他方法鉴别出正确的起始offset,否则就会有数据丢失。

在kafka中存储

kafka有一个offset提交api,这个api把特定的kafka topic的offset进行存储。默认情况下,新的消费者会周期性的自动提交offset,这肯定不是用于想要的,因为消费者成功poll到得消息可能并不能在spark中计算出结果,这就会产生无法预期的语义来。这就是为什么上边的例子中把enable.auto.commit设置为false。但是,使用commitAsync api,用户可以在确保计算结果被成功保存后自己来提交offset。和使用checkpoint方式相比,kafka是一个不用关心应用代码的变化可靠存储系统。但是,kafka不是事务性的,所以输出还是需要是幂等的。

stream.foreachRDD { rdd =>
  val offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges

  // some time later, after outputs have completed
  stream.asInstanceOf[CanCommitOffsets].commitAsync(offsetRanges)
}

至于HasOffsetRanges,强制转化成CanCommitOffsets只有在调用createDirectStream后会成功,而不是在后续的transformations中。调用commitAsync是线程安全的,但是如果用户想要得到有意义的语义,就必须要输出后调用。

你可能感兴趣的:(实时计算)