一、问题背景
使用SparkSession加载kafka流数据,调试时加载kafka的offset都是设置为earliest,每次重启时删除checkpoint文件
Dataset<Row> dataset = this.sparkSession.readStream().format("kafka")
.options(this.getSparkKafkaCommonOptions(sparkSession))
.option("kafka.bootstrap.servers", ConfigConstMdt.MDT_STREAMING_KAFKA_SERVERS)
.option("subscribe", ConfigConstMdt.MDT_STREAMING_KAFKA_TOPICS)
.option("startingOffsets", ConfigConstMdt.MDT_STREAMING_KAFKA_OFFSETS)
.load();
用以下方式写出处理结果到HDFS
StreamingQuery query = queryResult.writeStream().format("parquet")
.option("path", outputPath)
.option("checkpointLocation", ConfigConstMdt.MDT_STREAMING_PARSE_CHECKPOINT_DIR)
.partitionBy("month", "day", "hour")
.outputMode(OutputMode.Append())
.trigger(Trigger.ProcessingTime(1, TimeUnit.MINUTES))
.start();
二、问题现象
如下图,每个批次显示的读取kafka的偏移都在增加,但numInputRows为0,过很长时间numInputRows才开始不为0,也就是才开始处理数据。
三、问题原因
1、删除checkpoint之后,启动以后从currentBatchId=0开始处理
private def populateStartOffsets(sparkSessionToRunBatches: SparkSession): Unit = {
offsetLog.getLatest() match {
case Some((latestBatchId, nextOffsets)) =>
......
// checkpoint文件被删除,会匹配到这里
case None => // We are starting this stream for the first time.
logInfo(s"Starting new streaming query.")
currentBatchId = 0
watermarkTracker = WatermarkTracker(sparkSessionToRunBatches.conf)
}
}
看看offsetLog,以下代码说明批次加载要先去访问checkpoint文件记录计算起始偏移
val offsetLog = new OffsetSeqLog(sparkSession, checkpointFile("offsets"))
val commitLog = new CommitLog(sparkSession, checkpointFile("commits"))
2、我们再看下FileSink写出时会怎么做
override def addBatch(batchId: Long, data: DataFrame): Unit = {
// currentBatchId和获取到的已经处理过的最大批次ID进行比较,如果currentBatchId值比获取到的ID小,则跳过该批次ID的处
// 理。
if (batchId <= fileLog.getLatest().map(_._1).getOrElse(-1L)) {
logInfo(s"Skipping already committed batch $batchId")
} else {
......
FileFormatWriter.write(
sparkSession = sparkSession,
plan = qe.executedPlan,
fileFormat = fileFormat,
committer = committer,
outputSpec = FileFormatWriter.OutputSpec(path, Map.empty, qe.analyzed.output),
hadoopConf = hadoopConf,
partitionColumns = partitionColumns,
bucketSpec = None,
statsTrackers = Seq(basicWriteJobStatsTracker),
options = options)
}
}
注意:这里的fileLog.getLatest()是读取_spark_metadata这个目录下的文件获取到的
四、结论
在以MicroBatch的方式进行流式处理写出到hdfs时(也就是FileSink时),如果重启时checkpoint文件不存在,则从批次0开始处理,然后进行批次数据写出前,会先去输出路径下的_spark_metadata这个目录获取已经处理过的最大批次ID,以避免重复处理相同批次。所以在重启时,如果删除了checkpoint目录,而没有删除之前的输出,便会出现二中的加载不到kafka数据的现象。