宽窄依赖以及shuffle的部分源码理解

相关文章:Hadoop/Spark的shuffle理论知识
注:此文不谈宽窄依赖的概念以及计算失败时恢复的区别【关于这方面的博文,书籍都有不少但几乎雷同的讲解,搜索即可,比如】,仅从源码的角度学习下实现区别,并验证一些说法。

搜索Dependency,有以下类:
这里写图片描述

Dependency.scala

Dependency(总的基类)

/**
 * :: DeveloperApi ::
 * Base class for dependencies.
 * 所有依赖的父类(基类),抽象类
 */
@DeveloperApi
abstract class Dependency[T] extends Serializable {
  def rdd: RDD[T]
}
  • 可以看到,这个类就仅仅继承了Serializable接口,按照Java的思路的话,按理是implements实现接口 。但是Scala中无论是继承还是实现trait都用关键字extends。
  • java.io.Serializable :
  1. 序列化:对象的寿命通常随着生成该对象的程序的终止而终止,有时候需要把在内存中的各种对象的状态(也就是实例变量,不是方法)保存下来,并且可以在需要时再将对象恢复。
  2. Java 序列化技术可以使你将一个对象的状态写入一个Byte 流里(系列化),并且可以从其它地方把该Byte 流里的数据读出来(反序列化)。
  3. 用途:想把的内存中的对象状态保存到一个文件中或者数据库中时候;想把对象通过网络进行传播的时候
  4. 如果是通过网络传输的话,如果serialVersionUID不一致,那么反序列化就不能正常进行。
  5. 序列化保存的是对象的状态,静态变量属于类的状态,因此 序列化并不保存静态变量。
  6. 当某些变量不想被序列化,同是又不适合使用static关键字声明,那么此时就需要用transient关键字来声明该变量。
  7. 当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口。
    参考[1]
  • 序列化与持久化的区别:
  1. 简单的说持久化就是save/load;序列化就是把结构化的对象转化成无结构的字节流。
  2. 序列化是为了解决对象的传输问题,传输可以在线程之间、进程之间、内存外存之间、主机之间进行。
  3. 你也可以说将一个对象序列化后存储在数据库中,但是你也不能说是对象持久化

NarrowDependency(窄依赖基类)

/**
 * 窄依赖的基类,窄依赖允许流水线执行.
 */
@DeveloperApi
abstract class NarrowDependency[T](_rdd: RDD[T]) extends Dependency[T] {
  /**
   * Get the parent partitions for a child partition.
   * 通过partitionId得到子RDD的某个partition依赖父RDD的所有partitions.
   * @param partitionId a partition of the child RDD
   * @return the partitions of the parent RDD that the child partition depends upon(子RDD的每个分区依赖少量(一个或多个)父RDD分区)
   */
   // 为什么返回的是Seq[Int]?
  def getParents(partitionId: Int): Seq[Int]

  override def rdd: RDD[T] = _rdd
}

这里的getParents也是抽象方法

ShuffleDependency(宽依赖实现类)

该类主要实现创建新的shuffleId,以及新的shuffle,并把该shuffle相关的信息捆绑于shuffleHandle,shuffleManager用shuffleHandle传递信息给Tasks,并进行一些旧shuffle的清理工作。比如shuffleId,状态,广播变量等。

@DeveloperApi
// ClassTag参考:https://docs.scala-lang.org/overviews/reflection/typetags-manifests.html,https://blog.csdn.net/dax1n/article/details/77036447.
class ShuffleDependency[K: ClassTag, V: ClassTag, C: ClassTag](
    @transient private val _rdd: RDD[_ <: Product2[K, V]],
    val partitioner: Partitioner,
    val serializer: Serializer = SparkEnv.get.serializer,
    val keyOrdering: Option[Ordering[K]] = None,
    val aggregator: Option[Aggregator[K, V, C]] = None,
    val mapSideCombine: Boolean = false)
  extends Dependency[Product2[K, V]] {

  override def rdd: RDD[Product2[K, V]] = _rdd.asInstanceOf[RDD[Product2[K, V]]]

  private[spark] val keyClassName: String = reflect.classTag[K].runtimeClass.getName
  private[spark] val valueClassName: String = reflect.classTag[V].runtimeClass.getName
  // Note: It's possible that the combiner class tag is null, if the combineByKey
  // methods in PairRDDFunctions are used instead of combineByKeyWithClassTag.
  private[spark] val combinerClassName: Option[String] =
    Option(reflect.classTag[C]).map(_.runtimeClass.getName)

  // 产生新的shuffleId,表明要进行新的Shuffle操作.
  val shuffleId: Int = _rdd.context.newShuffleId()
  // ShuffleManager使用一个shuffle句柄将关于它的信息传递给Task,
  // 获取注册该RDD的SparkContext,再获取构建SparkContext时创建的SparkEnv,向SparkEnv中创建的shuffleManager注册shuffle,注册的信息捆绑于shuffleHandle,shuffleManager用shuffleHandle传递信息给Tasks
  val shuffleHandle: ShuffleHandle = _rdd.context.env.shuffleManager.registerShuffle(
    shuffleId, _rdd.partitions.length, this)
// 清除该Shuffle的一些信息,比如状态,与之相关的广播变量等
  _rdd.sparkContext.cleaner.foreach(_.registerShuffleForCleanup(this))
}
  1. Product2 is a Cartesian product of 2 components.
  2. Product, Equals, Any的子类,Tuple2的父类
  3. MORE:https://www.scala-lang.org/api/current/scala/Product2.html
  • shuffle垃圾清理
/** Register a ShuffleDependency for cleanup when it is garbage collected. */
  def registerShuffleForCleanup(shuffleDependency: ShuffleDependency[_, _, _]): Unit = {
    registerForCleanup(shuffleDependency, CleanShuffle(shuffleDependency.shuffleId))
  }
/** Register a ShuffleDependency for cleanup when it is garbage collected. */
    def registerShuffleForCleanup(shuffleDependency: ShuffleDependency[_, _, _]): Unit = {
      registerForCleanup(shuffleDependency, CleanShuffle(shuffleDependency.shuffleId))
    }
case CleanShuffle(shuffleId) =>
                doCleanupShuffle(shuffleId, blocking = blockOnShuffleCleanupTasks)

真正执行清理工作的方法是:ContextCleaner.scala中的doCleanupShuffle,unregisterShuffle等

mapStatuses、cachedSerializedStatuses、shuffleIdLocks都是线程安全的ConcurrentHashMap,cachedSerializedBroadcast是HashMap

/** Unregister shuffle data */
  override def unregisterShuffle(shuffleId: Int) {
    // 移除多个HashMap中的shuffleId
    mapStatuses.remove(shuffleId)
    cachedSerializedStatuses.remove(shuffleId)
    // 还要移除该shuffle上的所有广播变量.
    cachedSerializedBroadcast.remove(shuffleId).foreach(v => removeBroadcast(v))
    // shuffleIdLocks是为了防止同一个shuffle的多次序列化,比如说:在shuffle开始时以及发生请求风暴时.
    shuffleIdLocks.remove(shuffleId)
  }

OneToOneDependency(窄依赖实现类)

仅一个对基类中的getParents的重写方法,不太理解getParents为何返回的是Int数,还有List(partitionId)这个”操作“。

@DeveloperApi
class OneToOneDependency[T](rdd: RDD[T]) extends NarrowDependency[T](rdd) {
  override def getParents(partitionId: Int): List[Int] = List(partitionId)
}

RangeDependency(窄依赖实现类)

有朋友说RangeDependency仅在Union操作中用到,暂时未验证。

@DeveloperApi
class RangeDependency[T](rdd: RDD[T], inStart: Int, outStart: Int, length: Int)
  extends NarrowDependency[T](rdd) {

  override def getParents(partitionId: Int): List[Int] = {
    if (partitionId >= outStart && partitionId < outStart + length) {
      List(partitionId - outStart + inStart)
    } else {
      Nil
    }
  }
}

窄依赖的函数有:
map, filter, union, join(父RDD是hash-partitioned ), mapPartitions, mapValues等

宽依赖的函数有:
groupByKey, join(父RDD不是hash-partitioned ), partitionBy、sortByKey、reduceByKey、countByKey等

TODO

[1] ConcurrentHashMap和HashMap的进阶理解
[2] 不太理解getParents为何返回的是Int数,还有List(partitionId)这个”操作“

参考

[1]序列化

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值