在从kafka接受流式数据的时候,spark提供了两种方式,Dstream和DirectStream,在spark2.2中已经不在提供第一种方式,具体区别这儿就不再描述了,第二种方式spark是用的kafka低阶api,每个RDD对应一个topic的分区,这种情况,需要借助于外部存储来管理offset,或者简单点,自己手动利用kafka来管理offset,否则在程序重启时找不到offset从最新的开始消费,会有丢失数据的情况。一般步骤如下:
- 在 Direct DStream初始化的时候,需要指定一个包含每个topic的每个分区的offset用于让Direct DStream从指定位置读取数据。
- 读取并处理消息
- 处理完之后存储结果数据
- 最后,将offsets保存在外部持久化数据库如 HBase, Kafka, HDFS, and ZooKeeper中
一、kafka管理offset
Apache Spark 2.1.x以及spark-streaming-kafka-0-10使用新的的消费者API即异步提交API。你可以在你确保你处理后的数据已经妥善保存之后使用commitAsync API(异步提交 API)来向Kafka提交offsets。新的消费者API会以消费者组id作为唯一标识来提交offsets,将offsets提交到Kafka中。目前这还是实验性特性。
stream.foreachRDD { rdd =>
val offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
// some time later, after outputs have completed
stream.asInstanceOf[CanCommitOffsets].commitAsync(offsetRanges)
}
二、zookeeper管理offset
在初始化 kafka stream 的时候,查看 zookeeper 中是否保存有 offset,有就从该 offset 进行读取,没有就从最新/旧进行读取。在消费 kafka 数据的同时,将每个 partition 的 offset 保存到 zookeeper 中进行备份
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("spark-streaming")
val ssc = new StreamingContext(sparkConf, Seconds(10))
val topic: String = "test"
val kafkaParams = Map[String, Object](
"bootstrap.servers" -> "xxx.xxx.xxx.xxx:9092",
"key.deserializer" -> classOf[StringDeserializer],
"value.deserializer" -> classOf[StringDeserializer],
"group.id" -> "spark-streaming-06",
"auto.offset.reset" -> "earliest",
"enable.auto.commit" -> (false: java.lang.Boolean)
)
var kafkaStream: InputDStream[ConsumerRecord[String, String]] = null
val zkClient = new ZkClient("XXX.XXX.XXX.XXX")
var fromOffsets: Map[TopicPartition, Long] = Map()
val children = zkClient.countChildren("offsetDir")
if (children > 0) {
for (i <- 0 until children) {
val partitionOffset = zkClient.readData[String]("offsetDir" + "/" + i)
val tp = new TopicPartition(topic, i)
fromOffsets += (tp -> partitionOffset.toLong)
kafkaStream = KafkaUtils.createDirectStream[String, String](
ssc, PreferConsistent, Subscribe[String, String](Set(topic), kafkaParams, fromOffsets)
)
}
} else {
kafkaStream = KafkaUtils.createDirectStream[String, String](
ssc, PreferConsistent, Subscribe[String, String](Set(topic), kafkaParams)
)
}