为什么在RDD中,map给出NotSerializableException而foreach没有?

时间:2015-07-18 11:49:18

标签: scala serialization apache-spark rdd

我理解map和&之间的基本区别foreach(懒惰和渴望),我也理解为什么这段代码片段

sc.makeRDD(Seq("a", "b")).map(s => new java.io.ByteArrayInputStream(s.getBytes)).collect

应该给出

  

java.io.NotSerializableException:java.io.ByteArrayInputStream

然后我认为应该使用以下代码片段

sc.makeRDD(Seq("a", "b")).foreach(s => {
  val is = new java.io.ByteArrayInputStream(s.getBytes)
  println("is = " + is)
})

但是这段代码运行良好。为什么这样?

2 个答案:

答案 0 :(得分:2)

实际上mapforeach之间的根本区别不在于评估策略。让我们来看看签名(为简洁起见,我省略了map的隐式部分):

def map[U](f: (T) ⇒ U): RDD[U]
def foreach(f: (T) ⇒ Unit): Unit 

mapTU的函数将其应用于现有RDD[T]的每个元素并返回RDD[U]。允许像sh U一样的操作必须是可序列化的。

foreachTUnit(类似于Java void)采用函数,并且本身不返回任何内容。一切都在本地发生,没有涉及网络流量,因此不需要序列化。与map不同,foreach应该在想要获得某种副作用时使用,例如your previous question

另一方面,这两个实际上是不同的。您在map中使用的匿名函数是一个函数:

(s: String) => java.io.ByteArrayInputStream

和你在foreach中使用的那个:

(s: String) => Unit

如果您使用map的第二个函数,您的代码将编译,但结果将远离您想要的结果(RDD[Unit])。

答案 1 :(得分:0)

地图引起问题后的

collect呼叫。 以下是我在spark-shell中进行测试的结果。

下面通过,因为没有数据必须发送到其他节点。

sc.makeRDD(1 to 1000, 1).map(_ => {NullWritable.get}).count

以下调用失败,因为地图输出可以发送到其他节点。

sc.makeRDD(1 to 1000, 1).map(_ => {NullWritable.get}).first
sc.makeRDD(1 to 1000, 1).map(_ => {NullWritable.get}).collect

重新分区会强制将数据分发到节点,这会失败。

sc.makeRDD(1 to 1000, 1).map(_ => {NullWritable.get}).repartition(2).saveAsTextFile("/tmp/NWRepart")

呼叫传递下方没有分区。

sc.makeRDD(1 to 1000, 1).map(_ => {NullWritable.get}).saveAsTextFile("/tmp/NW")