我遇到一个简单的Spark工作问题,在简化后看起来像这样。
JavaRDD<ObjectNode> rdd = pullAndProcessData();
ManifestFilesystem fs = getOutputFS();
List<WriteObjectResult> writeObjectResults = rdd.mapPartitions(fs::write).collect();
fs.writeManifest(Manifest.makeManifest(writeObjectResults));
我对这段代码的期望是,无论发生什么,当且仅当所有任务都完成并且已成功将其分区写入S3时,才会调用writeManifest
。问题是,显然,一些任务是在清单之后写入S3,这应该永远不会发生。
在ManifestFilesystem.write
中,我删除现有的清单(如果有)以使其无效,因为正常的工作流程应为:
我怀疑它可能因为推测的任务而发生,在以下场景中:
collect
的结果返回给驱动程序ManifestTimeslice.write
并在编写分区之前删除清单这有可能发生吗?有没有人对这种行为有另一种假设?
注意:使用内置数据发布方法不是一个选项
注意2:我实际上发现了this,这往往证实了我的直觉,但确认仍然很好,因为我没有使用标准的HDFS或S3读/写出方法的原因超出了本问题的范围。
答案 0 :(得分:1)
Spark不会主动杀死投机任务。它只是等待任务完成并忽略结果。我认为你的推测任务完全有可能在collect
电话后继续写作。
答案 1 :(得分:0)
在从Spark的角度意识到没有办法解决之后我会回答我自己的问题:在你有时间完成之前,你如何确保杀掉所有的投机任务?让它们完全运行实际上更好,否则它们可能会在写入文件时被杀死,然后会被截断。
有不同的可能方法:
this thread中的一些消息表明,一种常见做法是在执行原子重命名之前写入临时尝试文件(大多数文件系统都很便宜,因为它只是一个指针开关)。如果推测任务尝试将其临时文件重命名为现有名称(如果操作是原子的,则不会同时发生),则忽略重命名请求并删除临时文件。
据我所知,S3不提供原子重命名。此外,尽管上述过程相当容易实现,但我们目前正在尝试将自制程序解决方案限制到最大程度,并使系统保持简单。因此,我的最终解决方案是使用jobId
(例如,作业开始的时间戳)并将其传递给从属服务器并将其写入清单中。将文件写入FS时,将应用以下逻辑:
public WriteObjectResult write(File localTempFile, long jobId) {
// cheap operation to check if the manifest is already there
if (manifestsExists()) {
long manifestJobId = Integer.parseInt(getManifestMetadata().get("jobId"));
if (manifestJobId == jobId) {
log.warn("Job " + jobId + " has already completed successfully and published a manifest. Ignoring write request."
return null;
}
log.info("A manifest has already been published by job " + jobId + " for this dataset. Invalidating manifest.");
deleteExistingManifest();
}
return publish(localTempFile);
}