Spark 架构概述

1,215 阅读8分钟

本文目录

  • 介绍 Spark 生态。
  • 介绍 Spark 基本概念和常用术语。
  • 介绍 Spark 的执行原理和架构设计。
  • 介绍 Spark-Yarn 部署模式。
  • 介绍 Saprk RDD 运行原理。

Spark 生态

  • Spark Core:包含了 Spark 的基础 API,比如对于 RDD 的操作 API,其他的 Spark 库也都是构建在 Spark Core 的基础上。
  • Spark Sql:包含了对于 Hive Sql 的操作,可以将 Hive Sql 转换成 Spark Rdd 操作。
  • Spark Streaming:提供了对于实时数据进行处理的方式。
  • MLib:包含了常用的机器学习算法实现,对于常见的分类和回归操作,可以对大量数据进分布式行迭代的操作。
  • GraphX:对图操作的工具集合。

p.s: 关于 Spark Sql/Spark Streaming/MLib 后续几篇文章在单独介绍,本文只先简单概述一下 Spark 的架构设计。

基础术语

在 Spark 中,被提交的程序叫做 Application,一个 Application 由多个 Job 组成,一个 Job 可以分解成多个 Stage,一个 Stage 又可以分解成多个 Task,然后将 Task 放在 Executor 进程上面运行。

  • Application:用户编写的 Spark 程序,可以理解为两部分,一部分是 Driver 代码,另外一部分是 Executor 代码。
  • Driver:Driver 可以理解为 Application 的 main 函数,会创建一个 SparkContext,SparkContext 负责与 Cluster Manager 通信,进行资源申请、任务的分配和监控等。
  • Cluster Manager:负责集群中资源的分配,目前主要有四种模式:
    • local:运行在本地非分布式部署。
    • Standalon:Spark 原生的资源管理。
    • Apache Mesos。
    • Yarn:Yarn 中的 ResourceManager,下文将对 Spark-On-Yarn 做详细介绍。
  • Worker:集群中可以运行 Application 代码的节点,在 Spark On Yarn 模式下可以理解为 NodeManager 节点。
  • Executor:运行在 Worker 上的一个进程,该进程可以负责多线程运行 task。
  • Job:一个 Application 由多个 Job 组成。
  • Stage:一个 Job 经过 DAG(DAGScheduler) 分解,可以拆分成多个 Stage。
  • Task:一个 Stage 可以拆解成多个 Task,然后放到 Executor 上面运行。

执行过程

  • 当一个 Spark 应用被提交时,首先需要为这个应用构建起基本的运行环境,即由任务控制节点(Driver)创建一个 SparkContext,由 SparkContext 负责和资源管理器(Cluster Manager)的通信以及进行资源的申请、任务的分配和监控等。SparkContext 会向资源管理器注册并申请运行 Executor 的资源。
  • 资源管理器为 Executor 分配资源,并启动 Executor 进程,Executor 运行情况将随着“心跳”发送到资源管理器上。
  • SparkContext 根据 RDD 的依赖关系构建 DAG 图,DAG 图提交给 DAG 调度器(DAGScheduler)进行解析,将 DAG 图分解成多个“阶段”(每个阶段都是一个任务集),并且计算出各个阶段之间的依赖关系,然后把一个个“任务集”提交给底层的任务调度器(TaskScheduler)进行处理;Executor 向 SparkContext 申请任务,任务调度器将任务分发给 Executor 运行,同时,SparkContext 将应用程序代码发放给 Executor。
  • 任务在 Executor 上运行,把执行结果反馈给任务调度器,然后反馈给 DAG 调度器,运行完毕后写入数据并释放所有资源。

总结

总结而言,Spark 运行架构具有以下特点:

  • 每个应用都有自己专属的 Executor 进程,Executor 进程以多线程的方式运行任务,减少了多进程任务频繁的启动开销,使得任务执行变得非常高效和可靠;
  • Spark 运行过程与资源管理器无关,只要能够获取 Executor 进程并保持通信即可;
  • Executor 上有一个 BlockManager 存储模块,类似于键值存储系统(把内存和磁盘共同作为存储设备),在处理迭代计算任务时,不需要把中间结果写入到HDFS等文件系统,而是直接放在这个存储系统上,后续有需要时就可以直接读取;在交互式查询场景下,也可以把表提前缓存到这个存储系统上,提高读写 IO 性能;
  • 任务采用了数据本地性和推测执行等优化机制。数据本地性是尽量将计算移到数据所在的节点上进行,即“计算向数据靠拢”,因为移动计算比移动数据所占的网络资源要少得多。而且,Spark 采用了延时调度机制,可以在更大的程度上实现执行过程优化。比如,拥有数据的节点当前正被其他的任务占用,那么,在这种情况下是否需要将数据移动到其他的空闲节点呢?答案是不一定。因为,如果经过预测发现当前节点结束当前任务的时间要比移动数据的时间还要少,那么,调度就会等待,直到当前节点可用。

Spark On Yarn

Spark On Yarn 模式根据 Driver 在集群中的位置分为两种模式:一种是 Yarn-Client 模式,另外一种是 Yarn-Cluster 模式。

Yarn-Client 模式

  • Spark Yarn Client 向 Yarn 的 ResourceManager 申请启动 Application Master。同时在 SparkContext 中创建 DAGScheduler 和 TASKScheduler 等。
  • ResourceManager 收到请求后,在集群中选择一个 NodeManager,为该应用程序第一个 Container,要求它在这个 Container 中启动应用程序的 ApplicationMaster。
  • Client 中的 SparkContext 初始完毕后,与 ApplicationMaster 建立通讯,向 ResourceManager 注册,根据任务信息向 ResourceManager 申请 Container。
  • 一旦 ApplicationMaster 申请到资源,也就是 Container 以后,便与对应的 NodeManger 通信,要求它在获得的 Container 中启动 CoarseGrainedExecutorBackend,CoarseGrainedExecutorBackend 启动后会向 Client 中的 SparkContext 注册并申请 Task。
  • Client 中的 SparkContext 分配 Task 给 CoarseGrainedExecutorBackend 执行,CoarseGrainedExecutorBackend 运行 Task 并向 Driver 汇报运行状态和监控。
  • 应用程序完成后,Client 的 SparkContext 向 ResourceManager 申请注销并关闭自己。

Yarn-Cluster 模式

在 Yarn-Cluster 模式中,当用户向 Yarn 提交一个应用程序后,Yarn 将分两个阶段运用该 Application:

  1. 把 Spark 的 Driver 作为一个 ApplicationMaster 在 Yarn 集群中先启动。
  2. 由 ApplicationMaster 创建应用程序,然后为它向 ResourceManager 申请资源,并启动 Executor 来运行 Task,同时监控它的整个运行过程,直到运行完成。

在 YARN 中,每个 Application 实例都有一个 ApplicationMaster 进程,它是 Application 启动的第一个容器。它负责和 ResourceManager 打交道并请求资源,获取资源之后告诉 NodeManager 为其启动 Container。从深层次的含义讲 YARN-Cluster 和 YARN-Client 模式的区别其实就是 ApplicationMaster 进程的区别。

YARN-Cluster 模式下,Driver 运行在 Application Master 中,它负责向 YARN 申请资源,并监督作业的运行状况。当用户提交了作业之后,就可以关掉 Client,作业会继续在 YARN 上运行,因而 YARN-Cluster 模式不适合运行交互类型的作业; YARN-Client 模式下,Application Master 仅仅向 YARN 请求 Executor,Client 会和请求的 Container 通信来调度他们工作,也就是说 Client 不能离开。

RDD

一个 RDD 就是一个只读分布式对象集合,一个 RDD 可以分成多个分区,每个分区就是一个数据集片段,不同的分区可以保存到不同的节点上。Spark RDD 可以分为两类:

  1. 转换操作(比如 map/filter/groupBy/join)接受 rdd 并返回 rdd。
  2. 行动操作(比如 count/collect)接受 rdd 但是不返回 rdd。Spark 中的 rdd 采用了惰性调用,真正的计算发生在行动操作,对于行动之前的转换操作,Spark 只是记录下转换操作的依赖关系。

宽依赖和窄依赖

  1. 窄依赖表示为,一个父 RDD 分区对应与一个子 RDD 分区,或者多个 RDD 分区对应一个子 RDD 分区。比如 map/filter/union。
  2. 宽依赖表示为,一个父 RDD 分区对应一个子 RDD 的多个分区。比如 groupByKey、sortByKey 等操作。

阶段划分(DAG)

Spark 通过分析各个 RDD 的依赖关系生成了 DAG,再通过分析各个 RDD 中的分区之间的依赖关系来决定如何划分阶段,具体划分方法是:在 DAG 中进行反向解析,遇到宽依赖就断开,遇到窄依赖就把当前的 RDD 加入到当前的阶段中;将窄依赖尽量划分在同一个阶段中,可以实现流水线计算。

如图所示,假设从 HDFS 中读入数据生成 3 个不同的 RDD(A、C和E),通过一系列转换操作后再将计算结果保存回 HDFS。对 DAG 进行解析时,在依赖图中进行反向解析,由于从 RDD A 到 RDD B 的转换以及从 RDD B 和F 到 RDD G的 转换,都属于宽依赖,因此,在宽依赖处断开后可以得到三个阶段,即阶段1、阶段2 和阶段3。可以看出,在阶段2 中,从 map 到 union 都是窄依赖,这两步操作可以形成一个流水线操作,比如,分区7 通过 map 操作生成的分区9,可以不用等待分区8 到分区10 这个转换操作的计算结束,而是继续进行 union 操作,转换得到分区13,这样流水线执行大大提高了计算的效率。