Spark 内核泛指 Spark 的核心运行机制,包括 Spark 核心组件的运行机制、Spark 任务调度机制、Spark 内存管理机制、Spark 核心功能的运行原理等,熟练掌握 Spark 内核原理,能够帮助我们更好地完成 Spark 代码设计,并能够帮助我们准确锁定项目运行过程中出现的问题的症结所在。
1.1 Spark 核心组件回顾
1.1.1 Driver
Spark 驱动器节点,用于执行 Spark 任务中的 main 方法,负责实际代码的执行工作。Driver 在 Spark 作业执行时主要负责:
- 将用户程序转化为作业(Job);
- 在 Executor 之间调度任务(Task);
- 跟踪 Executor 的执行情况;
- 通过 UI 展示查询运行情况;
1.1.2 Executor
Spark Executor 对象是负责在 Spark 作业中运行具体任务,任务彼此之间相互独立。Spark
应用启动时,ExecutorBackend 节点被同时启动,并且始终伴随着整个 Spark 应用的生命周
期而存在。如果有 ExecutorBackend 节点发生了故障或崩溃,Spark 应用也可以继续执行,
会将出错节点上的任务调度到其他 Executor 节点上继续运行。
Executor 有两个核心功能:
- 负责运行组成 Spark 应用的任务,并将结果返回给驱动器(Driver);
- 它们通过自身的块管理器(Block Manager)为用户程序中要求缓存的 RDD 提供内存
式存储。RDD 是直接缓存在 Executor 进程内的,因此任务可以在运行时充分利用缓存
数据加速运算。
1.2 Spark 通用运行流程概述
上图为 Spark 通用运行流程图,体现了基本的 Spark 应用程序在部署中的基本提交流程。
这个流程是按照如下的核心步骤进行工作的:
- 任务提交后,都会先启动 Driver 程序;
- 随后 Driver 向集群管理器注册应用程序;
- 之后集群管理器根据此任务的配置文件分配 Executor 并启动;
- Driver 开始执行 main 函数,Spark 查询为懒执行,当执行到 Action 算子时开始反向推
算,根据宽依赖进行 Stage 的划分,随后每一个 Stage 对应一个 Taskset,Taskset 中有多
个 Task,查找可用资源 Executor 进行调度; - 根据本地化原则,Task 会被分发到指定的 Executor 去执行,在任务执行的过程中,
Executor 也会不断与 Driver 进行通信,报告任务运行情况。
概念解释:
懒执行
懒执行(Lazy Evaluation)是一种计算策略,它延迟计算表达式的值,直到该值被实际需要的时候才进行计算。与之相对,即时执行(Eager Evaluation)是立即计算表达式的值。
在懒执行中,表达式的计算被推迟,直到它的结果被使用时才进行计算。这种策略通常用于优化计算资源的使用,特别是在某些表达式的计算是耗时的或者在某些情况下可能根本不需要计算的情况下。通过懒执行,可以避免不必要的计算开销。
在编程语言中,懒执行通常通过延迟加载或惰性求值的机制来实现。例如,在Scala中,可以使用关键字 “lazy” 来定义懒执行的值或表达式。当使用这个懒执行的值时,如果它还没有被计算过,它会自动进行计算,并将计算结果缓存起来供后续使用。
下面是一个简单的Scala示例,演示了如何使用懒执行:
lazy val expensiveValue: Int = {
// 执行耗时的计算
println("Performing expensive calculation")
42
}
// 在使用值之前,不会进行计算
println("Before accessing value")
println(expensiveValue) // 计算并输出值
println("After accessing value")
println(expensiveValue) // 直接使用缓存的值
运行上述代码会产生以下输出:
Before accessing value
Performing expensive calculation
42
After accessing value
42
可以看到,当我们第一次访问 expensiveValue
时,耗时的计算会被执行,并将结果缓存起来。在后续访问时,直接使用缓存的结果,而不需要再次进行计算。
懒执行可以在某些场景下提高性能和资源利用率,但也需要注意懒执行可能引入的延迟和副作用。因此,在使用懒执行时,需要根据具体情况权衡利弊,并确保正确地处理可能的并发或线程安全问题。
宽依赖
宽依赖(Wide Dependency)是指在并行计算中,一个任务(或操作)所依赖的输入数据来自于多个不同的任务或操作。宽依赖通常被认为是并行计算中的一种性能瓶颈,因为它限制了任务的并行性。
在并行计算中,任务之间的依赖关系可以分为窄依赖(Narrow Dependency)和宽依赖。窄依赖表示一个任务只依赖于少数其他任务的输出数据,因此可以很容易地并行执行。而宽依赖表示一个任务依赖于大量其他任务的输出数据,导致任务之间存在较高的依赖关系,从而限制了并行执行的能力。
当存在宽依赖时,执行引擎需要等待所有相关任务都完成并生成输出数据,才能继续执行下一个任务。这可能导致任务之间的串行执行,从而降低了并行计算的效率。
在大规模数据处理框架(如Apache Spark)中,宽依赖是一个常见的挑战。当一个操作需要依赖于多个分区的数据时,就会产生宽依赖。这种情况下,计算引擎需要等待所有分区的数据都可用,才能进行下一步计算。
为了减少宽依赖对并行计算的影响,可以采取一些优化策略,例如引入更细粒度的任务划分,减少任务之间的依赖关系,或者使用更高级的调度和执行策略。这些策略可以帮助提高并行计算的性能和效率。
总而言之,宽依赖指的是在并行计算中,一个任务所依赖的输入数据来自于多个不同任务或操作,从而限制了任务的并行性和计算效率。
Spark Driver
在Apache Spark中,Driver是Spark应用程序的主要组件之一。它是运行在Spark集群上的一个进程,负责管理整个应用程序的执行过程,并与集群中的Executor进行通信。
Spark应用程序通常由一个Driver程序和多个Executor组成。Driver程序是应用程序的控制中心,负责以下几个主要任务:
-
解析应用程序:Driver程序负责解析用户编写的Spark应用程序代码,并构建应用程序的逻辑执行计划。
-
调度任务:Driver程序将应用程序的任务划分为不同的阶段(stage),并根据任务之间的依赖关系构建DAG(有向无环图)。它将这些任务分发给Executor进行并行执行。
-
维护元数据:Driver程序维护有关应用程序的元数据信息,例如任务进度、数据分区、依赖关系等。
-
提供交互界面:Driver程序通常还提供与应用程序交互的界面,例如接收用户输入、显示任务进度和结果等。
-
处理异常和容错:Driver程序监控应用程序的执行过程,并处理执行过程中可能出现的异常情况。它负责执行容错机制,以确保应用程序的可靠执行。
Driver程序在启动时会向集群管理器(如Standalone模式、YARN或Apache Mesos)请求资源,包括Executor的数量和内存分配等。一旦资源被分配,Driver程序将与Executor建立连接,并将任务发送给它们执行。
需要注意的是,Driver程序通常运行在应用程序的客户端,例如在提交应用程序的机器上。它与集群中的Executor通过网络进行通信,并将计算任务委托给Executor执行。因此,Driver程序需要具备足够的计算和内存资源来管理和协调整个应用程序的执行过程。
总结起来,Spark的Driver是运行在Spark集群上的一个进程,负责管理整个应用程序的执行过程,包括任务调度、数据划分、容错处理等。它与集群中的Executor进行通信,并将计算任务分发给它们执行。
Spark Executor
在Apache Spark中,Executor是运行在Spark集群中的工作进程,负责执行Spark应用程序中的具体任务。
每个Executor都在集群中的节点上启动,并接收来自Driver程序的任务。Executor负责在其所在的节点上分配计算资源,执行具体的任务代码,并将结果返回给Driver程序。Executor通常在集群中的每个节点上启动一个或多个实例,可以根据应用程序的需求进行扩展。
Executor的主要职责包括:
-
任务执行:Executor根据Driver程序发送的任务,执行具体的计算代码。它会将任务分解为更小的任务单元,并在分配给它的资源上进行并行执行。每个Executor都有自己的计算资源,包括CPU、内存和磁盘空间。
-
数据存储:Executor负责在其所在的节点上存储和管理任务执行过程中所涉及的数据。它会将数据加载到内存中,并在需要时进行持久化或序列化。
-
数据分片:Executor将输入数据划分为多个分片,并将这些分片分发给不同的任务进行处理。它负责将数据移动到计算节点上,以便任务可以在本地执行。
-
任务监控和日志记录:Executor会定期向Driver程序汇报任务的状态和进度信息,以及可能的异常情况。它还负责记录任务执行过程中的日志信息,以便进行故障排查和调试。
Executor是Spark集群中的计算单元,通过与Driver程序进行通信,执行分布式计算任务。它在集群中运行,并利用分配给它的计算资源来执行具体的计算代码。Executor的数量和规模可以根据应用程序的需求进行调整,以实现更高的并行度和吞吐量。
需要注意的是,Executor是由Spark集群管理器(如Standalone模式、YARN或Apache Mesos)动态分配和管理的。Spark应用程序的Driver程序会与集群管理器协商资源,并向其请求分配Executor。一旦资源被分配,Executor将启动并准备执行任务。
总结起来,Spark的Executor是在集群中运行的工作进程,负责执行具体的任务代码,存储和管理任务数据,并向Driver程序汇报任务的状态和进度。它是实际执行计算的核心组件,通过并行执行任务来实现高性能和分布式计算能力。
action算子
在Scala中,"Action"算子通常用于Apache Spark这样的分布式计算框架,用于触发对数据集的计算操作并返回结果,这些操作会触发Spark的执行引擎将计算任务发送到集群上执行。
以下是Scala中常用的一些Spark Action算子:
-
collect()
:将数据集中的所有元素收集到驱动程序中,并以数组的形式返回。 -
count()
:统计数据集中的元素个数,并返回结果。 -
first()
:返回数据集中的第一个元素。 -
take(n: Int)
:返回数据集中的前n个元素,并以数组的形式返回。 -
reduce(func: (T, T) => T)
:对数据集中的元素进行归约操作,使用给定的二元函数对元素进行聚合。 -
foreach(func: T => Unit)
:对数据集中的每个元素应用给定的函数。 -
saveAsTextFile(path: String)
:将数据集中的元素以文本文件的形式保存到指定路径。 -
countByKey()
:对包含键值对的数据集进行统计,返回每个键的出现次数。
这些Action算子都会触发Spark的计算过程,因此在使用它们之前,通常需要先对数据集进行一些转换操作,例如使用map()
、filter()
、reduceByKey()
等转换算子对数据进行预处理。
需要注意的是,这些Action算子在执行过程中可能会触发数据的广播和持久化等操作,因此在使用时需要根据具体的情况考虑数据量、性能和内存使用等因素。
更多精彩内容,参考下一节:
第 2 章 Spark 部署模式。
http://t.csdn.cn/0wF5m