Spark:理解闭包

什么是闭包? 

        闭包就是一个函数,这个函数可能会访问到函数外部的变量。

        简单闭包(就是函数):

def addOne = (i:Int) => 1 + i 

        访问外部变量的闭包,如 : 

var factor = 1
def addOne = (i:Int) => factor + i 

       以上函数应用到函数外的变量factor,定义这个函数的过程就是将 外部的变量的引用 捕获并构成一个封闭的函数。这样, factor 和 addOne 就构成了一个闭包。 

       这样相当于factor变量的引用被 “包”在了addOne函数之中, 如果factor变量值改变, 那addOne中factor引用的值也会改变, 若factor重新定义,addOne还是会指向原先的引用。

var factor = 1
def addOne = (i:Int) => factor + i 

addOne(1) // 结果为2

factor = 2 
addOne(1) //结果为3

var factor = 4
addOne(1) //结果为3

      假设,现有以下两个类 :       

class Func1{
   val n = 2
   def addTwo = (i : Int) => i + n
}

class Func2{
   def addTwo = (i : Int) => i + 2
}

      在Scala中 ,函数也是一个对象, 上例中addTwo就是Function1[Int,Int]的一个对象, 而Function1[Int,Int] 这个类实现了序列化接口, 说明 addTwo 是可被序列化的。

     Func1中的 addTwo方法依赖了Func1类的成员变量n,所以 Func1的addTwo方法依赖于Func1类, 然后Func1类是不能被序列化的, 所以这个闭包不能被序列化。

    Func2中的addTwo方法不会依赖Func2中的成员变量,所以这个闭包可以被初始化。

Spark中的闭包

      为了执行作业,Spark将RDD操作的处理分解为任务,每个任务都由执行程序执行。在执行之前,Spark计算任务的闭包。该闭包需要被序列化并发送给每个执行者。

      简单来说, 每个任务是由若干个闭包组成, 任务需要被分配到各个节点上去执行, 这就需要每个闭包都可以被序列化。

      接上例, 在spark中分别调用上述方法:

     

val errorRdd = listRDD.map(new Func1().addTwo)  ----1

val rdd = listRDD.map(new Func2().addTwo)       ----2

       在 1 中, 因为这个闭包不能被初始化, 所以会报错

Spark:理解闭包_第1张图片

      在2中, 因为new Func2() 对于 addTwo 的闭包来说是无用的,所以在传递的过程中,spark会把这个无用的对象的引用给清理掉。

Spark源码中对闭包序列化处理的方法:clean 

      在许多算子中,都会见到此方法。 作用就是清除一个闭包中的无用依赖,使其准备好序列化并发送到其他执行者。


  private[spark] def clean[F <: AnyRef](f: F, checkSerializable: Boolean = true): F = {
    ClosureCleaner.clean(f, checkSerializable)
    f
  }

总结 : 

       spark对闭包的处理 : 

           1、减少闭包中无用的引用, 减少闭包序列化后的大小, 有利于网络传输

           2、尽可能保证闭包可序列化, 若不行, 则抛出异常: Task not serializable     

你可能感兴趣的:(spark)