Spark技术疑问:Overwrite写入报NPE及特定Schema DataFrame新列问题
嘿,这两个Spark相关的问题我之前帮不少开发者排查过,给你详细拆解下:
问题1:为什么
write.mode(SaveMode.Overwrite)抛空指针异常,但first()/show()能正常执行? 其实这俩操作的触发逻辑完全不一样:first()和show()只需要处理小部分数据(甚至是前几行),不会涉及全量数据写入、路径元数据操作或者外部数据源连接;但写入操作要走完整的全量计算+写入流程,很多在小数据量下隐藏的null问题都会暴露出来。常见原因有这几个:
- 写入路径配置无效:比如你指定的输出路径是
null,或者路径变量没正确初始化。show()不需要输出路径所以没问题,但写入时找不到目标路径就会直接抛NPE。赶紧检查下write.save()里的路径参数,或者对应的配置项是不是有遗漏。 - 外部数据源的连接参数为null:如果是写入JDBC、HBase这类外部存储,比如JDBC的
url、user参数不小心设成了null,show()根本不会用到这些配置,但写入时建立连接就会触发空指针。核对下你的写入代码,比如:df.write.mode(SaveMode.Overwrite) .option("url", null) // 这里如果是null必出问题 .jdbc(...) - 分区列存在null值且数据源不兼容:如果你的DataFrame按某列分区,而该列有null值,部分数据源(比如早期版本的Parquet)在Overwrite模式处理分区元数据时会抛NPE。先跑个
df.filter($"partition_col".isNull).count()看看有没有null,有的话要么过滤掉,要么替换成默认值(比如lit("unknown"))。 - Spark版本的特定bug:比如2.x的某些旧版本在Overwrite模式下处理已存在的目录时,如果目录权限异常,会触发NPE。这种情况可以先手动删除目标目录再试,或者直接升级到更稳定的版本。
- 自定义逻辑里的隐藏null:如果你用了自定义UDF或者Writer,
show()只取前20行可能没碰到有null的数据,但全量写入时就触发了NPE。可以用df.foreach(row => {})遍历全量数据,排查是不是有隐藏的null引用问题。
问题2:处理包含ArrayType(MapType)的DataFrame生成新列
先纠正下你的示例数据,应该是[11223344,ALAN,WrappedArray(Map(source -> central, document_number -> 1234, first_seen -> 2018-05-01))](多了个28应该是笔误)。根据常见的需求,给你几种解法:
场景1:提取数组中第一个文档的编号
如果只需要取第一个Map里的document_number,可以这么写,还能处理数组为空的情况:
import org.apache.spark.sql.functions._ val dfWithDocNum = df.withColumn( "document_number", when(size($"DOCUMENT") > 0, $"DOCUMENT"(0)("document_number")).otherwise(lit(null)) )
场景2:展开数组,每个文档单独成行
如果要把数组里的每个Map拆成单独的行,用explode函数最方便:
val explodedDf = df.withColumn("single_doc", explode($"DOCUMENT")) .withColumn("source", $"single_doc".getField("source")) .withColumn("document_number", $"single_doc".getField("document_number")) .withColumn("first_seen", $"single_doc".getField("first_seen")) .drop("single_doc", "DOCUMENT") // 可选:删除不需要的原列
场景3:把Map数组转成Struct数组(更结构化)
如果想让数据结构更清晰,把Map类型转成Struct类型,用transform函数(Spark 2.4+支持):
val dfWithStructDocs = df.withColumn( "structured_docs", transform( $"DOCUMENT", docMap => struct( docMap("source").alias("source"), docMap("document_number").alias("document_number"), docMap("first_seen").alias("first_seen") ) ) )
如果你的需求不是这些,可以补充下具体要生成什么样的新列,我再给你调整解法~
内容的提问来源于stack exchange,提问作者Ignacio Alorre
相关产品推荐
相关产品推荐

