Sparkstreaming and Kafka

简介

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

LINK(依赖)

对于使用SBT / Maven项目定义的Scala / Java应用程序,请将您的流应用程序与以下工件链接起来。

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

不要在org.apache.kafka构件上手动添加依赖项(例如kafka-clients)。 spark-streaming-kafka-0-10已经具有适当的传递依赖性,不同的版本可能在诊断的方式上不兼容。

Creating a Direct Stream

请注意,导入的名称空间包含版本org.apache.spark.streaming.kafka010

 1 import org.apache.kafka.clients.consumer.ConsumerRecord
 2 import org.apache.kafka.common.serialization.StringDeserializer
 3 import org.apache.spark.streaming.kafka010._
 4 import org.apache.spark.streaming.kafka010.LocationStrategies.PreferConsistent
 5 import org.apache.spark.streaming.kafka010.ConsumerStrategies.Subscribe
 6 
 7 val kafkaParams = Map[String, Object](
 8   "bootstrap.servers" -> "localhost:9092,anotherhost:9092",
 9   "key.deserializer" -> classOf[StringDeserializer],
10   "value.deserializer" -> classOf[StringDeserializer],
11   "group.id" -> "use_a_separate_group_id_for_each_stream",
12   "auto.offset.reset" -> "latest",
13   "enable.auto.commit" -> (false: java.lang.Boolean)
14 )
15 
16 val topics = Array("topicA", "topicB")
17 val stream = KafkaUtils.createDirectStream[String, String](
18   streamingContext,
19   PreferConsistent,
20   Subscribe[String, String](topics, kafkaParams)
21 )
22 
23 stream.map(record => (record.key, record.value))

流中的每个项目都是一个ConsumerRecord(消费者记录)

有关可能的kafkaParams,请参阅Kafka使用者配置文档。 如果您的Spark批处理持续时间大于默认Kafka心跳会话超时(30秒),请适当增加heartbeat.interval.ms和session.timeout.ms。 对于大于5分钟的批次,这将需要更改代理上的group.max.session.timeout.ms。 请注意,该示例将enable.auto.commit设置为false,有关讨论,请参阅下面的“存储偏移量”。

LocationStrategies(位置策略)

新的Kafka消费者API将预取消息到缓冲区中。因此,性能方面的原因是Spark集成将缓存的消费者保留在执行者上(而不是为每个批次重新创建它们),并且倾向于在具有相应使用者的主机位置上调度分区。

在大多数情况下,您应该使用LocationStrategies.PreferConsistent,如上所示。这会将分区平均分配给可用的执行者。如果您的执行者(executors )与您的Kafka经纪人(brokers)位于同一个主机上,请使用PreferBrokers,它将优先为该分区的Kafka领导安排分区。最后,如果分区间负载存在明显偏移,请使用PreferFixed。这允许您指定分区到主机的明确映射(任何未指定的分区将使用一致的位置)。

消费者缓存的默认最大大小为64.如果您希望处理多于(执行者数量为64 *)的Kafka分区,则可以通过spark.streaming.kafka.consumer.cache.maxCapacity更改此设置。

如果您想禁用Kafka使用者的缓存,则可以将spark.streaming.kafka.consumer.cache.enabled设置为false。可能需要禁用缓存来解决SPARK-19185中描述的问题。 SPARK-19185解决后,该属性可能会在更高版本的Spark中删除。

缓存由topicpartition和group.id键入,因此每次调用createDirectStream时都要使用一个单独的group.id。

ConsumerStrategies(消费者策略)

新的Kafka消费者API有许多不同的方式来指定主题,其中一些需要大量的后对象实例化设置。 ConsumerStrategies提供了一个抽象,允许Spark从检查点重新启动后即可获得正确配置的使用者。

ConsumerStrategies.Subscribe,如上所示,允许您订阅固定的主题集合。 SubscribePattern允许您使用正则表达式来指定感兴趣的主题。 请注意,与0.8集成不同,使用Subscribe或SubscribePattern应该在正在运行的流中添加分区。 最后,Assign允许你指定一个固定的分区集合。 所有这三种策略都有重载的构造函数,允许您指定特定分区的起始偏移量。

如果您具有以上选项不能满足的特定消费者设置需求,则ConsumerStrategy是可以扩展的公共类。

Creating an RDD

如果您有一个更适合批处理的用例,则可以为已定义的偏移范围创建一个RDD。

1 // Import dependencies and create kafka params as in Create Direct Stream above
2 
3 val offsetRanges = Array(
4   // topic, partition, inclusive starting offset, exclusive ending offset
5   OffsetRange("test", 0, 0, 100),
6   OffsetRange("test", 1, 0, 100)
7 )
8 
9 val rdd = KafkaUtils.createRDD[String, String](sparkContext, kafkaParams, offsetRanges, PreferConsistent)

请注意,您不能使用PreferBrokers,因为如果没有流,则不存在驱动程序方面的消费者为您自动查找代理元数据。 如有必要,请使用PreferFixed与您自己的元数据查找。

Obtaining Offsets

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 }

请注意,HasOffsetRanges的类型转换只会在createDirectStream的结果中调用的第一个方法中完成,而不是稍后向下的一系列方法。 请注意,RDD分区与Kafka分区之间的一对一映射在任何混洗或重新分区的方法(例如, reduceByKey()或window()。

Storing Offsets

Kafka交付语义在失败的情况下取决于如何以及何时存储偏移量。 spark输出操作至少一次。 所以,如果你想要相当于一次的语义,你必须在幂等输出之后存储偏移量,或者在输出中存储原子事务的偏移量。 通过这种集成,您可以选择3个选项,以提高可靠性(和代码复杂度),以便如何存储偏移量。

Checkpoints

如果启用了Spark检查点,偏移量将被存储在检查点中。 这很容易启用,但也有缺点。 你的输出操作必须是幂等的,因为你将得到重复的输出; 转换不是一种选择。 此外,如果应用程序代码已更改,则无法从检查点恢复。 对于计划升级,可以通过在旧代码的同时运行新代码来缓解这种情况(因为无论如何输出需要是幂等的,它们不应该发生冲突)。 但是对于需要更改代码的意外故障,除非您有另外的方法来识别已知的良好起始偏移量,否则将会丢失数据。

Kafka itself

Kafka有一个偏移提交API,用于在特殊的Kafka主题中存储偏移量。 默认情况下,新的使用者将定期自动提交偏移量。 这几乎肯定不是你想要的,因为由消费者成功轮询的消息可能还没有导致Spark输出操作,导致未定义的语义。 这就是为什么上面的流示例将“enable.auto.commit”设置为false的原因。 但是,在知道输出已存储之后,可以使用commitAsync API将偏移量提交给Kafka。 与检查点相比,好处在于,无论应用程序代码如何变化,Kafka都是耐用的商店。 然而,Kafka不是转换型的,所以你的输出仍然是幂等的。

1 stream.foreachRDD { rdd =>
2   val offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
3 
4   // some time later, after outputs have completed
5   stream.asInstanceOf[CanCommitOffsets].commitAsync(offsetRanges)
6 }

和HasOffsetRanges一样,如果调用createDirectStream的结果,而不是在转换之后,转换为CanCommitOffsets将会成功。 commitAsync调用是线程安全的,但是如果你想要有意义的语义,则必须在输出之后进行。

Your own data store

对于支持事务的数据存储,即使在出现故障的情况下,也可以在同一个事务中保存偏移量,以保持两者同步。 如果仔细检测重复或跳过的偏移量范围,回滚事务将防止重复或丢失的消息影响结果。 这给出了相当于一次的语义。 甚至可以使用这种策略,即使对于通常难以产生幂等性的聚合产生的输出也是如此。

 1 // The details depend on your data store, but the general idea looks like this
 2 
 3 // begin from the the offsets committed to the database
 4 val fromOffsets = selectOffsetsFromYourDatabase.map { resultSet =>
 5   new TopicPartition(resultSet.string("topic"), resultSet.int("partition")) -> resultSet.long("offset")
 6 }.toMap
 7 
 8 val stream = KafkaUtils.createDirectStream[String, String](
 9   streamingContext,
10   PreferConsistent,
11   Assign[String, String](fromOffsets.keys.toList, kafkaParams, fromOffsets)
12 )
13 
14 stream.foreachRDD { rdd =>
15   val offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
16 
17   val results = yourCalculation(rdd)
18 
19   // begin your transaction
20 
21   // update results
22   // update offsets where the end of existing offsets matches the beginning of this batch of offsets
23   // assert that offsets were updated correctly
24 
25   // end your transaction
26 }

SSL / TLS

新的Kafka使用者支持SSL。 要启用它,请在传递给createDirectStream / createRDD之前适当地设置kafkaParams。 请注意,这仅适用于Spark和Kafkabroker之间的沟通; 您仍负责单独确保Spark节点间通信。

1 val kafkaParams = Map[String, Object](
2   // the usual params, make sure to change the port in bootstrap.servers if 9092 is not TLS
3   "security.protocol" -> "SSL",
4   "ssl.truststore.location" -> "/some-directory/kafka.client.truststore.jks",
5   "ssl.truststore.password" -> "test1234",
6   "ssl.keystore.location" -> "/some-directory/kafka.client.keystore.jks",
7   "ssl.keystore.password" -> "test1234",
8   "ssl.key.password" -> "test1234"
9 )

Deploying

与任何Spark应用程序一样,spark-submit用于启动您的应用程序。

对于Scala和Java应用程序,如果您使用SBT或Maven进行项目管理,请将spark-streaming-kafka-0-10_2.11及其依赖项打包到应用程序JAR中。 确保spark-core_2.11和spark-streaming_2.11被标记为提供的依赖关系,因为这些已经存在于Spark安装中。 然后使用spark-submit启动您的应用程序(请参阅主编程指南中的部署)。

 
posted @ 2017-11-07 15:25  gyhuminyan  阅读(1936)  评论(3编辑  收藏  举报