我们的应用程序处理实时流数据,这些数据将被写入镶木地板文件中。我们经常启动一个新的实木复合地板文件,但是由于每秒更新一次,因此需要在输入数据后立即对其进行搜索,因此我们会不断更新“当前”实木复合地板文件。我们正在以原子方式进行这些更新(使用现有数据和新数据生成新的镶木文件,并将其转换为临时文件名,然后通过原子OS调用将文件重命名为现有文件的文件名)。
问题在于,如果我们在上述“半实时”文件中进行搜索,则会遇到错误。
可能无关紧要,但是正在通过AvroBasedParquetWriter.write()写入文件
通过调用SparkSession.read.parquet(path)完成读取 然后,我们将数据框转换为数据集并对其进行计数。 这样做会引发以下异常:
org.apache.spark.SparkException:由于阶段失败,作业被中止: 阶段1699.0中的任务0失败1次,最近一次失败:丢失的任务 阶段1699.0中的0.0(TID 2802,本地主机,执行驱动程序):java.io.IOException:无法读取文件的页脚: FileStatus {path =; isDirectory = false;长度= 418280; 复制= 0;块大小= 0; modify_time = 0; access_time = 0; 所有者=;组=;权限= rw-rw-rw-; isSymlink = false}
我怀疑读取发生的方式不是原子的。就像我们在调用SparkSession.read.parquet()主动读取文件时替换了Parquet文件一样。
这是长寿/非原子的吗?
如果是这样,是否可以通过以下方式锁定镶木地板文件(通过Scala / Java),使得对SparkSession.read.parquet()的调用可以正常播放(即,优雅地等待我释放锁,然后再执行试图从中读取)?
答案 0 :(得分:0)
我不是Spark SQL专家,但是从Parquet和Hive的角度来看,在您描述的场景中我看到两个独立的问题:
实木复合地板不适合流媒体使用。 Avro或文本文件在此方面要好得多,但它们的效率不如Parquet,因此通常的解决方案是将用于短期的面向行的格式与用于长期的面向列的格式混合。在Hive中,可以通过使用Avro或文本文件格式将新数据流式传输到单独的分区中,而将其余分区存储为Parquet来实现。 (我不确定Spark是否支持这种混合方案。)
有时需要压缩流数据。在您描述的方案中,这是在每次写入之后发生的,但是更典型的做法是在某个固定的时间间隔(例如每小时或每天)执行一次,并让新数据以次优格式驻留在它们之间。不幸的是,这在实践中更为复杂,因为没有一些额外的抽象层,压缩就不是原子的,结果,在短时间内,压缩数据要么消失,要么被复制。解决方案是使用一些其他逻辑来确保原子性,例如Hive ACID或Apache Iceberg (incubating)。如果我没记错的话,后者有一个Spark绑定,但是找不到它的链接。
答案 1 :(得分:0)
请参见https://databricks.com/blog/2017/01/19/real-time-streaming-etl-structured-streaming-apache-spark-2-1.html。没有像您这样的方法,它们表明可以同时进行读写。以及旧版本的Spark!采用他们的方法。