最近在学习spark时候老是遇到withScope函数的运用,看的我也是一脸懵逼,花了好长的时间也是终于弄懂了,憋说话,看屏幕,直接上代码:
而我们经常见得调用就是如下格式的:
def sortByKey(ascending: Boolean = true, numPartitions: Int = self.partitions.length)
: RDD[(K, V)] = self.withScope
{
val part = new RangePartitioner(numPartitions, self, ascending)
new ShuffledRDD[K, V, V](self, part)
.setKeyOrdering(if (ascending) ordering else ordering.reverse)
}
这个withScope方法如下:
//上面{}内容就是传参给body
private[spark] def withScope[U](body: => U): U = RDDOperationScope.withScope[U](sc)(body)
从形式上看,上面代码的花括号{}里面的几行代码就是一个匿名无参函数,直接传给了(body:=>U),而实际调用的是:
RDDOperationScope.withScopeU(body)这个方法。
这里涉及到一个知识点:柯里化
我普及一下柯里化知识点:就是把接收多个参数的函数变为接收一个单一参数的函数,将第一个参数运算得到的值传给下一个函数一起运算,从而得到一个新的函数个过程,讲的有点复杂,看例子:
例如:
def add(x:Int,y:Int)=x+y
柯里化后:
def add(x:Int)(y:Int)=x+y
柯里化的函数你可以这么理解:def add=(x:Int)=>(y:Int)=>x+y
具体可以参考如下的文章去理解:https://blog.csdn.net/onwingsofsong/article/details/77822920
OK,我们回到主题:RDDOperationScope.withScopeU(body)的中就是用到了柯里化的技术,他的作用就是把公共部分(函数体)抽出来封装成方法(这里的公共部分我的理解就是sc,也就是sparkCont), 把非公共部分通过函数值传进来(这里就是body的内容了),为什么要这么做呢:
withScope是最近的发现版中新增加的一个模块,它是用来做DAG可视化的(DAG visualization on SparkUI)
以前的sparkUI中只有stage的执行情况,也就是说我们不可以看到上个RDD到下个RDD的具体信息。于是为了在
sparkUI中能展示更多的信息。所以把所有创建的RDD的方法都包裹起来,同时用RDDOperationScope 记录 RDD 的操作历史和关联,就能达成目标。下图所示就是一个DAG图片了
我参考了如下文章:
原文链接:https://blog.csdn.net/lhui798/article/details/51860969
OK,现在我们知道了withScope的目的和用到了技术了,下面技术分析该函数用到的源码:
也就是RDDOperationScope.withScopeU(body)里面的源码:
private[spark] def withScope[T](
sc: SparkContext,
//是否允许嵌套
allowNesting: Boolean = false)(body: => T): T = {
///设置跟踪堆的轨迹的scope名字
val ourMethodName = "withScope"
//下面这段代码其实就是将调用的方法名选出来,getStackTrace我就不说了,获取栈轨
val callerMethodName = Thread.currentThread.getStackTrace()
//dropWhile就是移除前几个匹配断言函数的元素,意思就是如果该线程下的方法名不是ourMethodName(withScope)那就移除
.dropWhile(_.getMethodName != ourMethodName)
**//再从剩下的元素中选出方法名不是ourMethodName(withScope),老实说我也不明白为什么多此一举?**
.find(_.getMethodName != ourMethodName)
.map(_.getMethodName)
.getOrElse {
// Log a warning just in case, but this should almost certainly never happen
logWarning("No valid method name for this RDD operation scope!")
"N/A"
}
//实际调用的是这个方法:
withScope[T](sc, callerMethodName, allowNesting, ignoreParent = false)(body)
}
withScope[T](sc, callerMethodName, allowNesting, ignoreParent = false)(body)函数的源码如下:欢迎补充说明,并告诉我哈
private[spark] def withScope[T](
sc: SparkContext,
name: String,
allowNesting: Boolean,
ignoreParent: Boolean)(body: => T): T = {
//先保存老的scope,之后恢复它
val scopeKey = SparkContext.RDD_SCOPE_KEY
val noOverrideKey = SparkContext.RDD_SCOPE_NO_OVERRIDE_KEY
val oldScopeJson = sc.getLocalProperty(scopeKey)
val oldScope = Option(oldScopeJson).map(RDDOperationScope.fromJson)
val oldNoOverride = sc.getLocalProperty(noOverrideKey)
try {
if (ignoreParent) {//当ignorePatent设置为true的时候,那么回忽略之前的全部设置和scope ,从我们自己的根scope重新开始
// Ignore all parent settings and scopes and start afresh with our own root scope
sc.setLocalProperty(scopeKey, new RDDOperationScope(name).toJson)
} else if (sc.getLocalProperty(noOverrideKey) == null) {
// 否则,仅在上级调用者允许时才设置scope
sc.setLocalProperty(scopeKey, new RDDOperationScope(name, oldScope).toJson)
}
//可选地,不允许子body覆盖我们的scope
if (!allowNesting) {
sc.setLocalProperty(noOverrideKey, "true")
}
body
} finally {
// 记住,在退出之前要保存任何修改过的状态
sc.setLocalProperty(scopeKey, oldScopeJson)
sc.setLocalProperty(noOverrideKey, oldNoOverride)
}
}