java spark使用flatMap或flatMapToPair报错空指针

java版spark使用faltmap时报空指针错误,错误如下:

20/08/28 09:41:44 INFO DAGScheduler: ResultStage 0 (count at TestJob.java:252) failed in 3.500 s due to Job aborted due to stage failure: Task 299 in stage 0.0 failed 1 times, most recent failure: Lost task 299.0 in stage 0.0 (TID 299, localhost, executor driver): java.lang.NullPointerException
	at scala.collection.convert.Wrappers$JIteratorWrapper.hasNext(Wrappers.scala:42)
	at scala.collection.Iterator$$anon$12.hasNext(Iterator.scala:439)
	at org.apache.spark.util.Utils$.getIteratorSize(Utils.scala:1817)
	at org.apache.spark.rdd.RDD$$anonfun$count$1.apply(RDD.scala:1168)
	at org.apache.spark.rdd.RDD$$anonfun$count$1.apply(RDD.scala:1168)
	at org.apache.spark.SparkContext$$anonfun$runJob$5.apply(SparkContext.scala:2113)
	at org.apache.spark.SparkContext$$anonfun$runJob$5.apply(SparkContext.scala:2113)
	at org.apache.spark.scheduler.ResultTask.runTask(ResultTask.scala:90)
	at org.apache.spark.scheduler.Task.run(Task.scala:121)
	at org.apache.spark.executor.Executor$TaskRunner$$anonfun$11.apply(Executor.scala:407)
	at org.apache.spark.util.Utils$.tryWithSafeFinally(Utils.scala:1360)
	at org.apache.spark.executor.Executor$TaskRunner.run(Executor.scala:413)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

Driver stacktrace:
20/08/28 09:41:44 INFO DAGScheduler: Job 0 failed: count at TestJob.java:252, took 3.555955 s
Exception in thread "main" org.apache.spark.SparkException: Job aborted due to stage failure: Task 299 in stage 0.0 failed 1 times, most recent failure: Lost task 299.0 in stage 0.0 (TID 299, localhost, executor driver): java.lang.NullPointerException
	at scala.collection.convert.Wrappers$JIteratorWrapper.hasNext(Wrappers.scala:42)
	at scala.collection.Iterator$$anon$12.hasNext(Iterator.scala:439)
	at org.apache.spark.util.Utils$.getIteratorSize(Utils.scala:1817)
	at org.apache.spark.rdd.RDD$$anonfun$count$1.apply(RDD.scala:1168)
	at org.apache.spark.rdd.RDD$$anonfun$count$1.apply(RDD.scala:1168)
	at org.apache.spark.SparkContext$$anonfun$runJob$5.apply(SparkContext.scala:2113)
	at org.apache.spark.SparkContext$$anonfun$runJob$5.apply(SparkContext.scala:2113)
	at org.apache.spark.scheduler.ResultTask.runTask(ResultTask.scala:90)
	at org.apache.spark.scheduler.Task.run(Task.scala:121)
	at org.apache.spark.executor.Executor$TaskRunner$$anonfun$11.apply(Executor.scala:407)
	at org.apache.spark.util.Utils$.tryWithSafeFinally(Utils.scala:1360)
	at org.apache.spark.executor.Executor$TaskRunner.run(Executor.scala:413)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

Driver stacktrace:
	at org.apache.spark.scheduler.DAGScheduler.org$apache$spark$scheduler$DAGScheduler$$failJobAndIndependentStages(DAGScheduler.scala:1890)
	at org.apache.spark.scheduler.DAGScheduler$$anonfun$abortStage$1.apply(DAGScheduler.scala:1878)
	at org.apache.spark.scheduler.DAGScheduler$$anonfun$abortStage$1.apply(DAGScheduler.scala:1877)
	at scala.collection.mutable.ResizableArray$class.foreach(ResizableArray.scala:59)
	at scala.collection.mutable.ArrayBuffer.foreach(ArrayBuffer.scala:48)
	at org.apache.spark.scheduler.DAGScheduler.abortStage(DAGScheduler.scala:1877)
	at org.apache.spark.scheduler.DAGScheduler$$anonfun$handleTaskSetFailed$1.apply(DAGScheduler.scala:929)
	at org.apache.spark.scheduler.DAGScheduler$$anonfun$handleTaskSetFailed$1.apply(DAGScheduler.scala:929)
	at scala.Option.foreach(Option.scala:257)
	at org.apache.spark.scheduler.DAGScheduler.handleTaskSetFailed(DAGScheduler.scala:929)
	at org.apache.spark.scheduler.DAGSchedulerEventProcessLoop.doOnReceive(DAGScheduler.scala:2111)
	at org.apache.spark.scheduler.DAGSchedulerEventProcessLoop.onReceive(DAGScheduler.scala:2060)
	at org.apache.spark.scheduler.DAGSchedulerEventProcessLoop.onReceive(DAGScheduler.scala:2049)
	at org.apache.spark.util.EventLoop$$anon$1.run(EventLoop.scala:49)
	at org.apache.spark.scheduler.DAGScheduler.runJob(DAGScheduler.scala:740)
	at org.apache.spark.SparkContext.runJob(SparkContext.scala:2073)
	at org.apache.spark.SparkContext.runJob(SparkContext.scala:2094)
	at org.apache.spark.SparkContext.runJob(SparkContext.scala:2113)
	at org.apache.spark.SparkContext.runJob(SparkContext.scala:2138)
	at org.apache.spark.rdd.RDD.count(RDD.scala:1168)
	at org.apache.spark.api.java.JavaRDDLike$class.count(JavaRDDLike.scala:455)
	at org.apache.spark.api.java.AbstractJavaRDDLike.count(JavaRDDLike.scala:45)
	at com.TestJob.main(TestJob.java:252)
Caused by: java.lang.NullPointerException
	at scala.collection.convert.Wrappers$JIteratorWrapper.hasNext(Wrappers.scala:42)
	at scala.collection.Iterator$$anon$12.hasNext(Iterator.scala:439)
	at org.apache.spark.util.Utils$.getIteratorSize(Utils.scala:1817)
	at org.apache.spark.rdd.RDD$$anonfun$count$1.apply(RDD.scala:1168)
	at org.apache.spark.rdd.RDD$$anonfun$count$1.apply(RDD.scala:1168)
	at org.apache.spark.SparkContext$$anonfun$runJob$5.apply(SparkContext.scala:2113)
	at org.apache.spark.SparkContext$$anonfun$runJob$5.apply(SparkContext.scala:2113)
	at org.apache.spark.scheduler.ResultTask.runTask(ResultTask.scala:90)
	at org.apache.spark.scheduler.Task.run(Task.scala:121)
	at org.apache.spark.executor.Executor$TaskRunner$$anonfun$11.apply(Executor.scala:407)
	at org.apache.spark.util.Utils$.tryWithSafeFinally(Utils.scala:1360)
	at org.apache.spark.executor.Executor$TaskRunner.run(Executor.scala:413)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
20/08/28 09:41:44 INFO SparkContext: Invoking stop() from shutdown hook
20/08/28 09:41:44 INFO SparkUI: Stopped Spark web UI at http://DESKTOP-LSJJA44.mshome.net:4040

 

问题代码如下:

List<Tuple2<Integer, String>> list = new ArrayList<>();
list.add(new Tuple2<Integer, String>(1, "a1"));
list.add(new Tuple2<Integer, String>(2, "a2"));
JavaPairRDD<Integer, String> rdd1 = javaSparkContext.parallelize(list).flatMapToPair(x -> {
    List<Tuple2<Integer, String>> resultList = new ArrayList<>();
    if(x._1().intValue() == 1){
        resultList.add(new Tuple2<Integer, String>(x._1, x._2));
    }
    if(null != resultList && !resultList.isEmpty()){
        return resultList.iterator();
    }
    return null;
});
log.info("rdd1 count: {}", rdd1.count());

 

问题原因: 

因为flatmap系列的函数默认需要返回一个集合(即使集合为空也要返回一个非空集合的迭代器对象),如果此处有返回null则会报空指针错误

 

改正后代码:

List<Tuple2<Integer, String>> list = new ArrayList<>();
list.add(new Tuple2<Integer, String>(1, "a1"));
list.add(new Tuple2<Integer, String>(2, "a2"));
JavaPairRDD<Integer, String> rdd1 = javaSparkContext.parallelize(list).flatMapToPair(x -> {
    List<Tuple2<Integer, String>> resultList = new ArrayList<>();
    if(x._1().intValue() == 1){
        resultList.add(new Tuple2<Integer, String>(x._1, x._2));
    }
    return resultList.iterator();
});
log.info("rdd1 count: {}", rdd1.count());

执行结果:

 

<think>我们正在处理一个在DataWorks上使用Scala编写Spark任务时出现的空指针异常问题。具体来说,是在调用`itemMap.getAs[String]`时发生的。我们需要避免这种空指针错误。 根据引用[5],我们知道`Row`的`getAs`方法在遇到null值时会返回对应类型的零值(例如,对于String类型,返回null本身?但实际上,在Scala中,String的零值就是null)。但是,如果我们在调用`getAs[String]`时,字段本身为null,那么返回的null值在后续使用中可能会导致空指针异常。 引用[5]也提到,如果null不是我们期望的数据,建议在SQL阶段就将其过滤掉。另外,引用[2]中提到了一个类似的问题:`Row.getAs[T]`无效,并指出可能是因为处理过程中丢失了行数据的格式(即schema为null)。因此,我们需要确保在操作Row时,Row对象是带有schema的,否则调用`getAs`可能会抛出异常(如`UnsupportedOperationException: fieldIndex on a Row without schema is undefined`)。 因此,我们可以从以下几个方面来避免空指针异常: 1. **确保Row对象带有schema**:在Spark中,Row对象通常是从DataFrameDataset转换而来的,如果操作不当(比如直接操作RDD而丢失schema信息),则可能导致Row没有schema。因此,我们应该尽量使用DataFrame APIDataset API,避免直接操作RDD[Row](除非必要且确保schema存在)。 2. **处理null值**:即使Row对象有schema,字段值也可能为null。我们可以: a. 在SQL阶段过滤掉null值(使用`where`条件)。 b. 在读取数据时,使用`na.drop``na.fill`等方法处理null值。 c. 在代码中使用Option类型来安全处理可能为null的值。 3. **使用Option类型**:在Scala中,我们可以将可能为null的值转换为Option,然后使用`getOrElse`提供默认值。 4. **检查字段是否存在**:在调用`getAs`之前,确保字段存在(避免字段名拼写错误等)。 具体到代码中,我们可以这样处理: 假设我们有一个DataFrame(Dataset)的Row对象,我们可以: 方法1:使用Option ```scala val value: Option[String] = Option(itemMap.getAs[String]("fieldName")) // 然后使用value.getOrElse("default")来获取值,这样如果为null则返回默认值 ``` 方法2:使用`isNullAt`方法检查 ```scala if (!itemMap.isNullAt(itemMap.fieldIndex("fieldName"))) { val value = itemMap.getAs[String]("fieldName") // 使用value } else { // 处理null情况 } ``` 注意:`fieldIndex`方法也可能抛出异常(如果字段不存在),因此确保字段名正确。 另外,引用[2]中提到,如果Row没有schema,那么`fieldIndex`会抛出异常。因此,我们需要确保Row对象是带有schema的。通常,从DataFrame转换而来的RDD[Row]是带有schema的。但是,如果我们进行了某些转换(比如使用`map`操作)并手动创建了Row对象,那么这些Row可能没有schema。在这种情况下,我们需要使用`RowFactory`等方法创建带有schema的Row,使用DataFrame的API进行操作。 如果我们的`itemMap`是从一个DataFrame的某一行得到的,那么它应该带有schema。但如果我们是通过RDD的`map`操作自己构建的Row,则需要确保在创建Row时保留了schema,使用`DataFrame`的`as`方法指定schema。 总结步骤: 1. 确保数据源读取时字段不为空,者在读取后立即处理空值。 2. 在代码中访问字段时,使用安全的方法(如Option检查null)。 3. 确保Row对象有schema(避免手动创建Row时丢失schema)。 示例代码: 假设我们有一个DataFrame,我们想获取其中一列的值,并避免空指针: ```scala import org.apache.spark.sql.Row // 假设df是一个DataFrame,我们对其中的行进行操作 val rows = df.rdd // 注意:这样得到的RDD[Row]是带有schema的 val result = rows.map { row => // 方法1:使用Option val name = Option(row.getAs[String]("name")).getOrElse("unknown") // 方法2:使用isNullAt // val name = if (row.isNullAt(row.fieldIndex("name"))) "unknown" else row.getAs[String]("name") // 其他处理 (name, ...) } ``` 另外,如果我们是在DataWorks上编写Spark任务,可能还需要注意DataWorks的环境配置,确保Spark版本和依赖库的一致性,避免因环境问题导致的异常。 如果问题仍然存在,请检查堆栈跟踪,确定空指针异常发生的具体位置。如果是在`getAs`调用本身发生的空指针,那么可能是`itemMap`这个Row对象本身为null,而不仅仅是字段为null。此时,我们需要确保`itemMap`不为null。 因此,更安全的做法是: ```scala if (itemMap != null) { // 然后按照上述方法处理字段 } else { // 处理整个行对象为null的情况 } ``` 但是,在Spark中,通常整个行对象为null的情况很少见,除非在数据源中就存在null行。我们可以在读取数据后过滤掉整个行对象为null的情况: ```scala val df = spark.read... // 读取数据 val filteredDF = df.filter(row => row != null) // 但这样可能不够,因为Spark的DataFrame不会包含null行,通常RDD[Row]中也不会有null行(除非自定义操作引入) ``` 因此,更可能是字段为null,而不是行对象为null。 综上所述,我们应优先采用对字段进行null值处理的方法。 最后,根据引用[5]的建议,在SQL阶段过滤掉null值可能是最有效的,这样在后续处理中就无需担心null值。 例如,在读取数据后,我们可以: ```scala val cleanedDF = df.filter(col("fieldName").isNotNull) ``` 这样,在后续操作中,该字段就不会出现null值。 因此,根据具体情况,选择在SQL阶段过滤,者在代码中处理null值,都可以避免空指针异常。</think>### 避免 Spark 任务中 `getAs[String]` 的空指针异常 在 DataWorks 中使用 Scala 编写 Spark 任务时,调用 `itemMap.getAs[String]` 发生空指针异常通常由以下原因导致: #### 1. **字段值为 `null` 未处理**(最常见) 当字段实际值为 `null` 时,`getAs[String]` 会返回 `null`,后续操作直接使用返回值会触发 `NullPointerException`。 **解决方案:** ```scala // 使用 Option 包装返回值 val valueOpt: Option[String] = Option(itemMap.getAs[String]("field_name")) // 安全取值(推荐) val safeValue = valueOpt.getOrElse("default_value") // 使用模式匹配 valueOpt match { case Some(value) => // 处理有效值 case None => // 处理空值 } ``` #### 2. **Schema 丢失导致字段索引失效**(引用 [2][^2]) 若 `Row` 对象丢失 Schema 信息,调用 `getAs` 会抛出 `UnsupportedOperationException`,可能间接引发空指针。 **解决方案:** ```scala // 确保从 DataFrame/Dataset 获取带 Schema 的 Row val validRow: Row = spark.sql("SELECT ...").first() // 避免手动创建无 Schema 的 Row // 错误示例:Row(value1, value2) // 无 Schema ``` #### 3. **字段名拼写错误不存在** 当字段不存在时,`getAs` 会抛出 `IllegalArgumentException`,可能导致后续流程中断。 **解决方案:** ```scala // 检查字段是否存在 if (itemMap.schema.fieldNames.contains("field_name")) { val value = itemMap.getAs[String]("field_name") } ``` #### 4. **数据源存在空行** 整个 `itemMap` 对象为 `null` 时,调用其方法必然触发空指针。 **解决方案:** ```scala // 过滤空行 val cleanedDF = originalDF.filter(row => row != null) ``` #### 最佳实践总结 1. **前置过滤**:在 SQL 阶段过滤 `NULL` 值(引用 [5][^5]) ```sql SELECT * FROM table WHERE field_name IS NOT NULL ``` 2. **防御性编码**:始终用 `Option` 包装 `getAs` 的返回值 3. **Schema 保障**:避免直接操作无 Schema 的 `Row` 对象(引用 [2][^2]) 4. **默认值机制**:使用 `.getOrElse()` 提供兜底值 5. **单元测试**:模拟 `null` 输入验证代码健壮性 --- ### 相关问题 1. 如何在 Spark 中安全处理嵌套数据结构(如 Array[Struct])避免空指针? 2. DataWorks 中调试 Spark 空指针异常有哪些高效工具日志配置技巧? 3. 除了 `Option`,Scala 还有哪些语法特性可优雅处理 `null` 值? 4. 为什么 Spark 的 `Row` 对象有时会丢失 Schema 信息?如何彻底避免?[^2] [^1]: 测试 Spark 集群的命令示例及输出日志 [^2]: `Row.getAs[T]` 因 Schema 丢失导致的异常解决方案 [^3]: Spark 序列化结果超限的报错处理方法 [^4]: Scala 大小写敏感导致的主类加载问题 [^5]: `DataFrame.getAs` 遇到 `null` 值的处理建议
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值