Apache spark 如何使用ApacheSpark将混合拼花模式加载到数据帧中?

Apache spark 如何使用ApacheSpark将混合拼花模式加载到数据帧中?,apache-spark,dataframe,amazon-s3,parquet,Apache Spark,Dataframe,Amazon S3,Parquet,我有一个Spark工作,不断地将拼花地板文件上传到S3(带有分区)。 这些文件都具有相同的拼花模式 其中一个字段类型最近已经更改(从String更改为long),因此一些分区的拼花模式是混合的 两种类型数据混合的地方现在无法读取部分内容。 虽然看起来我可以执行:sqlContext.read.load(path) 当尝试对数据帧应用任何获取操作时(例如,collect),操作失败,出现ParquetDecodingException 我打算迁移数据并将其重新格式化,但未能将混合内容读取到数据框中

我有一个Spark工作,不断地将拼花地板文件上传到S3(带有分区)。
这些文件都具有相同的拼花模式

其中一个字段类型最近已经更改(从String更改为long),因此一些分区的拼花模式是混合的

两种类型数据混合的地方现在无法读取部分内容。
虽然看起来我可以执行:
sqlContext.read.load(path)

当尝试对数据帧应用任何获取操作时(例如,
collect
),操作失败,出现
ParquetDecodingException

我打算迁移数据并将其重新格式化,但未能将混合内容读取到数据框中
如何使用ApacheSpark将混合分区加载到数据帧或任何其他Spark构造中

以下是ParquetDecodingException跟踪:

scala> df.collect
[Stage 1:==============>        (1 + 3) / 4]
WARN TaskSetManager: Lost task 1.0 in stage 1.0 (TID 2, 172.1.1.1, executor 0): org.apache.parquet.io.ParquetDecodingException: 
Can not read value at 1 in block 0 in file 
s3a://data/parquet/partition_by_day=20180620/partition_by_hour=10/part-00000-6e4f07e4-3d89-4fad-acdf-37054107dc39.snappy.parquet
    at org.apache.parquet.hadoop.InternalParquetRecordReader.nextKeyValue(InternalParquetRecordReader.java:243)
    at org.apache.parquet.hadoop.ParquetRecordReader.nextKeyValue(ParquetRecordReader.java:227)
    at org.apache.spark.sql.execution.datasources.RecordReaderIterator.hasNext(RecordReaderIterator.scala:39)
    at org.apache.spark.sql.execution.datasources.FileScanRDD$$anon$1.hasNext(FileScanRDD.scala:102)
    at org.apache.spark.sql.execution.datasources.FileScanRDD$$anon$1.nextIterator(FileScanRDD.scala:166)
    at org.apache.spark.sql.execution.datasources.FileScanRDD$$anon$1.hasNext(FileScanRDD.scala:102)
    at org.apache.spark.sql.catalyst.expressions.GeneratedClass$GeneratedIterator.processNext(Unknown Source)
    at org.apache.spark.sql.execution.BufferedRowIterator.hasNext(BufferedRowIterator.java:43)
    at org.apache.spark.sql.execution.WholeStageCodegenExec$$anonfun$8$$anon$1.hasNext(WholeStageCodegenExec.scala:377)
    at org.apache.spark.sql.execution.SparkPlan$$anonfun$2.apply(SparkPlan.scala:231)
    at org.apache.spark.sql.execution.SparkPlan$$anonfun$2.apply(SparkPlan.scala:225)
    at org.apache.spark.rdd.RDD$$anonfun$mapPartitionsInternal$1$$anonfun$apply$25.apply(RDD.scala:826)
    at org.apache.spark.rdd.RDD$$anonfun$mapPartitionsInternal$1$$anonfun$apply$25.apply(RDD.scala:826)
    at org.apache.spark.rdd.MapPartitionsRDD.compute(MapPartitionsRDD.scala:38)
    at org.apache.spark.rdd.RDD.computeOrReadCheckpoint(RDD.scala:323)
    at org.apache.spark.rdd.RDD.iterator(RDD.scala:287)
    at org.apache.spark.scheduler.ResultTask.runTask(ResultTask.scala:87)
    at org.apache.spark.scheduler.Task.run(Task.scala:99)
    at org.apache.spark.executor.Executor$TaskRunner.run(Executor.scala:282)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.ClassCastException: [B cannot be cast to java.lang.Long
    at scala.runtime.BoxesRunTime.unboxToLong(BoxesRunTime.java:105)

据我所知,您不能将具有相同字段和不同类型的2个模式混合使用。因此,我能想到的唯一解决办法是:

  • 将每个文件重新写入新位置,然后

  • 如果原始数据已分区,则需要另一个过程来恢复分区。
    这是因为逐个文件重新写入数据会覆盖分区
  • 检查是否可以将所有新分区作为正确的模式读取
  • 删除“坏”分区并复制tmp分区

  • 据我所知,您不能将具有相同字段和不同类型的2个模式混合使用。因此,我能想到的唯一解决办法是:

  • 将每个文件重新写入新位置,然后

  • 如果原始数据已分区,则需要另一个过程来恢复分区。
    这是因为逐个文件重新写入数据会覆盖分区
  • 检查是否可以将所有新分区作为正确的模式读取
  • 删除“坏”分区并复制tmp分区

  • 还有另一个想法:不是更改现有字段(field_string)的类型,而是添加一个长类型的新字段(field_long),并将读取数据的代码更新为类似这样的内容(伪代码),并启用模式合并。我相信它是默认启用的,但这是一个很好的明确说明:

    sqlContext.read.option("mergeSchema", "true").parquet(<parquet_file>)
    
    ...
    
    if isNull(field_long) 
      field_value_long = field_string.value.to_long
    else
      field_value_long = field_long.value
    
    sqlContext.read.option(“mergeSchema”、“true”).parquet()
    ...
    如果为空(字段长度)
    field_value_long=field_string.value.to_long
    其他的
    field_value_long=field_long.value
    
    还有另一个想法:与其更改现有字段(字段字符串)的类型,不如添加一个长类型的新字段(字段字符串),并将读取数据的代码更新为类似以下内容(伪代码)并启用模式合并。我相信它是默认启用的,但这是一个很好的明确说明:

    sqlContext.read.option("mergeSchema", "true").parquet(<parquet_file>)
    
    ...
    
    if isNull(field_long) 
      field_value_long = field_string.value.to_long
    else
      field_value_long = field_long.value
    
    sqlContext.read.option(“mergeSchema”、“true”).parquet()
    ...
    如果为空(字段长度)
    field_value_long=field_string.value.to_long
    其他的
    field_value_long=field_long.value
    
    自Spark 1.5以来,默认情况下已禁用架构合并。另外,不推荐使用
    sqlContext
    编写新代码时,首选
    val mergedDF=spark.read.option(“mergeSchema”、“true”).parquet(“数据/测试表”)
    。自spark 1.5以来,默认情况下已禁用模式合并。另外,
    sqlContext
    也不推荐使用,在编写新代码时,首选
    val mergedDF=spark.read.option(“mergeSchema”、“true”).parquet(“数据/测试表”)
    。精确的答案证明有效且正确。添加了第(4)步,以获得分区数据的完整答案,就像我的情况一样。精确的答案被证明是有效和正确的。添加了步骤(4)以获得分区数据的完整答案,就像在我的案例中一样。