spark源码学习(十):map端计算结果缓存处理(二)

来源:互联网 发布:人脸姿态估计源码 编辑:程序博客网 时间:2024/06/06 07:25

                           spark源码学习(十):map端计算结果缓存处理(二)


       在介绍下面的内容之前,先介绍几个相关的概念:

       (1) bypassMergeThreshold:表示是在map端做合并还是在reduce端做合并的一个参考数值。当partitions的个数小于这个阈值的时候,不需要在executor执行聚合和排序操作,只需要把各个partition写到executor的存储文件,最后在reduce端在做串联。可以设置set

spark.shuffle.sort.bypassMergeThreshold=num来修改这个数值。

      (2) bypassMergeSort:他是一个布尔值,标志是否直接 将各个partition写入Executor的存储文件。当我们没有定义aggregator,ordering函数,并且partition的数量小于上面的那个阈值函数的时候,会把map中间结果直接输出到磁盘,此时并不会占用太多的内存,避免了OOM。

       

      有了上面的预备,来看看map到底是怎样处理中间的计算结果的,大致上分为三种方式:

      <1>map端的计算结果在缓存中执行聚合和排序。

      <2>map会直接把各个partition写到自己的存储文件,最后由reduce统一进行合并和排序。

      <3>map端计算结果的简单缓存。

        上面的第二种情况就是当bypassmergeSort的数值为true的时候才会执行的。啰嗦了这么多,我们想知道,map任务到底是怎样执行的呢?在taskrunner.run函数中我们会最终会看到runtask函数,这是一个被子类实现的函数,具体的例如shuffleMapTask中的writer.write( ) 方法就是写map的中间结果,具体的进入到iterator会发现是迭代的去执行computeOrReadCheckpoint函数,有检查点的话就去读,否则就重新计算(compute函数)。compute函数是rdd中最重要的一个方法,主要是在rdd的每一个partition上运行task任务。我们知道在触发一个active之后,会进行stage的依赖划分以及相应的resultStage和activeJob的产生,之后运行的task是从最后一个RDD去开始运行的,但是任务怎么可以这样倒着运行?不可能吧,其实在运行最后一个resultTask的时候是倒着迭代iterator-->computeOrReadCheckpoint-->compute-->iteraotr(父类的)-->.....迭代去执行的。例如在wordcount中直到我们发现了HadoopRDD.compute函数才会从头到位的去执行任务。如下图所示:


         

       现在开始进入正题,shuffleMapTask具体是怎样执行任务的。首先进入write.write方法去看看究竟。我们经过一段时间按照上图的迭代,就会发现进入了HadoopRdd的compute方法,至于这个class是哪里来的,以后讨论,这里不管它,进入sortShuffleWriter的write实现,然后在进入sort.insertAll( )方法的实现,在进入最最核心的map.changeValue方法的实现,这个方法会创建两个关键createCombiner和CombineValue,前一个是创建一个(#,1)的数据类型,后一个主要是执行对应value的相加,也就相当于map(x=>(x,1))和reducebykey操作。changeValue就会去执行这个两个函数,显示出函数式编程的威力,具体的下面与讨论。

 /**   * Set the value for key to updateFunc(hadValue, oldValue), where oldValue will be the old value   * for key, if any, or null otherwise. Returns the newly updated value.   */  def changeValue(key: K, updateFunc: (Boolean, V) => V): V = {    assert(!destroyed, destructionMessage)//这里的k就是需要合并的value对应的key-->oldValue// oldValue和经过迭代器iterator得到的value进行相加得到newValue//这里表示的是当前的key是null,那么当然找不到对应的value与之相加,那么就直接返回就可以//返回的就是(#,1)    val k = key.asInstanceOf[AnyRef]    if (k.eq(null)) {      if (!haveNullValue) {        incrementSize()//增加data的大小      }  //haveNullValue代表的是false,也就是执行createCombiner函数      nullValue = updateFunc(haveNullValue, nullValue)      haveNullValue = true      return nullValue    }//计算得到当期的key插入data数组中的位置    var pos = rehash(k.hashCode) & mask    var i = 1//相当于hashCode    while (true) {      val curKey = data(2 * pos)      if (k.eq(curKey) || k.equals(curKey))   {//这里的代表的是执行combineValue类型的函数,当前的CurKey在data中可以找到对应的k  //data(2 * pos + 1).asInstanceOf[V]代表的是oldValue  //那和oldValue相加的数值在哪里呢?  //由insertAll中迭代iterator的while循环得到kv=records.next()得到的(key,value)        val newValue = updateFunc(true, data(2 * pos + 1).asInstanceOf[V])        data(2 * pos + 1) = newValue.asInstanceOf[AnyRef]        return newValue      }   else if (curKey.eq(null))   {//这里代表的是执行CreateCombiner函数,当前的CurKey在data中找不到对应的k    //当前的CurKey等于null//既然等于null(代表原来没有)那么在合并之后当然就要incrementSize()        val newValue = updateFunc(false, null.asInstanceOf[V])        data(2 * pos) = k        data(2 * pos + 1) = newValue.asInstanceOf[AnyRef]        incrementSize()        return newValue      } else   {//当前的CurKey不等于null并且不等于k,迭代一直找到和上面两种情况相匹配的条件        val delta = i        pos = (pos + delta) & mask        i += 1      }    }    null.asInstanceOf[V] // Never reached but needed to keep compiler happy  }

     具体的注释很清楚,就不多说啦。

    

0 0
原创粉丝点击