Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/apache-spark/6.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Apache spark 读取拼花地板文件时刷新Dataframe的元数据_Apache Spark_Apache Spark Sql_Parquet_Apache Spark Dataset - Fatal编程技术网

Apache spark 读取拼花地板文件时刷新Dataframe的元数据

Apache spark 读取拼花地板文件时刷新Dataframe的元数据,apache-spark,apache-spark-sql,parquet,apache-spark-dataset,Apache Spark,Apache Spark Sql,Parquet,Apache Spark Dataset,我试图将拼花地板文件读取为数据帧,该数据帧将定期更新(路径为/folder\u name。每当出现新数据时,旧拼花地板文件路径(/folder\u name)将重命名为临时路径,然后我们合并新数据和旧数据,并将存储在旧路径中(/folder\u name) 假设我们有一个拼花地板文件hdfs://folder_name/part-xxxx-xxx.snappy.parquet更新之前和更新之后,它将更改为hdfs://folder_name/part-00000-yyyy-yyy.snappy.

我试图将拼花地板文件读取为数据帧,该数据帧将定期更新(路径为
/folder\u name
。每当出现新数据时,旧拼花地板文件路径(
/folder\u name
)将重命名为临时路径,然后我们合并新数据和旧数据,并将存储在旧路径中(
/folder\u name

假设我们有一个拼花地板文件
hdfs://folder_name/part-xxxx-xxx.snappy.parquet
更新之前和更新之后,它将更改为
hdfs://folder_name/part-00000-yyyy-yyy.snappy.parquet

问题是在更新完成时,我试图读取拼花地板文件

sparksession.read.parquet(“filename”)=>它采用旧路径
hdfs://folder_name/part-xxxx-xxx.snappy.parquet
(路径存在)

在数据帧上调用操作时,它试图从
hdfs://folder_name/part-xxxx-xxx.snappy.parquet
但由于更新,文件名发生了更改,我收到了以下问题

java.io.FileNotFoundException:文件不存在:
hdfs://folder_name/part-xxxx-xxx.snappy.parquet
底层文件可能已更新。您可以通过在SQL中运行“REFRESH TABLE tableName”命令或重新创建所涉及的数据集/数据帧,在Spark中显式使缓存无效

我正在使用Spark 2.2

有人能帮我刷新元数据吗

  • 一个简单的解决方案是首先使用df.cache.count引入内存,然后与新数据合并,并使用模式
    覆盖
    写入
    /folder\u name
    。在这种情况下,您不必使用
    临时路径

  • 您提到要将
    /folder\u name
    重命名为某个临时路径。因此,您应该从该临时路径读取旧数据,而不是
    hdfs://folder_name/part-xxxx-xxx.snappy.parquet


  • 当您试图读取不存在的文件时,会发生此错误

    如果我错了,请纠正我,但我怀疑您在保存新数据帧(使用
    .mode(“overwrite”)
    )时正在覆盖所有文件。当此进程运行时,您正在尝试读取已删除的文件并引发异常-这会使表在一段时间内(在更新期间)不可用

    据我所知,没有一种直接的方法可以像您所希望的那样“刷新元数据”

    解决此问题的两种(几种可能的)方法:

    1-使用附加模式 如果您只想将新数据框附加到旧数据框,则无需创建临时文件夹并覆盖旧数据框。您只需将保存模式从“覆盖”更改为“附加”。这样,您就可以向现有拼花地板文件添加分区,而无需重写现有分区

    df.write
      .mode("append")
      .parquet("/temp_table")
    
    这是迄今为止最简单的解决方案,不需要读取已存储的数据。但是,如果必须更新旧数据(例如:如果正在进行upsert),则此方法将不起作用。为此,您可以选择2:

    2-使用配置单元视图 您可以创建配置单元表,并使用视图指向最新(可用)的配置单元表

    下面是一个关于此方法背后逻辑的示例:

    第1部分

    • 如果视图
      不存在,我们将创建一个名为
      \u alpha0
      存储新数据
    • 创建表之后 我们创建一个视图
      作为
      select*from
      _alpha0
    第二部分

    • 如果视图存在,我们需要查看它指向哪个表

    • 您可以使用新数据执行所有需要的操作,并将其保存为名为
      \u alpha(N+1)

    • 创建表后,我们将视图
      更改为
      select*from\u alpha(N+1)

    下面是一个代码示例:

    import org.apache.spark.sql.{DataFrame, Row, SparkSession}
    import org.apache.spark.sql.types._
    import spark.implicits._
    
    
    //This method verifies if the view exists and returns the table it is pointing to (using the query 'describe formatted')
    
    def getCurrentTable(spark: SparkSession, databaseName:String, tableName: String): Option[String] = {
      if(spark.catalog.tableExists(s"${databaseName}.${tableName}")) {
    
        val rdd_desc = spark.sql(s"describe formatted ${databaseName}.${tableName}")
          .filter("col_name == 'View Text'")
          .rdd
    
        if(rdd_desc.isEmpty()) {
          None
        }
        else {
          Option(
            rdd_desc.first()
              .get(1)
              .toString
              .toLowerCase
              .stripPrefix("select * from ")
          )
        }
      }
      else
        None
    }
    
    //This method saves a dataframe in the next "alpha table" and updates the view. It maintains 'rounds' tables (default=3). I.e. if the current table is alpha2, the next one will be alpha0 again.
    
    def saveDataframe(spark: SparkSession, databaseName:String, tableName: String, new_df: DataFrame, rounds: Int = 3): Unit ={
      val currentTable = getCurrentTable(spark, databaseName, tableName).getOrElse(s"${databaseName}.${tableName}_alpha${rounds-1}")
      val nextAlphaTable = currentTable.replace(s"_alpha${currentTable.last}",s"_alpha${(currentTable.last.toInt + 1) % rounds}")
    
      new_df.write
        .mode("overwrite")
        .format("parquet")
        .option("compression","snappy")
        .saveAsTable(nextAlphaTable)
    
      spark.sql(s"create or replace view ${databaseName}.${tableName} as select * from ${nextAlphaTable}")
    }
    
    //An example on how to use this:
    
    //SparkSession: spark
    val df = Seq((1,"I"),(2,"am"),(3,"a"),(4,"dataframe")).toDF("id","text")
    val new_data = Seq((5,"with"),(6,"new"),(7,"data")).toDF("id","text")
    val dbName = "test_db"
    val tableName = "alpha_test_table"
    
    println(s"Current table: ${getCurrentTable(spark, dbName, tableName).getOrElse("Table does not exist")}")
    println("Saving dataframe")
    
    saveDataframe(spark, dbName, tableName, df)
    
    println("Dataframe saved")
    println(s"Current table: ${getCurrentTable(spark, dbName, tableName).getOrElse("Table does not exist")}")
    spark.read.table(s"${dbName}.${tableName}").show
    
    val processed_df = df.unionByName(new_data) //Or other operations you want to do
    
    println("Saving new dataframe")
    saveDataframe(spark, dbName, tableName, processed_df)
    
    println("Dataframe saved")
    println(s"Current table: ${getCurrentTable(spark, dbName, tableName).getOrElse("Table does not exist")}")
    spark.read.table(s"${dbName}.${tableName}").show
    
    结果:

    Current table: Table does not exist
    Saving dataframe
    Dataframe saved
    Current table: test_db.alpha_test_table_alpha0
    +---+---------+
    | id|     text|
    +---+---------+
    |  3|        a|
    |  4|dataframe|
    |  1|        I|
    |  2|       am|
    +---+---------+
    
    Saving new dataframe
    Dataframe saved
    Current table: test_db.alpha_test_table_alpha1
    +---+---------+
    | id|     text|
    +---+---------+
    |  3|        a|
    |  4|dataframe|
    |  5|     with|
    |  6|      new|
    |  7|     data|
    |  1|        I|
    |  2|       am|
    +---+---------+
    
    通过这样做,您可以保证视图的版本始终可用。这还有一个优点(或者不可用,取决于您的情况)就是维护表的早期版本。即,
    的早期版本将是

    3-奖金 如果升级Spark版本是一个选项,请查看(最低Spark版本:2.4.2)


    希望这有帮助:)

    Spark没有像Zookeeper这样的事务管理器来锁定文件,因此执行并发读/写操作是一个需要单独处理的挑战

    要刷新目录,可以执行以下操作:-

    spark.catalog.refreshTable("my_table")
    
    或 例子 通过阅读您的问题,我认为这可能是您的问题,如果这样,您应该能够在不使用DeltaLake的情况下运行代码。在下面的用例中,Spark将按如下方式运行代码:(1)将文件夹位置的文件名(在本例中为显式零件文件名)本地加载到InputDFA存储区;(2a)到达第2行并覆盖模板位置内的文件;(2b)从inputDF加载内容并输出到模板位置;(3) 按照与1相同的步骤,但在模板位置上;(4a)删除inputLocation文件夹内的文件;(4b)尝试加载缓存在1中的零件文件,从inputDF加载数据,以运行union和break,因为该文件不存在

    val inputDF=spark.read.format(“拼花”).load(inputLocation)
    inputDF.write.format(“拼花”).mode(“覆盖”).save(模板位置)
    val tempDF=火花.读取.切割(“拼花地板”).加载(模板位置)
    val outputDF=inputDF.unionAll(tempDF)
    outputDF.write.format(“拼花”).mode(“覆盖”).save(inputLocation)
    
    根据我的经验,您可以遵循两条路径:持久化或临时输出用于覆盖的所有内容

    var tmp = sparkSession.read.parquet("path/to/parquet_1").cache()
    tmp.write.mode(SaveMode.Overwrite).parquet("path/to/parquet_1") // same path
    
    坚持不懈 在下面的用例中,我们将加载inputDF并立即将其保存为另一个元素并将其持久化。执行操作后,持久化将在数据a上进行
    var tmp = sparkSession.read.parquet("path/to/parquet_1").cache()
    tmp.write.mode(SaveMode.Overwrite).parquet("path/to/parquet_1") // same path