初识 Spark - 7000字+15张图解,学习 Spark 入门基础知识

2022-02-14 12:35:57 浏览数 (1)

文章大纲

Spark 是 UC Berkeley AMP Lab 开源的通用分布式并行计算框架,目前已成为 Apache 软件基金会的顶级开源项目。

基于以下原因,Spark 已经成为数据行业从业者(尤其是大数据领域)绕不开的必学技术栈中的一员:

  • Spark 已经成为大数据领域中必备的计算引擎框架
  • Spark 已经基本替代了传统的 MapReduce 离线计算框架和 Storm 流式实时计算框架
  • Spark 正在数据科学、机器学习及 AI 等热门技术方向持续发力

下面对 Spark 的特性、对比 Hadoop 的优势、组成模块及运行原理等基础知识进行学习。

1

Spark 的特性

图1:Apache 官网描述的 Spark 特性

Apache 在改版后的 Spark 官网中用了四个单词描述 Spark 的特性:Simple. Fast. Scalable. Unified.

1.1

Simple(简单易用)

Spark 提供了丰富的高级运算操作,支持丰富的算子,并支持 Java、Python、Scala、R、SQL 等语言的 API,使用户可以快速构建不同的应用。

开发人员只需调用 Spark 封装好的 API 来实现即可,无需关注 Spark 的底层架构。

1.2

Fast(高效快速)

Spark 将处理的每个任务都构造成一个DAG(Directed Acyclic Graph, 有向无环图)来执行,实现原理是基于RDD(Resilient Distributed Dataset, 弹性分布式数据集)在内存中对数据进行迭代计算,以实现批量和流式数据的高性能快速计算处理。

之前的官方数据表明:如果计算数据是从磁盘中读取,Spark 计算速度是 MapReduce 的 10 倍以上;如果计算数据是从内存中读取,Spark 计算速度则是 MapReduce 的 100 倍以上。

目前的官网已经撤下这一数据,估计是统计的场景和数据存在偏颇,不够全面。但这也从侧面说明,Spark 拥有出色的计算性能已经是深入人心的不争事实,无需再用数据来佐证。

1.3

Scalable(可融合性)

Spark 可以非常方便地与其他的开源产品进行融合。比如:Spark 可以使用 Hadoop 的 YARN 和 Apache Mesos 作为它的资源管理和调度器;可以处理所有 Hadoop 支持的数据,包括 HDFS、HBase 和 Cassandra 等

这对于已经部署 Hadoop 集群的用户特别重要,因为不需要做任何数据迁移就可以使用 Spark 强大的计算处理能力。

Spark 也可以不依赖于第三方的资源管理和调度器,它实现了 Standalone 作为其内置的资源管理和调度框架,这样进一步降低了 Spark 的使用门槛。

用户可以根据现有的大数据平台灵活地选择运行模式,使得所有人都可以非常容易地部署和使用 Spark。

Spark 还提供了在 EC2 上部署 Standalone 的 Spark 集群的工具。

此外,由于 Spark 是使用 Scala 这种函数式编程语言开发的,因此 Spark 也继承了 Scala 的可扩展性,可对类型数据结构、控制体结构等进行自定义的扩展。

1.4

Unified(统一通用)

大数据处理的传统方案需要维护多个平台,比如,离线任务是放在 Hadoop MapRedue 上运行,实时流计算任务是放在 Storm 上运行。

而Spark 提供了一站式的统一解决方案,可用于批处理、交互式查询(Spark SQL)、实时流处理(Spark Streaming)、机器学习(Spark MLlib)和图计算(GraphX)等。这些不同类型的处理都可以在同一个应用中无缝组合使用。

Spark 统一的解决方案非常具有吸引力,毕竟任何公司都想用统一的平台去处理遇到的问题,减少开发和维护的人力成本和部署平台的物理成本。

2

Spark 的优势

这里说的 Spark 的优势,是对比 Hadoop 的 MapReduce 而言,因此,我们需要先看看 MapReduce 的局限与不足:

  1. 计算的中间结果放在 HDFS 文件系统中,效率低下
  2. 时延高,只适用 Batch 数据处理,对于交互式数据处理、实时数据处理的支持不够
  3. 抽象层次低,需要手工编写代码来完成,使用上难以上手
  4. 只提供两个操作,Map 和 Reduce,表达力欠缺,且 ReduceTask 需要等待所有 MapTask 都完成后才可以开始
  5. 一个 Job 只有 Map 和 Reduce 两个阶段,复杂的计算需要大量的 Job 完成,Job 之间的依赖关系是由开发者自己管理的
  6. 处理逻辑隐藏在代码细节中,没有整体逻辑
  7. 对于迭代式数据处理性能比较差,容错率低

比如说,用 MapReduce 实现两个表的 Join 都是一个很有技巧性的过程,如下图所示:

图2-1:使用 MapReduce 实现 Join 的过程

在 Spark 中,MapReduce 的这些局限与不足都能找到很好的解决方案,而且 Spark 具备 MapReduce 的所有优点,这就是 Spark 的优势所在。

2.1

高性能(★)

Hadoop MapReduce 每次计算的中间结果都会存储到 HDFS 的磁盘上;而 Spark 的中间结果可以保存在内存,在内存中进行数据处理,内存放不下了会写入本地磁盘,而不是 HDFS。

Spark 可以通过将流拆成小的 batch,来提供 Discretized Stream 处理交互式实时数据。

因此,Spark 可以解决上面列出的 MapReduce 的第 1、2 个问题。

2.2

易使用(★☆)

Spark 引入了基于 RDD 的抽象,数据处理逻辑的代码非常简短,且提供了丰富的Transformation(转换,用于创建新的 RDD)和Action(执行,用于对 RDD 进行实际的计算)操作及对应的算子,很多基本的操作(如 filter, union, join, groupby, reduce)都已经在 RDD 的 Transformation 和 Action 中实现。

Spark 中的一个 Job 可以包含 RDD 的多个 Transformation 操作,在调度时可以根据依赖生成多个Stage(阶段)。

RDD 内部的数据集在逻辑上和物理上都被划分为了多个Partitions(分区),每一个 Partition 中的数据都可以在单独的任务中被执行,而 Partition 不同的 Transformation 操作需要 Shuffle,被划分到不同的 Stage 中,要等待前面的 Stage 完成后才可以开始。

在 Spark 使用的 Scala 语言中,通过匿名函数和高阶函数 ,RDD 的转换支持流式 API,可以提供处理逻辑的整体视图。代码不包含具体操作的实现细节,逻辑更加清晰。

因此,Spark 可以解决上面列出的 MapReduce 的第 3-6 个问题。

2.3

高容错(★☆)

Spark 引入的 RDD,是分布在一组节点中的只读的弹性分布式数据集合,想更新 RDD 分区中的数据,那么只能对原有 RDD 进行 Transformation 操作,在原来 RDD 的基础上创建一个新的 RDD。

这样,在任务运算过程中,各个 RDD 之间就会产生前后依赖的关系。

当运算中出现异常情况导致分区数据丢失时,可以根据“血统”(Lineage)关系对数据进行重建,而不是对最开始的 RDD 分区数据重新进行计算。

Spark 中还存在CheckPoint机制,这是一种基于快照的缓存机制,如果在任务运算中,多次使用同一个 RDD,可以将这个 RDD 进行缓存处理,在后续使用到该 RDD 时,就不需要重新进行计算。

图2-2:Spark CheckPoint 机制

如图2-2所示,对 RDD-b 做快照缓存处理,那么当 RDD-n 在用到 RDD-b 的数据时,就无需再重新计算 RDD-b,而是直接从 Cache(缓存)处取 RDD-b 的数据进行计算。

CheckPoint 通过冗余数据和日志记录更新操作两种方式,对“血统”检测进行容错辅助,避免“血统”过长造成容错成本过高。

通过 Spark 的以上机制,就能提高迭代计算的性能和容错率,解决上面列出的 MapReduce 的第 7 个问题。

3

Spark 的生态圈(组成模块)

Spark 生态中包含多个紧密集成的模块组件,这些模块结合密切并且可以相互调用。目前,Spark 的生态圈已经从大数据计算和数据挖掘,扩展到机器学习、NLP、语音识别等领域。

图3-1:Apache Spark 生态圈

Spark 有多组件的支持应用场景,其在 Spark Core 的基础上提供了 Spark SQL、Spark Streaming、MLlib、GraphX、SparkR 等核心组件。

Spark SQL 旨在将熟悉的 SQL 数据库查询语言与更复杂的基于算法的分析相结合,Spark Streaming 用于实时流计算,MLlib 应用于机器学习领域,GraphX 应用于图计算,SparkR 用于对 R 语言的数据计算。

Spark 支持多种编程语言,包括 Java、Python(PySpark)、R(SparkR) 和 Scala。

Spark 在计算资源调度层支持 Local 模式、Standalone 模式、YARN 模式、Mesos 模式等。

Spark 支持多种的存储介质,在存储层 Spark 支持从 HDFS、HBase、Hive、ES、MongoDB、MySQL、PostgreSQL、AWS、Ali Cloud 等不同的存储系统、大数据库、关系型数据库中读入和写出数据,在实时流计算中可以从 Flume、Kafka 等多种数据源获取数据并执行流式计算。

Spark 也支持非常丰富的数据文件格式,比如 txt、json、csv 等。同时也支持 parquet、orc、avro 等格式,这几种格式在数据压缩和海量数据查询上优势较为明显。

3.1

Spark Core

Spark Core 实现了 Spark 基本的核心功能,其包含以下几个部分:

图3-2:Spark Core 结构

3.1.1. Spark 基础配置

  • SparkConf :用于定义 Spark Application 的配置信息。
  • SparkContext :Spark Application 所有功能的主要入口点, 其隐藏了网络通信、消息通信,分布式部署、存储体系、计算存储等底层逻辑,开发人员只需使用其提供的 API 即可完成 Application 的提交与执行。核心作用是初始化 Spark Application 所需要的组件,同时还负责向 Master 进程进行注册等。
  • SparkRPC :基于 Netty 实现的 Spark RPC 框架用于 Spark 组件之间的网络通信,分为异步和同步两种方式。
  • SparkEnv :Spark 的执行环境,其内部封装了很多 Spark 运行所需要的基础环境组件。
  • ListenerBus :事件总线,主要用于 SparkContext 内部各组件之间的事件交互,属于监听者模式,采用异步调用。
  • MetricsSystem :度量系统,用于整个 Spark 集群中各个组件状态的运行监控。

3.1.2. Spark 存储系统

Spark 存储系统用于管理 Spark 运行中依赖的数据的存储方式和存储位置。存储系统会优先考虑在各节点的内存中存储数据,内存不足时将数据写入磁盘中,这也是 Spark 计算性能高的重要原因。

数据存储在内存和磁盘之间的边界可以灵活控制,同时可以通过远程网络调用将结果输出到远程存储中,比如 HDFS、HBase 等。

3.1.3. Spark 调度系统

Spark 调度系统主要由 DAGScheduler 和 TaskScheduler 组成。

  • DAGScheduler:负责创建 Job,把一个 Job 根据 RDD 间的依赖关系,划分到不同 Stage 中,并将划分后的每个 Stage 都抽象为一个或多个 Task 组成的 TaskSet,批量提交给 TaskScheduler 来进行进一步的任务调度。
  • TaskScheduler:负责按照调度算法对每个具体的 Task 进行批量调度执行,协调物理资源,跟踪并获取状态结果。

主要的调度算法有 FIFO、FAIR。

  • FIFO 调度:先进先出,这是 Spark 默认的调度模式。
  • FAIR 调度:支持将作业分组到池中,并为每个池设置不同的调度权重,任务可以按照权重来决定执行顺序。

3.1.4. Spark 计算引擎

Spark 计算引擎主要由内存管理器、TaskSet 管理器、Task 管理器、Shuffle 管理器等组成。

3.2

Spark SQL

Spark SQL 是 Spark 用来操作结构化数据的程序包,其提供了基于 SQL、Hive SQL、与传统的 RDD 编程的数据操作结合的数据处理方法,使得分布式的数据集处理变得更加简单,这也是 Spark 被广泛使用的重要原因。

目前大数据相关计算引擎一个重要的评价指标就是:是否支持 SQL,这样才会降低使用者的门槛。Spark SQL 提供了两种抽象的数据集合:DataFrame 和 DataSet。

  • DataFrame:Spark SQL 对结构化数据的抽象,可以简单的理解为 Spark 中的表,相对于 RDD 多了数据的表结构信息,是分布式 Row 的集合,提供了比 RDD 更丰富的算子,同时提升了数据的执行效率。
  • DataSet:数据的分布式集合 ,具有 RDD 强类型的优点 和 Spark SQL 优化后执行的优点。其可以由 jvm 对象构建,然后使用 map、filter、flatmap 等 Transformation 操作。

3.3

Spark Streaming

Spark Streaming 提供了对实时数据进行流式计算的 API,支持流数据的可伸缩和容错处理,可以与 Kafka、Flume、TCP 等多种流式数据源集成。

Spark Streaming 的实现,也使用 RDD 抽象的概念,使得在为流数据编写 Application 时更为方便。

此外,Spark Streaming 还提供了基于时间窗口的批量流操作,用于对一定时间周期内的流数据执行批量处理。

3.4

MLlib

Spark MLlib 作为一个提供常见机器学习(ML)功能的程序库,包括分类、回归、聚类等多种机器学习算法的实现,其简单易用的 API 接口降低了机器学习的门槛。

3.5

GraphX

GraphX 用于分布式图计算,比如可以用来操作社交网络的朋友关系图,能够通过其提供的 API 快速解决图计算中的常见问题。

3.6

PySpark

为了用 Spark 支持 Python,Apache Spark 社区发布了一个工具 PySpark。使用 PySpark,就可以使用 Python 编程语言中的 RDD 。

PySpark 提供了 PySpark Shell ,它将 Python API 链接到 Spark 核心并初始化 SparkContext。

3.7

SparkR

SparkR 是一个 R 语言包,提供了轻量级的基于 R 语言使用 Spark 的方式,使得基于 R 语言能够更方便地处理大规模的数据集。

4

Spark 的运行原理

下面介绍 Spark 的运行模式及架构。

4.1

Spark 的运行模式(★☆)

Spark 的底层被设计为可以高效地在一个到数千个节点之间进行可伸缩的计算。为了实现这样的需求,同时获得最大的灵活性,Spark 支持在各种集群管理器上运行。

Spark 的运行模式主要有以下几种:

图4-1-1:Spark 运行模式

除了 Local 是本地模式外,Standalone、YARN、Mesos、Cloud 都是集群模式,需要搭建集群环境才能运行。

4.2

Spark 的集群架构及角色(★★)

Spark 的集群架构主要由 Cluster Manager(集群资源管理器)、Worker (工作节点)、Executor(执行器)、Driver(驱动器)、Application(应用程序)共五部分角色组成,如下图所示:

图4-2-1:Spark 集群架构

下面简要介绍每部分角色。

4.2.1. Cluster Manager

Cluster Manager 是 Spark 的集群资源管理器,存在于 Master 进程中,主要用于对整个集群资源进行管理和分配,根据其部署模式的不同,可以分为 Local、Standalone、YARN、Mesos、Cloud 等模式。

4.2.2. Worker

Worker 是 Spark 的工作节点,用于执行提交的任务,其主要的工作职责有以下几点:

  1. Worker 节点通过注册机向 Cluster Manager 汇报自身的 CPU、内存等资源使用信息。
  2. Worker 节点在 Spark Master 的指示下,创建并启用 Executor(真正的计算单元)。
  3. Spark Master 将资源和 Task 分配给 Worker 节点上的 Executor 并执行运用。
  4. Worker 节点同步 Executor 状态和资源信息给 Cluster Manager。

图4-2-2:Spark Worker 节点工作机制

在 YARN 集群模式下运行 Worker 节点一般指的是 NodeManager 节点,Standalone 模式下运行一般指的是 slave 节点。

4.2.3. Executor

Executor 是真正执行计算任务的组件,是 Application 运行在 Worker 上的一个进程。这个进程负责 Task 的运行,并将数据保存在内存或磁盘存储中,也能够将结果数据返回给 Driver。

4.2.4. Application

Application 是基于 Spark API 编写的应用程序,包括实现 Driver 功能的代码和在集群中各个 Executor 上要执行的代码。

一个 Application 由多个 Jobs 组成。

其中 Application 的入口为用户所定义的 main() 方法。

4.2.5. Driver

Driver 是 Spark 的驱动器节点,可以运行在 Application 节点上,也可以由 Application 提交给 Cluster Manager,再由 Cluster Manager 安排 Worker 进行运行,其主要的工作职责有:

  • Application 通过 Driver 跟 Cluster Manager 及 Executor 进行通信;
  • 运行 Application 中的 main() 函数;
  • 创建 SparkContext;
  • 划分 RDD 并生成 DAG;
  • 创建 Job 并将每个 Job 拆分为多个 Stage,每个 Stage 由多个 Task 构成,也被称为 Task Set;
  • 生成并发送 Task 到 Executor;
  • 在各个 Executor 进程间协调 Task 的调度;
  • 与 Spark 中的其他组件进行资源协调。

4.3

Worker 作业运行拆解(★★★)

图4-3-1:Worker 内部作业运行过程拆解

Spark 中的一个 Worker 可以运行一个或多个 Executor。

一个 Executor 可以运行的 Task 个数取决于 Executor 的 Core 数量,默认一个 Task 占用一个 Core。

4.3.1. Job

RDD 的 Transformation 操作过程采用惰性计算机制,不会立即计算出结果。当真正触发 Action 操作时,才会执行计算,产生一个 Job。

Job 是由多个 Stage 构建的并行计算任务。

一个 Job 包含多个 RDD 以及作用在 RDD 的各种操作算子。

4.3.2. RDD 依赖

RDD(Resilient Distributed Dataset, 弹性分布式数据集)是 Spark 中最重要的一个概念,是 Spark 对所有数据处理的一种基本抽象,它代表一个不可变、可分区、元素可并行计算的数据集合。

RDD 具有数据流模型的特点:自动容错、位置感知性调度和可伸缩性。

在 Spark 中可以通过一系列的算子对 RDD 进行操作,主要分为 Transformation(转换) 和 Action(执行) 两种操作:

  • Transformation:对已有的 RDD 进行转换生成新的 RDD,转换的过程采用惰性求值计算机制,不会立即触发执行实际的转换,而是先记录 RDD 之间的转换关系,只有当触发 Action 操作时才会真正地执行转换,并返回计算结果,以避免所有操作都执行一遍运算,减少数据计算步骤,提高 Spark 运算效率。常用的方法有 map、filter、flatmap、union、groupByKey 等。
  • Action:强制执行求值必须用到的 RDD 的转换操作,对数据集执行实际的计算,并将最终的计算结果返回给 Driver 程序,或者写入到外部存储中。常用到方法有 reduce、collect、count、countByKey、 saveAsTextFile 等。

图4-3-2:RDD 操作处理过程

由于 RDD 是只读的弹性分区数据集,如果对 RDD 中的数据进行改动,就只能通过 Transformation 操作,由一个或多个 RDD 计算生成一个新的 RDD,所以 RDD 之间就会形成类似流水线(Pipeline)的前后依赖关系,前面的称为父 RDD,后面的称为子 RDD

当计算过程中出现异常情况导致部分 Partition 数据丢失时,Spark 可以通过这种依赖关系从父 RDD 中重新计算丢失的分区数据,而不需要对 RDD 中的所有分区全部重新计算,以提高迭代计算性能。

RDD 之间的依赖关系又分为Narrow Dependency(窄依赖)和Wide Dependency(宽依赖)。

  • Narrow Dependency:父 RDD 中的每个 Partition 数据最多只能被子 RDD 的一个 Partition 所使用,例如 map、filter、union 等操作都会产生窄依赖(类似独生子女)。
  • Wide Dependency:父 RDD 中的每个 Partition 数据可以被多个子 RDD 的多个 Partition 所使用,例如 groupByKey、reduceByKey、sortByKey 等操作都会产生宽依赖(类似多个子女)。

图4-3-3:RDD 的窄依赖与宽依赖

简单来说,两个 RDD 的 Partition 之间,如果是一对一的关系,则为窄依赖,否则为宽依赖。

RDD 是 Spark 中一个十分重要的知识点,后面会另起章节详细介绍。

4.3.3. Stage

当 Spark 执行作业时,会根据 RDD 之间的宽窄依赖关系,将 DAG 划分成多个相互依赖的 Stage。

Spark 划分 Stage 的整体思路是,按照倒序从后往前推:

如果遇到 RDD 之间为窄依赖,由于 Partition 依赖关系的确定性,Transformation 操作可以在同一个线程里完成,窄依赖就被划分到同一个 Stage 中;

如果遇到 RDD 之间为宽依赖,则划分到一个新的 Stage 中,且新的 Stage 为之前 Stage 的 Parent,然后依次类推递归执行,Child Stage 需要等待所有的 Parent Stages 执行完成后才可以执行。

这样每个 Stage 内的 RDD 都尽可能在各个节点上并行地被执行,以提高运行效率。

图4-3-4:Stage 的划分过程(黑色表示前置计算分区)

以图4-3-4作为例子,使用这个思路进行 Stage 划分,从后往前倒推:

RDD C, RDD D, RDD E, RDD F 之间均为窄依赖,因此被构建在同一 Stage2 中;

RDD A 与 RDD B 之间是宽依赖关系,因此被构建在不同的 Stage 中,RDD A 在 Stage1 中,RDD B 在 Stage3 中;

RDD B 与 RDD G 之间为窄依赖,因此被构建在同一 Stage3 中。

在一个 Stage 内,所有的操作以串行的 Pipeline 方式,由一个或多个 Task 组成的 TaskSet 完成计算。

4.3.4. Partition

图4-3-5:RDD 中的 Partitions

RDD 内部的数据集在逻辑上和物理上都被划分为了多个Partitions(分区),每一个 Partition 中的数据都可以在单独的任务中被执行,这样 Partition 数量就决定了计算的并行度。

如果在计算中没有指定 RDD 中的 Partition 数量,那么 Spark 默认的 Partition 数就是 Applicaton 运行分配到的 CPU 核数。

官网推荐 Partition 数量设置为 Task 个数的 2-3 倍,以充分利用资源。

4.3.5. TaskSet

图4-3-6:Tasks 组成的 TaskSet

TaskSet 可以理解为一种任务,对应一个 Stage,是 Task 组成的任务集。

一个 TaskSet 中的所有 Task 没有 Shuffle 依赖可以并行计算。

4.3.6. Task

Task 是 Spark 中最独立的计算单元,每个 Task 中执行的数据通常只对应一个 Partition。

Task 分为 ShuffleMapTask 和 ResultTask 两种,位于最后一个 Stage 的 Task 为 ResultTask,其他阶段的属于 ShuffleMapTask。

ShuffleMapTask 相当于 MapReduce 中的 Mapper(如图4-3-4中的 Stage1 和 Stage2);ResultTask 相当于 MapReduce 中的 Reducer(如图4-3-4中的 Stage3)。

封面图片:加拿大魁北克圣劳伦斯湾的皮尔斯巨石

版权信息:© Pietro Canali / SOPA / eStock Photo

0 人点赞