SparkRDD - 基础编程

SparkRDD - 基础编程

RDD序列化

闭包检测

  • 从计算的角度,算子以外的代码都是在Driver端执行,算子里面的代码都是在Executor端执行。那么在scala的函数式编程中,就会导致算子内经常会用到算子外的数据,这样就形成了闭合的效果。
  • 如果使用的算子外的数据无法序列化,就意味着无法传值给Executor端执行,就会发生错误。所以,需要在执行任务计算前,检测闭包内的对象是否可以进行序列化,这个操作我们称之为闭包检测
  • Scala2.12版本后闭包编译方式发生了改变。
源码说明

我们执行collect等action算子,底层都会调用此方法来提交job。在下面的代码中可以看到,在提交job之前会进行clean操作,闭包检测就在里面。

// SparkContext

def runJob[T, U: ClassTag](
      rdd: RDD[T],
      func: Iterator[T] => U,
      partitions: Seq[Int]): Array[U] = {
    val cleanedFunc = clean(func)
    runJob(rdd, (ctx: TaskContext, it: Iterator[T]) => cleanedFunc(it), partitions)
}

我们点击clean(func),进入到如下代码:

// SparkContext

  /**
   * 清理一个闭包,使其准备好序列化并发送到tasks(移除$outer中未引用的变量,更新REPL变量)
   * 如果设置了checkSerializable,clean还将主动检测f是否可序列化,如果不可序列化,则抛出SparkException
   *
   * @param f 要清洗的闭包
   * @param checkSerializable 是否立即检查f的可序列化性
   * @throws SparkException 如果设置了checkSerializable但f不可序列化
   * @return 清洗后的闭包
   */
  private[spark] def clean[F <: AnyRef](f: F, checkSerializable: Boolean = true): F = {
    ClosureCleaner.clean(f, checkSerializable)
    f
  }

我们再进入到ClosureCleaner.clean方法中:

  /**
   * 在适当的地方清洗给定的闭包。
   * 更具体地说,只要给定的闭包没有显式地引用不可序列化的对象,它就会呈现为可序列化的。
   *
   * @param closure 要清洗的闭包
   * @param checkSerializable 清洗后是否验证闭包是可序列化的
   * @param cleanTransitively 是否传递清除封闭闭包
   */
  def clean(
      closure: AnyRef,
      checkSerializable: Boolean = true,
      cleanTransitively: Boolean = true): Unit = {
    clean(closure, checkSerializable, cleanTransitively, Map.empty)
  }

继续往里面走,由于代码篇幅过长,所以就摘了核心的那句:

  /**
   * Helper方法来就地清洗给定的闭包。
   *
   * 其机制是遍历封闭闭包的层次结构,并将启动闭包过程中,实际上没有使用但仍包含在已编译的匿名类中的所有引用清空。
   * 注意,简单地对封闭闭包进行就地转换是不安全的,因为其他代码路径可能依赖于它们。
   * 相反,我们克隆每个封闭闭包并相应地设置父指针。
   *
   * 默认情况下,闭包以传递方式清除。
   * 这意味着我们检测封闭对象是否被开始对象实际引用,无论是直接引用还是传递引用,如果不是,则从层次结构中分离这些闭包。
   * 换句话说,除了清空未使用的字段引用之外,我们还清空了任意父指针,这些父指针引用的封闭对象实际上不是开始闭包需要的。
   * 我们通过跟踪内部闭包最终调用的所有方法的树来确定传递性,并记录流程中引用的所有字段。
   * 
   * 例如,在下面的场景中,传递式清理是必需的:
   *   class SomethingNotSerializable {
   *     def someValue = 1
   *     def scope(name: String)(body: => Unit) = body
   *     def someMethod(): Unit = scope("one") {
   *       def x = someValue
   *       def y = 2
   *       scope("two") { println(y + 1) }
   *     }
   *   }
   *
   * 在本例中,scope "two" 不可序列化,因为它引用了scope "one",后者引用了SomethingNotSerializable。
   * 但是,请注意,scope "two" 的主体实际上并不依赖于SomethingNotSerializable。
   * 这意味着我们可以安全地将克隆的scope "one" 的父指针空出来,
   * 并将其设置为scope "two"的父指针,这样scope "two"就不再以可传递方式引用SomethingNotSerializable。
   * 
   * @param func 要清理的开始闭包
   * @param checkSerializable 清理后是否验证闭包是可序列化的
   * @param cleanTransitively 是否传递地清理封闭闭包
   * @param accessedFields 从类到开始闭包访问的类的一组字段的map
   */
  private def clean(
      func: AnyRef,
      checkSerializable: Boolean,
      cleanTransitively: Boolean,
      accessedFields: Map[Class[_], Set[String]]): Unit = {

    。。。

    if (checkSerializable) {
      ensureSerializable(func)
    }
  }

终于看到确认序列化的方法了:

  private def ensureSerializable(func: AnyRef) {
    try {
      if (SparkEnv.get != null) {
        SparkEnv.get.closureSerializer.newInstance().serialize(func)
      }
    } catch {
      case ex: Exception => throw new SparkException("Task not serializable", ex)
    }
  }

可以看见:如果不可序列化就会throw new SparkException(“Task not serializable”, ex)
至此,闭包检测结束。回到上~层继续runJob

Kryo序列化框架

参考地址: https://github.com/EsotericSoftware/kryo

  • Java的序列化能够序列化任何的类。但是比较重(字节多),序列化后,对象的提交也比较大。
  • Spark出于性能的考虑,Spark2.0开始支持另外一种Kryo序列化机制。Kryo速度是Serializable的10倍。
  • 当RDD在Shuffle数据的时候,简单数据类型、数组和字符串类型已经在Spark内部使用Kryo来序列化。

注意:即使使用Kryo序列化,也要继承Serializable接口

val conf: SparkConf = new SparkConf()
                .setAppName("SerDemo")
                .setMaster("local[*]")
                // 替换默认的序列化机制
                .set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
                // 注册需要使用 kryo 序列化的自定义类
                .registerKryoClasses(Array(classOf[Searcher]))

RDD依赖关系

RDD血缘关系

  • RDD只支持粗粒度转换,即在大量记录上执行的单个操作。
  • 将创建RDD的一系列Lineage(血统)记录下来,以便恢复丢失的分区。
  • RDD的Linage会记录RDD的元数据信息转换行为(数据源&计算),当该RDD的部分分区数据丢失时,它可以根据这些信息来重新运算和恢复丢失的数据分区。

案例:

  def main(args: Array[String]): Unit = {
    val sparkConf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD Lineage")
    val sc = new SparkContext(sparkConf)

    val rdd: RDD[String] = sc.makeRDD(List("hello world", "hello spark"))
    println(rdd.toDebugString)
    println("-------------------------")

    val wordRDD: RDD[String] = rdd.flatMap(_.split(" "))
    println(wordRDD.toDebugString)
    println("-------------------------")

    val mapRDD: RDD[(String, Int)] = wordRDD.map((_, 1))
    println(mapRDD.toDebugString)
    println("-------------------------")

    val resRDD: RDD[(String, Int)] = mapRDD.reduceByKey(_+_)
    println(resRDD.toDebugString)

    resRDD.collect()
  }
  
  
  运行结果:
  (8) ParallelCollectionRDD[0] at makeRDD at LineageTest.scala:16 []
  -------------------------
  (8) MapPartitionsRDD[1] at flatMap at LineageTest.scala:20 []
   |  ParallelCollectionRDD[0] at makeRDD at LineageTest.scala:16 []
  -------------------------
  (8) MapPartitionsRDD[2] at map at LineageTest.scala:24 []
   |  MapPartitionsRDD[1] at flatMap at LineageTest.scala:20 []
   |  ParallelCollectionRDD[0] at makeRDD at LineageTest.scala:16 []
  -------------------------
  (8) ShuffledRDD[3] at reduceByKey at LineageTest.scala:28 []
   +-(8) MapPartitionsRDD[2] at map at LineageTest.scala:24 []
      |  MapPartitionsRDD[1] at flatMap at LineageTest.scala:20 []
      |  ParallelCollectionRDD[0] at makeRDD at LineageTest.scala:16 []

RDD依赖关系

  • 这里所谓的依赖关系,其实就是RDD之间的关系

案例:

  def main(args: Array[String]): Unit = {
    val sparkConf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD Lineage")
    val sc = new SparkContext(sparkConf)

    val rdd: RDD[String] = sc.makeRDD(List("hello world", "hello spark"))
    println(rdd.dependencies)
    println("-------------------------")

    val wordRDD: RDD[String] = rdd.flatMap(_.split(" "))
    println(wordRDD.dependencies)
    println("-------------------------")

    val mapRDD: RDD[(String, Int)] = wordRDD.map((_, 1))
    println(mapRDD.dependencies)
    println("-------------------------")

    val resRDD: RDD[(Strin
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值