Apache spark 如何在ApacheSpark中处理拼花地板模式的更改
我遇到了一个问题,在S3中,我将拼花地板数据作为每日块(以Apache spark 如何在ApacheSpark中处理拼花地板模式的更改,apache-spark,apache-spark-sql,spark-dataframe,emr,parquet,Apache Spark,Apache Spark Sql,Spark Dataframe,Emr,Parquet,我遇到了一个问题,在S3中,我将拼花地板数据作为每日块(以S3://bucketName/prefix/YYYY/MM/DD/的形式),但我无法从不同的日期读取AWS EMR Spark中的数据,因为某些列类型不匹配,并且我遇到了许多异常之一,例如: java.lang.ClassCastException: optional binary element (UTF8) is not a group 当在某些文件中有一个数组类型具有值,但在其他文件中同一列可能具有null值,而这些文件随后被推
S3://bucketName/prefix/YYYY/MM/DD/
的形式),但我无法从不同的日期读取AWS EMR Spark中的数据,因为某些列类型不匹配,并且我遇到了许多异常之一,例如:
java.lang.ClassCastException: optional binary element (UTF8) is not a group
当在某些文件中有一个数组类型具有值,但在其他文件中同一列可能具有null
值,而这些文件随后被推断为字符串类型时,会出现此消息
或
我在S3中有JSON格式的原始数据,我最初的计划是创建一个自动作业,它启动一个EMR集群,读入前一个日期的JSON数据,并将其作为拼花写回S3
JSON数据也分为日期,即键有日期前缀。阅读JSON很好。无论当前读取了多少数据,都会从数据中推断出模式
但是当拼花文件被写入时,问题就出现了。据我所知,当我使用元数据文件编写拼花地板时,这些文件包含拼花地板文件的所有部分/分区的模式。在我看来,这也可以是不同的模式。当我禁用写入元数据时,Spark被认为是从给定拼花地板路径中的第一个文件推断出整个模式,并假定它在其他文件中保持不变
当某些列(应该是double
类型)在给定的一天内只有整数值时,从JSON(这些数字是整数,没有浮点数)读取它们会使Spark认为它是类型为long
的列。即使我可以在编写拼花文件之前将这些列转换为两倍,这仍然不好,因为模式可能会更改,可以添加新的列,并且跟踪这一点是不可能的
我见过一些人有同样的问题,但我还没有找到一个足够好的解决办法
这方面的最佳实践或解决方案是什么 这些是我用于将拼花地板写入S3的选项;关闭模式合并可以提高写回性能——这也可以解决您的问题
val PARQUET_OPTIONS = Map(
"spark.sql.parquet.mergeSchema" -> "false",
"spark.sql.parquet.filterPushdown" -> "true")
当我从JSON中读取每日数据块并写入每日S3文件夹中的Parquet时,在读取JSON时没有指定自己的模式,也没有在写入Parquet之前将容易出错的列转换为正确的类型,Spark可能会根据数据实例中的值为不同天数的数据推断不同的模式,并使用冲突模式编写拼花文件 这可能不是一个完美的解决方案,但我发现用不断发展的模式解决问题的唯一方法是: 在批量处理前一天数据的日常(更具体地说是夜间)cron作业之前,我正在创建一个虚拟对象,其中大部分是空值 我确保ID是可识别的,例如,由于真实数据具有唯一的ID-s,我将“伪”字符串作为ID添加到伪数据对象中 然后,我将为具有易出错类型的属性提供预期值,例如,我将提供浮点/双精度非零值,因此当编组为JSON时,它们肯定会使用十进制分隔符,例如“0.2”而不是“0”(当编组为JSON时,具有0值的双精度/浮点将显示为“0”而不是“0.0”) 字符串、布尔值和整数可以很好地工作,但是除了double/float之外,我还需要将数组实例化为空数组,并使用相应的空对象实例化其他类/结构的对象,这样它们就不会是“null”-s,因为Spark在字符串中读取null-s
然后,如果我已经填充了所有必需的字段,我将把对象整理成JSON,并将文件写入S3 然后,我会在Scala批处理脚本中使用这些文件来读取它们,将模式保存到一个变量中,并在读取真实JSON数据时将此模式作为参数,以避免Spark进行自己的模式推断 这样我就知道所有字段都是同一类型的,只有在添加新字段时才需要合并模式来连接模式
当然,它增加了一个缺点,即在添加易出错类型的新字段时手动更新虚拟对象创建,但这是目前的一个小缺点,因为这是我发现的唯一有效的解决方案。只要创建一个rdd[String],其中每个字符串都是json,将rdd设置为数据帧时,使用primitiveAsString选项将所有数据类型设置为字符串
val binary_zip_RDD = sc.binaryFiles(batchHolder.get(i), minPartitions = 50000)
// rdd[String] each string is a json ,lowercased json
val TransformedRDD = binary_zip_RDD.flatMap(kv => ZipDecompressor.Zip_open_hybrid(kv._1, kv._2, proccessingtimestamp))
// now the schema of dataframe would be consolidate schema of all json strings
val jsonDataframe_stream = sparkSession.read.option("primitivesAsString", true).json(TransformedRDD)
println(jsonDataframe_stream.printSchema())
jsonDataframe_stream.write.mode(SaveMode.Append).partitionBy(GetConstantValue.DEVICEDATE).parquet(ApplicationProperties.OUTPUT_DIRECTORY)
嗨,我要试试这个。但我想知道,在编写拼花时,是否在
.option()
函数中包含此PARQUET\u OPTIONS
映射?但是读书怎么样?我只使用了sqlContext.read.option(“mergeSchema”,true)。parquet(“path”)
,但仍然使用没有选项的常规写操作。我尝试了使用.option(“mergeSchema”,“false”)。option(“filterPushdown”,“true”)
进行读写操作,但没有任何改变。当mergeSchema为true时,我得到无法合并不兼容的数据类型DoubleType和LongType
,当它为false时,读取数据有效。打印架构显示该列为双重类型,show()
命令显示了前20行,但该列上的筛选和分组失败:Cost声明的类型(java.lang.double)与文件元数据中找到的架构不匹配。
听起来有些文件的架构不一致,而最近发布的架构不一致。他们的一个关键主题是“永远不要删除字段,只在末尾添加它们”。毕竟,如果您将字段标记为可选,则可以忽略数据。就像我说的,这不是我工作的领域。我所知道的是,模式合并需要parquet在每个文件的末尾读取模式,这是非常昂贵的,尤其是在Hadoop<2.8上,在s3a上查找是非常昂贵的。如果你必须进行合并,你必须承担责任。抱歉是的,当前合并没有帮助,因为它无法选择一种类型并使用它。它所做的唯一一件事是在读取模式不符合matc时抛出异常
val binary_zip_RDD = sc.binaryFiles(batchHolder.get(i), minPartitions = 50000)
// rdd[String] each string is a json ,lowercased json
val TransformedRDD = binary_zip_RDD.flatMap(kv => ZipDecompressor.Zip_open_hybrid(kv._1, kv._2, proccessingtimestamp))
// now the schema of dataframe would be consolidate schema of all json strings
val jsonDataframe_stream = sparkSession.read.option("primitivesAsString", true).json(TransformedRDD)
println(jsonDataframe_stream.printSchema())
jsonDataframe_stream.write.mode(SaveMode.Append).partitionBy(GetConstantValue.DEVICEDATE).parquet(ApplicationProperties.OUTPUT_DIRECTORY)