架构师训练营 1 期第 13 周:数据应用(二)- 总结
Spark 是大数据领域现阶段使用最广泛的大数据引擎,它被认为是 MapReduce 的替代者,下面会介绍 Spark 的特点,优点及其工作原理
Spark 的特点
Spark VS Hadoop

分别使用 Spark 和 MapReduce 完成逻辑回归计算(一个大数据机器学习的算法)的性能测试对比图,Spark 有明显优势
Spark 特点(Spark 为什么更快)
DAG 切分的多阶段计算过程更快速
使用内存存储中间计算结果更高效
RDD 的编程模型更简单
Spark 编程示例
Spark WordCount 编程示例


作为编程模型的 RDD
RDD 是 Spark 的核心概念,是弹性分布式数据集(Resilient Distributed Datasets)的 缩写。RDD 既是 Spark 面向开发者的编程模型,又是 Spark 自身架构的核心元素。
作为 Spark 编程模型的 RDD。我们知道,大数据计算就是在大规模的数据集上进行一系列的数据计算处理。MapReduce 针对输入数据,将计算过程分为两个阶段,一个 Map 阶段,一个 Reduce 阶段,可以理解成是面向过程的大数据计算。我们在用 MapReduce 编程的时候,思考的是,如何将计算逻辑用 Map 和 Reduce 两个阶段实现, map 和 reduce 函数的输入和输出是什么,MapReduce 是面向过程的。
而 Spark 则直接针对数据进行编程,将大规模数据集合抽象成一个 RDD 对象,然后在 这个 RDD 上进行各种计算处理,得到一个新的 RDD,继续计算处理,直到得到最后的结果数据。所以 Spark 可以理解成是面向对象的大数据计算。我们在进行 Spark 编程的时候,思考的是一个 RDD 对象需要经过什么样的操作,转换成另一个 RDD 对象,思考的重心和落脚点都在 RDD 上。
WordCount 的代码示例里,第 2 行代码实际上进行了 3 次 RDD 转换,每次转换都得到 一个新的 RDD,因为新的 RDD 可以继续调用 RDD 的转换函数,所以连续写成一行代码。 事实上,可以分成 3 行。

RDD 上定义的函数分两种,一种是转换(transformation)函数,这种函数的返回值还 是 RDD;另一种是执行(action)函数,这种函数不再返回 RDD。
RDD 定义了很多转换操作函数,比如有计算 map(func)、过滤 filter(func)、合并数据 集 union(otherDataset)、根据 Key 聚合 reduceByKey(func, [numPartitions])、连接 数据集 join(otherDataset, [numPartitions])、分组 groupByKey([numPartitions]) 等 十几个函数。
作为数据分片的 RDD
跟 MapReduce 一样,Spark 也是对大数据进行分片计算,Spark 分布式计算的数据分 片、任务调度都是以 RDD 为单位展开的,每个 RDD 分片都会分配到一个执行进程去处 理。
RDD 上的转换操作又分成两种,一种转换操作产生的 RDD 不会出现新的分片,比如 map、filter 等,也就是说一个 RDD 数据分片,经过 map 或者 filter 转换操作后,结 果还在当前分片。就像你用 map 函数对每个数据加 1,得到的还是这样一组数据,只是 值不同。实际上,Spark 并不是按照代码写的操作顺序去生成 RDD,比如 rdd2 = rdd1.map(func) 这样的代码并不会在物理上生成一个新的 RDD。物理上,Spark 只有在产生新的 RDD 分片时候,才会在物理上真的生成一个 RDD,Spark 的这种特性也被称作惰性计算。
另一种转换操作产生的 RDD 则会产生新的分片,比如 reduceByKey,来自不同分片的相同 Key 必须聚合在一起进行操作,这样就会产生新的 RDD 分片。然而,实际执行过程中,是否会产生新的 RDD 分片,并不是根据转换函数名就能判断出来的。

Spark 的计算阶段
和 MapReduce 一个应用一次只运行一个 map 和一个 reduce 不同,Spark 可以根据 应用的复杂程度,分割成更多的计算阶段(stage),这些计算阶段组成一个有向无环图 DAG,Spark 任务调度器可以根据 DAG 的依赖关系执行计算阶段。

这个 DAG 对应的 Spark 程序伪代码如下

整个应用被切分成 3 个阶段,阶段 3 需要依赖阶段 1 和阶段 2,阶段 1 和阶段 2 互不依 赖。Spark 在执行调度的时候,先执行阶段 1 和阶段 2,完成以后,再执行阶段 3。如 果有更多的阶段,Spark 的策略也是一样的。只要根据程序初始化好 DAG,就建立了依 赖关系,然后根据依赖关系顺序执行各个计算阶段,Spark 大数据应用的计算就完成了。
Spark 作业调度执行的核心是 DAG,有了 DAG,整个应用就被切分成哪些阶段,每个阶段的依赖关系也就清楚了。之后再根据每个阶段要处理的数据量生成相应的任务集合(TaskSet),每 个任务都分配一个任务进程去处理,Spark 就实现了大数据的分布式计算。
负责 Spark 应用 DAG 生成和管理的组件是 DAGScheduler,DAGScheduler 根据程序代码生成 DAG,然后将程序分发到分布式计算集群,按计算阶段的先后关系调度执行。
那么 Spark 划分计算阶段的依据是什么呢?显然并不是 RDD 上的每个转换函数都会生成一个计算阶段,比如上面的例子有 4 个转换函数,但是只有 3 个阶段。
当 RDD 之间的转换连接线呈现多对多交叉连接的时候,就会产生新的阶段。一个 RDD 代表一 个数据集,图中每个 RDD 里面都包含多个小块,每个小块代表 RDD 的一个分片。
Spark 也需要通过 shuffle 将数据进行重新组合,相同 Key 的数据放在一起,进行聚合、 关联等操作,因而每次 shuffle 都产生新的计算阶段。这也是为什么计算阶段会有依赖关系,它需要的数据来源于前面一个或多个计算阶段产生的数据,必须等待前面的阶段执 行完毕才能进行 shuffle,并得到数据。
计算阶段划分的依据是 shuffle,不是转换函数的类型,有的函数有时候有 shuffle,有时候没有。例子中 RDD B 和 RDD F 进行 join,得到 RDD G,这里的 RDD F 需要进行 shuffle,RDD B 就不需要。

Spark 的作业管理
Spark 里面的 RDD 函数有两种,一种是转换函数,调用以后得到的还是一个 RDD,RDD 的计算逻辑主要通过转换函数完成。
另一种是 action 函数,调用以后不再返回 RDD。比如 count() 函数,返回 RDD 中数据的元素个数;saveAsTextFile(path),将 RDD 数据存储到 path 路径下。Spark 的 DAGScheduler 在遇到 shuffle 的时候,会生成一个计算阶段,在遇到 action 函数的时候,会生成一个作业(job)。
RDD 里面的每个数据分片,Spark 都会创建一个计算任务去处理,所以一个计算阶段会包含很多个计算任务(task)。

Spark 的执行过程
Spark 支持 Standalone、Yarn、Mesos、Kubernetes 等多种部署方案,几种部署方案原理也都一样,只是不同组件角色命名不同,但是核心功能和运行流程都差不多。

首先,Spark 应用程序启动在自己的 JVM 进程里,即 Driver 进程,启动后调用 SparkContext 初始化执行配置和输入数据。SparkContext 启动 DAGScheduler 构造执行的 DAG 图,切分成最小的执行单位也就是计算任务。
然后 Driver 向 Cluster Manager 请求计算资源,用于 DAG 的分布式计算。Cluster Manager 收到请求以后,将 Driver 的主机地址等信息通知给集群的所有计算节点 Worker。
Worker 收到信息以后,根据 Driver 的主机地址,跟 Driver 通信并注册,然后根据自己的空闲资源向 Driver 通报自己可以领用的任务数。Driver 根据 DAG 图开始向注册的 Worker 分配任务。
Worker 收到任务后,启动 Executor 进程开始执行任务。Executor 先检查自己是否有 Driver 的执行代码,如果没有,从 Driver 下载执行代码,通过 Java 反射加载后开始执行。
Spark 生态体系

版权声明: 本文为 InfoQ 作者【piercebn】的原创文章。
原文链接:【http://xie.infoq.cn/article/358a79bafd12a7bfd3e5c3734】。文章转载请联系作者。
评论