spark RDD join的核心过程
来源:互联网 发布:mac是什么意思 编辑:程序博客网 时间:2024/06/03 18:36
spark RDD join的核心过程
spark join的过程是查询过程中最核心的过程,怎么做到实现两个表的关联查询耗费资源最少。可看源码如下
join的实现在 PairRDDFunctions类当中。
def join[W](other: RDD[(K, W)], partitioner: Partitioner): RDD[(K, (V, W))] = self.withScope {this.cogroup(other, partitioner).flatMapValues( pair => // _1 是左表,_2 是右表的值,这是一个笛卡尔积的过程,key 一样,左表和右表各一些数据 for (v <- pair._1.iterator; w <- pair._2.iterator) yield (v, w))}
可以看到上面,自身RDD和其它的RDD进行数据的关联,同时传进去partitioner对象
def cogroup[W](other: RDD[(K, W)], partitioner: Partitioner) : RDD[(K, (Iterable[V], Iterable[W]))] = self.withScope {if (partitioner.isInstanceOf[HashPartitioner] && keyClass.isArray) { // hash分区方式不能用于key是数组的对象 throw new SparkException("Default partitioner cannot partition array keys.")}// join操作中很核心的执行类val cg = new CoGroupedRDD[K](Seq(self, other), partitioner)cg.mapValues { case Array(vs, w1s) => (vs.asInstanceOf[Iterable[V]], w1s.asInstanceOf[Iterable[W]])}}
然后创建 CoGroupedRDD 专门用于RDD的关联操作对象。我们现在完整分析CoGroupedRDD源码
override def getDependencies: Seq[Dependency[_]] = {rdds.map { rdd: RDD[_] => if (rdd.partitioner == Some(part)) { logDebug("Adding one-to-one dependency with " + rdd) // 该RDD和 join合并的分区partitioner一样 new OneToOneDependency(rdd) } else { logDebug("Adding shuffle dependency with " + rdd) // 当partitioner不一样时,要对数据进行重新分区,就是shuff的过程 new ShuffleDependency[K, Any, CoGroupCombiner]( rdd.asInstanceOf[RDD[_ <: Product2[K, _]]], part, serializer) }}}
上面就是这个join的相关RDD依赖,如果part分区一样,就是OneToOneDependency依赖,不用进行hash拆分。否则
要关联的RDD和part的分区不一致时,就要对RDD进行重新hash分区,分到正确的分片上面,所以就要用ShuffleDependency 进行
hash分片数据,然后在正确的split分片处理业务进程中进行处理。
override def getPartitions: Array[Partition] = {// 这里对数据进行分片,一个分片就在一台work进程中进行处理了val array = new Array[Partition](part.numPartitions)for (i <- 0 until array.length) { // Each CoGroupPartition will have a dependency per contributing RDD array(i) = new CoGroupPartition(i, rdds.zipWithIndex.map { case (rdd, j) => // Assume each RDD contributed a single dependency, and get it dependencies(j) match { case s: ShuffleDependency[_, _, _] => // 当这个数据要进行shuffler时 None case _ => // 当分区是一样时,就直接进行了 Some(new NarrowCoGroupSplitDep(rdd, i, rdd.partitions(i))) } }.toArray)}// 这样就可以把关联的RDD拆成了numPartitions分了array}
上面就是对各个关联的数据进行hash分片了,就是有几个RDD,然后根据它们的key进行hash分片,分到正确的partition中,如果是 OneToOneDependency 就不用进行数据的再拆分片了,ShuffleDependency 就要通过传进去的part对key进行分片,把所有一样的key
分到同样的split数据分片当中。这样各个RDD一样的key就在一样的,就可以执行关联操作了。
override def compute(s: Partition, context: TaskContext): Iterator[(K, Array[Iterable[_]])] = {// 在其中一个work进程中执行这一分区数据了val split = s.asInstanceOf[CoGroupPartition]// 依赖这么多RDDval numRdds = dependencies.length// A list of (rdd iterator, dependency number) pairs// 拿这个分片的数据进行计算val rddIterators = new ArrayBuffer[(Iterator[Product2[K, Any]], Int)]for ((dep, depNum) <- dependencies.zipWithIndex) dep match { case oneToOneDependency: OneToOneDependency[Product2[K, Any]] @unchecked => // 依赖于 depNum 那个RDD的分片数据 val dependencyPartition = split.narrowDeps(depNum).get.split // Read them from the parent // 在这个work进程中读取这个分片数据 val it = oneToOneDependency.rdd.iterator(dependencyPartition, context) rddIterators += ((it, depNum)) case shuffleDependency: ShuffleDependency[_, _, _] => // Read map outputs of shuffle // 说明之前对这个RDD 的数据进行分片hash过的了 // 然后这里专门去拉取该分片对应的数据回来 val it = SparkEnv.get.shuffleManager .getReader(shuffleDependency.shuffleHandle, split.index, split.index + 1, context) .read() rddIterators += ((it, depNum))}// 创建一个多个RDD的合并器val map = createExternalMap(numRdds)for ((it, depNum) <- rddIterators) { map.insertAll(it.map(pair => (pair._1, new CoGroupValue(pair._2, depNum))))}context.taskMetrics().incMemoryBytesSpilled(map.memoryBytesSpilled)context.taskMetrics().incDiskBytesSpilled(map.diskBytesSpilled)context.internalMetricsToAccumulators( InternalAccumulator.PEAK_EXECUTION_MEMORY).add(map.peakMemoryUsedBytes)// 结果就这样排好序的了new InterruptibleIterator(context, map.iterator.asInstanceOf[Iterator[(K, Array[Iterable[_]])]])}
再来研究一下compute方法,这个方法就是对当前要计算的split进行处理的,上面已经对多个RDD进行hash分片了,然后把
相同的key都分片到这里来了,如果是oneToOneDependency就直接读取那个分片数据,否则就要启动对RDD的shuffle的过程
把一个RDD通过hash分到多个分片当中,然后该函数拉取自己需求的那一个分片数据。当该split需求的分片数据准备好后,就创建
下面的ExternalAppendOnlyMap 类进行对数据的排序关联功能了。
private def createExternalMap(numRdds: Int): ExternalAppendOnlyMap[K, CoGroupValue, CoGroupCombiner] = {// 创建一个多个RDD的合并器,key value rdd_index// value._2 应该是rdd的index value._1 应该是value// 初始化 rdd_index --> valueval createCombiner: (CoGroupValue => CoGroupCombiner) = value => { val newCombiner = Array.fill(numRdds)(new CoGroup) newCombiner(value._2) += value._1 newCombiner}// 中间过程数据的合并 rdd_index --> valueval mergeValue: (CoGroupCombiner, CoGroupValue) => CoGroupCombiner = (combiner, value) => { combiner(value._2) += value._1 combiner}// 最后所有 rdd数据的合并val mergeCombiners: (CoGroupCombiner, CoGroupCombiner) => CoGroupCombiner = (combiner1, combiner2) => { var depNum = 0 while (depNum < numRdds) { combiner1(depNum) ++= combiner2(depNum) depNum += 1 } combiner1 }// key 在这个 对象里面自己管控,各业务方法只要缓存value和rdd_index的关系就好了new ExternalAppendOnlyMap[K, CoGroupValue, CoGroupCombiner]( createCombiner, mergeValue, mergeCombiners)}
可以看到这里有一个公共的多RDD数据联合器,只要把数据往里面插入进去,就自动进行数据的关联操作了。
最后就返回排好序的InterruptibleIterator对象,实现多RDD的联合join。
下面再看下 left out join
def leftOuterJoin[W]( other: RDD[(K, W)], partitioner: Partitioner): RDD[(K, (V, Option[W]))] = self.withScope {this.cogroup(other, partitioner).flatMapValues { pair => if (pair._2.isEmpty) { // 当是右表为空时,左表也要输出 pair._1.iterator.map(v => (v, None)) } else { for (v <- pair._1.iterator; w <- pair._2.iterator) yield (v, Some(w)) }}}
可以看到,当是 left out join 时,底层也是一样关联的,只是在外面通过判断左表有值时,也进行输出。
同时,right out join也一样。
总结
- 传入多个RDD对象
- 判断该RDD对象是否和给定的part分区函数一致,如果是就直接拉取对应的分区,否则就shuffle,hash分片数据,然后拉取
- 把相同partition分片后的数据发到对应work进程中进行读取
- 然后在该work业务进程中,单独对这hash分片一致的数据进行关联操作
- 最后返回有序iterator对象
- spark RDD join的核心过程
- 理解Spark的核心RDD
- 理解Spark的核心RDD
- 理解Spark的核心RDD
- 理解Spark的核心RDD
- 理解Spark的核心RDD
- 理解Spark的核心RDD
- Spark核心RDD的研究
- 理解Spark的核心RDD
- 理解Spark的核心RDD
- 理解Spark的核心RDD
- 理解Spark的核心RDD
- 理解Spark的核心RDD
- 理解Spark的核心RDD
- 理解Spark的核心RDD
- 理解Spark的核心RDD
- Spark的核心概念——RDD
- Spark核心RDD理解
- 异步任务(AsyncTask)
- Java开发中的23种设计模式详解(转)
- [APP开发技巧] video标签在IOS和安卓上的非全屏自动播放
- 苹果、三星手机无线充电解析
- spark下载安装和第一个Wordcount程序
- spark RDD join的核心过程
- python3 资源分享
- USB2.0协议分析
- 使用log4j是遇到的错误以及解决
- TP5验证
- 机器学习中的数据预处理中的标准化(sklearn preprocessing)
- Activiti工作流框架学习(二)——使用Activiti提供的API完成流程操作
- 设计高素质的UI应该掌握这7个关键属性
- yum 安装httpd时,遇到错误apr_sockaddr_info_get() failed for kickstart;Could not reliably determine the serve