Bootstrap

Spark 核心概念解析:DAG、分布式部署模式与共享变量

DAG 解决了什么问题?

DAG (有向无环图) 是 Spark 的核心架构之一,它的引入主要解决了 Hadoop MapReduce 框架的以下局限性:

  1. MapReduce 的独立性问题
    在 Hadoop MapReduce 中,任务的每一步(Map 和 Reduce)是独立的,系统无法提前知道后续的计算计划。这种方式无法对整个计算流程进行全局优化。
    Spark 的 DAG 允许系统通过解析操作链,提前构建完整的计算图,优化整个执行流程。
  2. 高频硬盘读写的性能瓶颈
    在 MapReduce 中,每一步计算的中间结果都会写入硬盘或 HDFS。频繁的磁盘 I/O 操作会导致性能的大幅降低。
    Spark 使用内存计算,并通过 DAG 优化操作顺序,避免不必要的磁盘操作,显著提升性能。

DAG 是如何工作的?

DAG 通过以下步骤优化计算和任务调度:

  1. 操作解析
    Operator Graph:当代码被提交到 Spark 的解释器时,系统会生成一个操作图,称为 Operator Graph。它记录了所有的 Transformation 操作及其依赖关系。
  2. DAG Scheduler
    分解阶段 (Stage):当一个 Action 操作(如 collect 或 save)被触发时,DAG Scheduler 会将 Operator Graph 分解为多个 Stage(阶段)。
  • 窄依赖(Narrow Dependency):操作如 map 和 filter 不需要数据重新分区,属于同一阶段。
  • 宽依赖(Wide Dependency):操作如 reduceByKey 需要数据 Shuffle,不同阶段之间以宽依赖为界。
  1. Task Scheduler
    每个 Stage 会被拆分为多个基于数据分区的 Task(任务)。
    Task Scheduler 将这些 Task 分发到集群的 Worker 节点上执行。
  2. 执行与结果
    每个 Worker 节点执行分配的 Task,并将结果返回给 Driver 程序。
    DAG 确保各个阶段按依赖顺序执行,并通过内存优化中间结果存储,最大限度减少 I/O 和通信开销。

DAG 的核心特性

  1. 任务依赖分析
    每个 RDD 包含父节点的元信息和依赖关系(窄依赖或宽依赖)。
    通过这些依赖,DAG Scheduler 能高效地决定任务的执行顺序。

  2. 内存计算优化
    通过减少 Shuffle 和磁盘读写,DAG 提高了计算效率。

  3. 全局优化
    DAG 确保每个 Stage 都包含最少的任务,避免重复计算。

三种 Spark 分布式部署模式

  1. Standalone 模式

特点

  • Spark 自带的资源管理器,无需依赖其他系统。
  • 支持集群的基本功能,易于部署和维护。

适用场景:

  • 小型测试环境或开发环境。
  • 不需要与其他框架共享资源。
  1. Spark on Mesos 模式

特点
官方推荐,灵活性更高。
支持两种资源分配模式:

  • 粗粒度模式 (Coarse-grained):一次性申请资源,适用于长时间运行的任务。
  • 细粒度模式 (Fine-grained):按需动态分配资源,适用于短作业任务。

适用场景:

  • 需要高效的资源共享和管理。
  • 与 Docker 等资源管理工具结合更自然。
  1. Spark on YARN 模式

特点
利用 Hadoop YARN 管理资源,适用于 Hadoop 集群。
支持两种模式:

  • YARN-cluster:适合生产环境,所有作业运行在集群中。
  • YARN-client:适合调试和交互式作业,Driver 在客户端运行。

适用场景:

  • 同时运行 Spark 和 Hadoop。
  • 对 YARN 的兼容性要求较高。

Spark 共享变量

在Spark中,通常当一个函数被分配到集群中的远程节点执行时,Spark会在每个节点上创建这个函数的独立副本。这样,每个节点都会处理这个函数的副本,而不会与驱动程序或其他节点共享变量。为了优化这种情况,Spark提供了两种共享变量的机制:广播变量(Broadcast Variables)累加器(Accumulators)

  1. 广播变量(Broadcast Variables)
    广播变量允许在集群中的每个节点上缓存一个只读变量的副本,而不是在每个任务中传递变量的完整拷贝。这样,能够有效地减少数据传输的开销。

使用场景:当你有一个大数据集,并且希望在多个节点中共享该数据集时,可以使用广播变量。比如,在分布式计算中,每个节点需要访问同一份数据,但不需要重复传输这份数据。

创建广播变量:可以通过SparkContext.broadcast(v)方法创建广播变量。广播变量是原始变量v的包装,创建之后,可以通过.value方法访问广播的内容。

示例代码

val broadcastVar = sc.broadcast(Array(1, 2, 3))  // 创建广播变量
println(broadcastVar.value)  // 输出广播变量的值:Array(1, 2, 3)

注意:广播变量一旦创建后,其值是不可修改的,确保集群中所有节点看到相同的值。

  1. 累加器(Accumulators)
    累加器是专为并行计算设计的变量,它只能通过累加操作进行更新。累加器非常适用于执行计数或求和等任务。Spark原生支持数值类型的累加器,也支持开发者通过自定义类型来扩展累加器。

使用场景:累加器常用于计算任务中,需要对某些值进行统计或累加时。

创建累加器:可以通过SparkContext.accumulator(v)方法创建累加器,其中v是初始值。累加器的值只能通过add方法或+=操作符进行更新,且只能由驱动程序(driver)读取其值。

示例代码:

val accum = sc.accumulator(0)  // 创建一个初始值为0的累加器
sc.parallelize(Array(1, 2, 3, 4)).foreach(x => accum += x)  // 在每个任务中累加
println(accum.value)  // 输出累加器的总值:10

自定义累加器:可以通过实现AccumulatorParam接口来自定义累加器的行为。例如,定义一个向量累加器来处理向量类型的数据。

object VectorAccumulatorParam extends AccumulatorParam[Vector] {
  def zero(initialValue: Vector): Vector = {
    Vector.zeros(initialValue.size)  // 返回一个零向量
  }
  def addInPlace(v1: Vector, v2: Vector): Vector = {
    v1 += v2  // 累加操作
  }
}
val vecAccum = sc.accumulator(new Vector(...))(VectorAccumulatorParam)  // 创建向量累加器

注意:在Python中,累加器目前还不被完全支持。

总结

广播变量:适用于在多个节点之间共享大型只读数据,避免重复传输。
累加器:用于并行计算中的求和或计数,只能通过“加法”操作更新值,并且只能由驱动程序访问其最终值。

;