前言
withScope是RDDOperationScope的函数,本文主要介绍RDDOperationScope,其主要是包围执行给定的代码块,使得此代码块中创建的所有RDD具有相同的范围
1. 适用场景
该源码文件是 Spark 中 RDDOperationScope 的实现,用于构建和跟踪 RDD 作用域的层次结构。主要适用于以下场景:
- 在 Spark 内部跟踪 RDD 操作的层次结构,以便在调试、优化和监控过程中使用。
- 记录 RDD 操作的序列,以便追溯 RDD 的来源和转换过程。
- 构建 RDD 操作的树状结构,帮助理解和分析 RDD 的依赖关系。
2. 方法总结
RDDOperationScope 类主要包含以下方法:
-
toJson: String
- 功能:将 RDDOperationScope 对象转换为 JSON 字符串形式。
- 返回值:RDDOperationScope 对象的 JSON 字符串表示。
-
getAllScopes: Seq[RDDOperationScope]
- 功能:返回包含当前作用域在内的所有父级作用域的列表。
- 返回值:RDDOperationScope 对象的列表,按从外到内的顺序排序。
-
equals(other: Any): Boolean
- 功能:判断两个 RDDOperationScope 对象是否相等。
- 参数:需要比较的另一个对象 other。
- 返回值:若两个对象相等则返回 true,否则返回 false。
-
hashCode(): Int
- 功能:计算 RDDOperationScope 对象的哈希值。
- 返回值:RDDOperationScope 对象的哈希值。
-
toString: String
- 功能:将 RDDOperationScope 对象转换为字符串形式。
- 返回值:RDDOperationScope 对象的字符串表示。
-
fromJson(s: String): RDDOperationScope
- 功能:将 JSON 字符串转换为 RDDOperationScope 对象。
- 参数:需要解析的 JSON 字符串 s。
- 返回值:解析后得到的 RDDOperationScope 对象。
-
nextScopeId(): Int
- 功能:生成全局唯一的作用域 ID。
- 返回值:下一个可用的作用域 ID。
-
withScope[T](sc: SparkContext, allowNesting: Boolean = false)(body: => T): T
- 功能:执行给定的代码块,使其中创建的所有 RDD 具有相同的作用域。
- 参数:
- sc: 当前的 SparkContext。
- allowNesting: 是否允许嵌套的作用域,默认为 false。
- body: 需要执行的代码块。
- 返回值:代码块的返回值。
3.主要用法
RDDOperationScope
是一个表示操作的通用命名代码块,用于实例化RDD。以下是它的一些主要用法和代码示例:
构造函数:
val scope = new RDDOperationScope("scopeName", parentScope, "scopeId")
scopeName
:操作范围的名称,作为标识符。parentScope
:父级操作范围(可选)。如果存在,则当前操作范围将成为父级操作范围的子级。scopeId
:操作范围的唯一ID。
toJson方法:
val json = scope.toJson
- 返回操作范围的JSON字符串表示。
getAllScopes方法:
val scopes = scope.getAllScopes
- 返回包括当前操作范围在内的所有父级操作范围的列表。
- 结果从最外层范围(最老的祖先)到当前范围排序。
equals方法和hashCode方法:
val isEqual = scope.equals(otherScope)
val hashCode = scope.hashCode()
equals
方法用于比较两个操作范围是否相等。hashCode
方法返回操作范围的哈希码。
toString方法:
val str = scope.toString
- 返回操作范围的JSON字符串表示。
4.中文注释
/**
* 通用的、带名称的代码块,表示实例化RDD的操作。
*
* 所有在对应代码块中实例化的RDD将存储指向该对象的指针。
* 例如,包括但不限于textFile、reduceByKey和treeAggregate等现有的RDD操作。
*
* 操作范围可以嵌套在其他范围中。例如,一个SQL查询可能会包含在其底层使用的公共RDD API相关联的范围内。
*
* 操作范围与阶段或作业之间没有特定关系。
* 范围可以存在于一个阶段(例如map)中,也可以跨多个作业(例如take)。
*/
@JsonInclude(Include.NON_NULL)
@JsonPropertyOrder(Array("id", "name", "parent"))
private[spark] class RDDOperationScope(
val name: String,
val parent: Option[RDDOperationScope] = None,
val id: String = RDDOperationScope.nextScopeId().toString) {
def toJson: String = {
RDDOperationScope.jsonMapper.writeValueAsString(this)
}
/**
* 返回此范围所属的所有范围的列表,包括此范围本身。
* 结果按照从最外层范围(最老的祖先)到此范围的顺序排序。
*/
@JsonIgnore
def getAllScopes: Seq[RDDOperationScope] = {
parent.map(_.getAllScopes).getOrElse(Seq.empty) ++ Seq(this)
}
override def equals(other: Any): Boolean = {
other match {
case s: RDDOperationScope =>
id == s.id && name == s.name && parent == s.parent
case _ => false
}
}
override def hashCode(): Int = Objects.hashCode(id, name, parent)
override def toString: String = toJson
}
/**
* 用于构建RDD范围的分层表示的实用方法集合。
* RDD范围跟踪创建给定RDD的一系列操作。
*/
private[spark] object RDDOperationScope extends Logging {
private val jsonMapper = new ObjectMapper().registerModule(DefaultScalaModule)
private val scopeCounter = new AtomicInteger(0)
def fromJson(s: String): RDDOperationScope = {
jsonMapper.readValue(s, classOf[RDDOperationScope])
}
/** 返回全局唯一的操作范围ID。 */
def nextScopeId(): Int = scopeCounter.getAndIncrement
/**
* 执行给定的代码块,使得此代码块中创建的所有RDD具有相同的范围。
* 范围的名称将是堆栈跟踪中第一个不同于该方法的方法名。
*
* 注意:不允许在代码块中使用return语句。
*/
private[spark] def withScope[T](
sc: SparkContext,
allowNesting: Boolean = false)(body: => T): T = {
val ourMethodName = "withScope"
val callerMethodName = Thread.currentThread.getStackTrace()
.dropWhile(_.getMethodName != ourMethodName)
.find(_.getMethodName != ourMethodName)
.map(_.getMethodName)
.getOrElse {
// 以防万一记录一个警告,但这几乎肯定不会发生
logWarning("无有效的方法名用于此RDD操作范围!")
"N/A"
}
withScope[T](sc, callerMethodName, allowNesting, ignoreParent = false)(body)
}
/**
* 执行给定的代码块,使得此代码块中创建的所有RDD具有相同的范围。
*
* 如果允许嵌套,则在给定代码块中对此方法的任何后续调用将实例化位于我们范围内的子范围。否则,这些调用将不起作用。
*
* 另外,此方法的调用者可以选择忽略更高级别调用者设置的配置和范围。
* 在这种情况下,此方法将忽略
*/
private[spark] def withScope[T](
sc: SparkContext,
name: String,
allowNesting: Boolean,
ignoreParent: Boolean)(body: => T): T = {
// Save the old scope to restore it later
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) {
// 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) {
// Otherwise, set the scope only if the higher level caller allows us to do so
sc.setLocalProperty(scopeKey, new RDDOperationScope(name, oldScope).toJson)
}
// Optionally disallow the child body to override our scope
if (!allowNesting) {
sc.setLocalProperty(noOverrideKey, "true")
}
body
} finally {
// Remember to restore any state that was modified before exiting
sc.setLocalProperty(scopeKey, oldScopeJson)
sc.setLocalProperty(noOverrideKey, oldNoOverride)
}
}