Scala 为什么在RDD中,map提供NotSerializableException,而foreach不提供';T

Scala 为什么在RDD中,map提供NotSerializableException,而foreach不提供';T,scala,serialization,apache-spark,rdd,Scala,Serialization,Apache Spark,Rdd,我理解map和foreach(懒惰和渴望)之间的基本区别,我也理解为什么会出现这个代码片段 sc.makeRDD(Seq("a", "b")).map(s => new java.io.ByteArrayInputStream(s.getBytes)).collect sc.makeRDD(Seq("a", "b")).foreach(s => { val is = new java.io.ByteArrayInputStream(s.getBytes) println("

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

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

java.io.NotSerializableException:java.io.ByteArrayInputStream

然后我认为下面的代码片段也应该如此

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

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

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

map
获取从
T
U
的函数,将其应用于现有
RDD[T]
的每个元素,并返回
RDD[U]
。要允许像shuffling
U
这样的操作,必须是可序列化的

foreach
接受一个从
T
Unit
的函数(类似于Java
void
),其本身不返回任何内容。一切都发生在本地,不涉及网络流量,因此不需要序列化。与
map
不同,
foreach
应该在想要获得某种副作用时使用,比如在

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

(s: String) => java.io.ByteArrayInputStream
您在
foreach
中使用的

(s: String) => Unit

如果将第二个函数与
map
一起使用,您的代码将被编译,尽管结果与您想要的相差甚远(
RDD[Unit]
)。

collect
在map导致问题后调用。 下面是我在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}).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")

很好的解释!我知道需要对
foreach
进行洗牌,但请您详细说明为什么在
map
的情况下会涉及洗牌?这是因为生成的分区(在map之后)的大小可能比该机器中可用的磁盘大小大吗?老实说,我不确定是否仅
map
就可以触发洗牌,但重要的是下游操作可能:
rdd.map(f).重新分区(1)
。如果返回类型为
f
则所有操作都失败。请澄清。可以在映射函数中使用不可序列化的类型。它不能是返回类型。