创建数据集时,Spark无法反序列化记录

时间:2018-12-17 00:23:32

标签: scala apache-spark apache-spark-sql apache-spark-dataset

我正在从S3中读取大量CSV(所有内容都在键前缀下),并创建一个强类型的Dataset

val events: DataFrame = cdcFs.getStream()
events
  .withColumn("event", lit("I"))
  .withColumn("source", lit(sourceName))
  .as[TradeRecord]

其中TradeRecord是一个Case类,通常可以通过SparkSession隐式反序列化为该类。但是,对于某个批次,记录无法反序列化。这是错误(省略了堆栈跟踪)

Caused by: java.lang.NullPointerException: Null value appeared in non-nullable field:
- field (class: "scala.Long", name: "deal")
- root class: "com.company.trades.TradeRecord"
If the schema is inferred from a Scala tuple/case class, or a Java bean, please try to use scala.Option[_] or other nullable types (e.g. java.lang.Integer instead of int/scala.Int).

dealTradeRecord的字段,在源数据(S3对象)中永远不能为空,因此它不是Option

不幸的是,该错误消息没有给我任何有关CSV数据的外观,甚至是来自哪个CSV文件的线索。该批次包含数百个文件,因此我需要一种方法来将其范围缩小到最多几个文件以调查问题。

2 个答案:

答案 0 :(得分:1)

suggesteduser10465355,您可以加载数据:

val events: DataFrame = ???

过滤器

val mismatched = events.where($"deal".isNull)

添加文件名

import org.apache.spark.sql.functions.input_file_name

val tagged = mismatched.withColumn("_file_name", input_file_name)

可选地添加块,块和偏移量:

import org.apache.spark.sql.functions.{spark_partition_id, monotonically_increasing_id, shiftLeft, shiftRight

df
  .withColumn("chunk", spark_partition_id())
  .withColumn(
    "offset",
    monotonically_increasing_id - shiftLeft(shiftRight(monotonically_increasing_id, 33), 33))

答案 1 :(得分:0)

这是我想出的解决方案(我正在使用Spark结构化流):

val stream = spark.readStream
  .format("csv")
  .schema(schema) // a StructType defined elsewhere
  .option("mode", "PERMISSIVE")
  .option("columnNameOfCorruptRecord", "corruptRecord")
  .load(path)

// If debugging, check for any corrupted CSVs
if (log.isDebugEnabled) { // org.apache.spark.internal.Logging trait 
  import spark.implicits._
  stream
    .filter($"corruptRecord".isNotNull)
    .withColumn("input_file", input_file_name)
    .select($"input_file", $"corruptRecord")
    .writeStream
    .format("console")
    .option("truncate", false)
    .start()
}

val events = stream
  .withColumn("event", lit("I"))
  .withColumn("source", lit(sourceName))
  .as[TradeRecord]

基本上,如果将Spark日志级别设置为“调试”或更低级别,则会检查DataFrame中是否有损坏的记录,并打印出任何此类记录及其文件名。最终,程序尝试将此DataFrame强制转换为强类型的Dataset[TradeRecord]并失败。