Spark Streaming + Kafka Integration Guide (Kafka broker version 0.10.0 or higher)官网翻译

在设计上,Spark Streaming集成Kafka对于 0.10版本的类似于0.8版本(现在只讲Direct Stream,其它不管,这里没有receiver)。
Spark StreamingKafka对于 0.10版本的集成提供了更简化的并行度,在Kafka分区和Spark分区之间是 1:1 的对应关系,能够去访问偏移量和元数据。在有receiver的Spark Streaming里,Spark的分区和Kafka的分区根本不是一回事,但是现在是Direct Stream,那么两个分区就是一样了,一个Spark里的partition去消费一个Kafka里的partition更好。(以前是receiver的方式,现在是Direct Stream的方式,具体可以看上面提到的0.8.2.1官网)。
但是,由于较新的集成使用新的 Kafka consumer API而不是之前的simple API,因此在使用上存在显著的差异。
此版本的集成被标记为实验性的,因此API可能会发生更改。

Linking

依赖

groupId = org.apache.spark
artifactId = spark-streaming-kafka-0-10_2.12
version = 2.4.3

不要手动添加org.apache.kafka相关的依赖,如kafka-clients。spark-streaming-kafka-0-10已经包含相关的依赖了,并且不同版本可能在难以诊断的方式上不兼容。

Creating a Direct Stream

创建一个Direct 流
请注意,导入的命名空间包括版本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的参数,请参考kafka官网。如果,你的spark批次时间超过了kafka的心跳时间(30s),需要增加 heartbeat.interval.mssession.timeout.ms。如果批处理时间是5min,那么就需要调整 group.max.session.timeout.ms。注意,例子中是将enable.auto.commit设置为了false。

LocationStrategies

本地策略

新版本Kafka的消费者API会预先获取消息(数据)到buffer缓冲区。因此,为了提升性能,在Executor端缓存消费者(这些数据)(而不是每个批次重新创建)是非常有必要的,并且优先在拥有适合的消费者所在的主机上调度安排分区。

在大多数情况下,你需要像上面所示,使用LocationStrategies.PreferConsistent,这个参数会将分区尽量均匀地分配到可用的executors上去。如果你的executors和Kafka brokers在相同机器上(生产环境基本不可能),请使用PreferBrokers,它将优先将分区调度到kafka分区leader所在的主机上。最后,分区间负荷有明显的倾斜,请使用PreferFixed。这个允许你指定一个明确的分区到主机的映射(没有指定的分区将会使用连续一致的地址)。

消费者缓存的默认最大值是64。如果你希望处理超过(64 * executors数目)kafka分区数,spark.streaming.kafka.consumer.cache.maxCapacity这个参数可以帮助你修改这个值。

如果你想禁止kafka消费者缓存,可以将spark.streaming.kafka.consumer.cache.enabled修改为false。
Cache是将topicpartition和groupid作为key的,所以每次调用creaDirectStream的时候要使用单独的group.id。

ConsumerStrategies

消费策略

新的Kafka消费者API有许多不同的方法来指定主题,其中一些需要需要考虑post-object-instantiation设置。 ConsumerStrategies提供了一种抽象,它允许spark能够获得正确配置的消费者,即使从Checkpoint重启之后。

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

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

Creating an RDD

创建一个RDD

如果您的用例更适合批处理,则可以为定义的偏移范围创建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的,因为不是流处理的话就没有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}")
  }
}

请注意,只有在createDirectStream结果调用的第一个方法中完成对HasOffsetRanges的类型转换才会成功,而不是在方法链的后面。 请注意,在任何repartition或shuffle操作之后,RDD分区和Kafka分区之间的一对一映射不会保留,例如: reduceByKey()或window()。

Storing Offsets

失败时的Kafka传递语义取决于存储偏移的方式和时间。 Spark输出操作至少一次。 因此,如果你想获得仅仅一次的语义,你要么在幂等输出后存储offset偏移量(主要用这个),要么在原子事务中将offset偏移量与输出一起存储(这个不太用)。
关于语义可以看Spark Streaming官网:
http://spark.apache.org/docs/latest/streaming-programming-guide.html#fault-tolerance-semantics
在这里插入图片描述
通过这种集成,你有3种方式,来提高可靠性(以及代码复杂性),用于存储偏移量。

Checkpoints

(这个没用,不用这个)

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

Kafka itself

Kafka有一个偏移提交API,用于在特殊的Kafka主题中存储偏移量。 默认情况下,新消费者将定期自动提交偏移量。 这几乎肯定不是您想要的,因为消费者成功轮询的消息可能尚未导致Spark输出操作,从而导致未定义的语义。就是说虽然消费了这些数据,但是却没用输出,那么有什么用呢。 就是说消息可能已经被消费者从kafka拉去出来,但是spark还没处理,这种情况下会导致一些错误。这就是上面的stream示例将“enable.auto.commit”设置为false的原因。 但是,您可以使用commitAsync API,在你知道输出已被存储之后,去向Kafka提交偏移量。与checkpoint方式相比,该种方式的好处是Kafka是一个持久化的存储,而不需要考虑代码的更新。然而,Kafka是非事物性的,所以你的输出操作仍需要具有幂等性。

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

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

有了上面代码的hasofsetranges之后,只有在对createdDirectStream的结果调用时(而不是在转换之后),才能成功对canCommitoffset进行强制转换。commitasync调用是threadsafe,但如果需要有意义的语义,则必须在输出之后发生。

Your own data store

对于支持事务的数据存储,可以在同一个事务中保存偏移量,这样即便在失败的情况下也可以保证两者的同步。如果您关心检测重复或跳过的偏移范围,回滚事务可以防止重复或丢失消息影响结果。这等价于仅仅一次性语义。 甚至对于由聚合(通常很难实现等幂)产生的输出也可以使用这种策略

// The details depend on your data store, but the general idea looks like this

// begin from the the offsets committed to the database
val fromOffsets = selectOffsetsFromYourDatabase.map { resultSet =>
  new TopicPartition(resultSet.string("topic"), resultSet.int("partition")) -> resultSet.long("offset")
}.toMap

val stream = KafkaUtils.createDirectStream[String, String](
  streamingContext,
  PreferConsistent,
  Assign[String, String](fromOffsets.keys.toList, kafkaParams, fromOffsets)
)

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

  val results = yourCalculation(rdd)

  // begin your transaction

  // update results
  // update offsets where the end of existing offsets matches the beginning of this batch of offsets
  // assert that offsets were updated correctly

  // end your transaction
}
SSL / TLS

新的kafka消费者支持SSL。开启它,只需要在把值传递给createDirectStream / createRDD之前设置kafkaParams。注意,这仅仅应用与Spark和Kafka brokers之间的通讯;你同样需要保证Spark节点内部之间的安全(Spark安全)通信。

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

For Scala and Java applications, if you are using SBT or Maven for project management, then package spark-streaming-kafka-0-10_2.12 and its dependencies into the application JAR. Make sure spark-core_2.12 and spark-streaming_2.12 are marked as provided dependencies as those are already present in a Spark installation. Then use spark-submit to launch your application (see Deploying section in the main programming guide).
同Spark应用程序一样,spark-submit命令被用来提交你的程序。

对于JAVA或Scala应用程序来说,如果你使用SBT或Maven来做项目管理,需要将spark-streaming-kafka-0-10_2.12包以及它的依赖包添加到你的应用的JAR包中。确保spark-core_2.12包和spark-streaming_2.12包在你的依赖中被标记为provided,因为他们在Spark的安装包中已经提供了。接下来使用spark-submit命令来部署你的应用(详情请看部署环节)。

参考:
http://blog.sina.com.cn/s/blog_6dd718930102y02o.html
必读:Spark与kafka010整合
https://www.e-learn.cn/content/qita/613810

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值