一、Offset 管理
### --- Offset管理
~~~ Spark Streaming集成Kafka,允许从Kafka中读取一个或者多个 topic 的数据。
~~~ 一个Kafka Topic包含一个或多个分区,每个分区中的消息顺序存储,并使用 offset 来标记消息的位置。
~~~ 开发者可以在 Spark Streaming 应用中通过 offset 来控制数据的读取位置。
~~~ Offsets 管理对于保证流式应用在整个生命周期中数据的连贯性是非常重要的。
~~~ 如果在应用停止或报错退出之前没有将 offset 持久化保存,该信息就会丢失,
~~~ 那么SparkStreaming就没有办法从上次停止或报错的位置继续消费Kafka中的消息。
二、获取偏移量(Obtaining Offsets)
### --- 获取偏移量(Obtaining Offsets)
~~~ Spark Streaming与kafka整合时,允许获取其消费的 offset ,具体方法如下:
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分区之间的对应关系在 shuffle 或 重分区后会丧失,如reduceByKey 或 window。
三、存储偏移量(Storing Offsets)
### --- 存储偏移量(Storing Offsets)
~~~ 在Streaming程序失败的情况下,Kafka交付语义取决于如何以及何时存储偏移量。
~~~ Spark输出操作的语义为 at-least-once。
~~~ 如果要实现EOS语义(Exactly Once Semantics),
~~~ 必须在幂等的输出之后存储偏移量或者 将存储偏移量与输出放在一个事务中。
~~~ 可以按照增加可靠性(和代码复杂度)的顺序使用以下选项来存储偏移量:
### --- Checkpoint
~~~ Checkpoint是对Spark Streaming运行过程中的元数据和每RDDs的数据状态保存到一个持久化系统中,
~~~ 当然这里面也包含了offset,一般是HDFS、S3,如果应用程序或集群挂了,可以迅速恢复。
~~~ 如果Streaming程序的代码变了,重新打包执行就会出现反序列化异常的问题。
~~~ 这是因为Checkpoint首次持久化时会将整个 jar 包序列化,以便重启时恢复。
~~~ 重新打包之后,新旧代码逻辑不同,就会报错或仍然执行旧版代码。
~~~ 要解决这个问题,只能将HDFS上的checkpoint文件删除,但这样也会同时删除Kafka 的offset信息。
### --- Kafka
~~~ 默认情况下,消费者定期自动提交偏移量,
~~~ 它将偏移量存储在一个特殊的Kafka主题中(__consumer_offsets)。
~~~ 但在某些情况下,这将导致问题,因为消息可能已经被消费者从Kafka拉去出来,但是还没被处理。
~~~ 可以将 enable.auto.commit 设置为 false ,在 Streaming 程序输出结果之后,手动提交偏移到kafka。
~~~ # 与HasOffsetRanges一样,只有在createDirectStream的结果上调用时,
~~~ 转换到CanCommitOffsets才会成功,而不是在转换之后。
~~~ commitAsync调用是线程安全的,但必须在输出之后执行。
stream.foreachRDD { rdd => val offsetRanges =
rdd.asInstanceOf[HasOffsetRanges].offsetRanges
// 在输出操作完成之后,手工提交偏移量;此时将偏移量提交到 Kafka 的消息队列中
stream.asInstanceOf[CanCommitOffsets].commitAsync(offsetRanges)
}
四、自定义存储
### --- Offsets可以通过多种方式来管理,但是一般来说遵循下面的步骤:
~~~ 在 DStream 初始化的时候,需要指定每个分区的offset用于从指定位置读取数据
~~~ 读取并处理消息
~~~ 处理完之后存储结果数据
~~~ 用虚线圈存储和提交offset,强调用户可能会执行一系列操作来满足他们更加严
~~~ 格的语义要求。这包括幂等操作和通过原子操作的方式存储offset
~~~ 将 offsets 保存在外部持久化数据库如 HBase、Kafka、HDFS、ZooKeeper、Redis、MySQL ... ...
~~~ # 可以将 Offsets 存储到HDFS中,但这并不是一个好的方案。
~~~ 因为HDFS延迟有点高,此外将每批次数据的offset存储到HDFS中还会带来小文件问题;
~~~ 可以将 Offset 存储到保存ZK中,但是将ZK作为存储用,也并不是一个明智的选择,
~~~ 同时ZK也不适合频繁的读写操作;
![](https://i-blog.csdnimg.cn/blog_migrate/b1e249708a86d8ab14497ed3e531a315.png)