《深入理解Spark:核心思想与源码分析》一书前言的内容请看链接 《深入理解SPARK:核心思想与源码分析》一书正式出版上市
《深入理解Spark:核心思想与源码分析》一书第一章的内容请看链接《第1章 环境准备》
本文主要展示本书的第2章内容:
“若夫乘天地之正,而御六气之辩,以游无穷者,彼且恶乎待哉?”
—— 《庄子·逍遥游》
n 本章导读:
上一章,介绍了 Spark 环境的搭建,为方便读者学习 Spark 做好准备。本章首先从 Spark 产生的背景开始,介绍 Spark 的主要特点、基本概念、版本变迁。然后简要说明 Spark 的主要模块和编程模型。最后从 Spark 的设计理念和基本架构入手,使读者能够对 Spark 有宏观的认识,为之后的内容做一些准备工作。
Spark 是一个通用的并行计算框架,由加州伯克利大学( UCBerkeley )的 AMP 实验室开发于 2009 年,并于 2010 年开源。 2013 年成长为 Apache 旗下为大数据领域最活跃的开源项目之一。 Spark 也是基于 map reduce 算法模式实现的分布式计算框架,拥有 Hadoop MapReduce 所具有的优点,并且解决了 Hadoop MapReduce 中的诸多缺陷。
早在 Hadoop1.0 版本,当时采用的是 MRv1 版本的 MapReduce 编程模型。 MRv1 版本的实现都封装在 org.apache.hadoop.mapred 包中, MRv1 的 Map 和 Reduce 是通过接口实现的。 MRv1 包括三个部分:
q 运行时环境( JobTracker 和 TaskTracker );
q 编程模型( MapReduce );
q 数据处理引擎( Map 任务和 Reduce 任务)。
MRv1 存在以下不足:
q 可扩展性差:在运行时, JobTracker 既负责资源管理又负责任务调度,当集群繁忙时, JobTracker 很容易成为瓶颈,最终导致它的可扩展性问题。
q 可用性差:采用了单节点的 Master ,没有备用 Master 及选举操作,这导致一旦 Master 出现故障,整个集群将不可用。
q 资源利用率低: TaskTracker 使用“ slot ”等量划分本节点上的资源量。“ slot ”代表计算资源( CPU 、内存等)。一个 Task 获取到一个 slot 后才有机会运行, Hadoop 调度器负责将各个 TaskTracker 上的空闲 slot 分配给 Task 使用。一些 Task 并不能充分利用 slot ,而其他 Task 也无法使用这些空闲的资源。 slot 分为 Map slot 和 Reduce slot 两种,分别供 MapTask 和 Reduce Task 使用。有时会因为作业刚刚启动等原因导致 MapTask 很多,而 Reduce Task 任务还没有调度的情况,这时 Reduce slot 也会被闲置。
q 不能支持多种 MapReduce 框架:无法通过可插拔方式将自身的 MapReduce 框架替换为其他实现,如 Spark 、 Storm 等。
MRv1 的示意如图 2-1 。
图 2-1 MRv1 示意图 [1]
Apache 为了解决以上问题,对 Hadoop 升级改造, MRv2 最终诞生了。 MRv2 中,重用了 MRv1 中的编程模型和数据处理引擎。但是运行时环境被重构了。 JobTracker 被拆分成了通用的资源调度平台( ResourceManager ,简称 RM )和负责各个计算框架的任务调度模型( ApplicationMaste ,简称 AM )。 MRv2 中 MapReduce 的核心不再是 MapReduce 框架,而是 YARN 。在以 YARN 为核心的 MRv2 中, MapReduce 框架是可插拔的,完全可以替换为其他 MapReduce 实现,比如 Spark 、 Storm 等。 MRv2 的示意如图 2-2 所示。
图 2-2 MRv2 示意图
Hadoop MRv2 虽然解决了 MRv1 中的一些问题,但是由于对 HDFS 的频繁操作(包括计算结果持久化、数据备份及 shuffle 等)导致磁盘 I/O 成为系统性能的瓶颈,因此只适用于离线数据处理,而不能提供实时数据处理能力。
Hadoop 常用于解决高吞吐、批量处理的业务场景,例如离线计算结果用于浏览量统计。如果需要实时查看浏览量统计信息, Hadoop 显然不符合这样的要求。 Spark 通过内存计算能力极大地提高了大数据处理速度,满足了以上场景的需要。此外, Spark 还支持 SQL 查询,流式计算,图计算,机器学习等。通过对 Java 、 Python 、 Scala 、 R 等语言的支持,极大地方便了用户的使用。
Spark 看到 MRv1 的问题,对 MapReduce 做了大量优化,总结如下:
q 快速处理能力。随着实时大数据应用越来越多, Hadoop 作为离线的高吞吐、低响应框架已不能满足这类需求。 Hadoop MapReduce 的 Job 将中间输出和结果存储在 HDFS 中,读写 HDFS 造成磁盘 IO 成为瓶颈。 Spark 允许将 中间输出和结果存储在内存中,节省了大量的磁盘 IO 。同时 Spark 自身的 DAG 执行引擎也支持数据在内存中的计算。 Spark 官网声称性能比 Hadoop 快 100 倍,如图 2-3 所示。即便是内存不足需要磁盘 IO ,其速度也是 Hadoop 的 10 倍以上。
图 2-3 Hadoop 与 Spark 执行逻辑回归时间比较
q 易于使用。 Spark 现在支持 Java 、 Scala 、 Python 和 R 等语言编写应用程序,大大降低了使用者的门槛。自带了 80 多个高等级操作符,允许在 Scala , Python , R 的 shell 中进行交互式查询。
q 支持查询。 Spark 支持 SQL 及 Hive SQL 对数据查询。
q 支持流式计算。与 MapReduce 只能处理离线数据相比, Spark 还支持实时的流计算。 Spark 依赖 Spark Streaming 对数据进行实时的处理,其流式处理能力还要强于 Storm 。
q 可用性高。 Spark 自身实现了 Standalone 部署模式,此模式下的 Master 可以有多个,解决了单点故障问题。此模式完全可以使用其他集群管理器替换,比如 YARN 、 Mesos 、 EC2 等。
q 丰富的数据源支持。 Spark 除了可以访问操作系统自身的文件系统和 HDFS ,还可以访问 Cassandra, HBase, Hive, Tachyon 以及任何 Hadoop 的数据源。这极大地方便了已经使用 HDFS 、 Hbase 的用户顺利迁移到 Spark 。
经过 4 年多的发展, Spark 目前的版本是 1.4.1 。我们简单看看它的版本发展过程。
1) Spark 诞生于 UCBerkeley 的 AMP 实验室( 2009 )。
2) Spark 正式对外开源( 2010 )。
3) Spark 0.6.0 版本发布( 2012-10-15 ),大范围的性能改进,增加了一些新特性,并对 Standalone 部署模式进行了简化。
4) Spark 0.6.2 版本发布( 2013-02-07 ),解决了一些 bug ,并增强了系统的可用性。
5) Spark 0.7.0 版本发布( 2013-02-27 ),增加了更多关键特性,例如: Python API 、 Spark Streaming 的 alpha 版本等。
6) Spark 0.7.2 版本发布( 2013-06-02 ),性能改进并解决了一些 bug ,新的 API 使用的例子。
7) Spark 接受进入 Apache 孵化器( 2013-06-21 )。
8) Spark 0.7.3 版本发布( 2013-07-16 ),一些 bug 的解决,更新 Spark Streaming API 等。
9) Spark 0.8.0 版本发布( 2013-09-25 ),一些新功能及可用性改进。
10) Spark 0.8.1 版本发布( 2013-12-19 ),支持 Scala 2.9 , YARN 2.2 , Standalone 部署模式下调度的高可用性, shuffle 的优化等。
11) Spark 0.9.0 版本发布( 2014-02-02 ),增加了 GraphX ,机器学习新特性,流式计算新特性,核心引擎优化(外部聚合、加强对 YARN 的支持)等。
12) Spark 0.9.1 版本发布( 2014-04-09 ),增加使用 YARN 的稳定性,改进 Scala 和 Python API 的奇偶性。
13) Spark 1.0.0 版本发布( 2014-05-30 ),增加了 Spark SQL 、 MLlib 、 GraphX 和 Spark Streaming 都增加了新特性并进行了优化。 Spark 核心引擎还增加了对安全 YARN 集群的支持。
14) Spark 1.0.1 版本发布( 2014-07-11 ),增加了 Spark SQL 的新特性和堆 JSON 数据的支持等。
15) Spark 1.0.2 版本发布( 2014-08-05 ), Spark 核心 API 及 Streaming , Python , MLlib 的 bug 修复。
16) Spark 1.1.0 版本发布( 2014-09-11 )。
17) Spark 1.1.1 版本发布( 2014-11-26 ), Spark 核心 API 及 Streaming , Python , SQL , GraphX 和 MLlib 的 bug 修复。
18) Spark 1.2.0 版本发布( 2014-12-18 )。
19) Spark 1.2.1 版本发布( 2015-02-09 ), Spark 核心 API 及 Streaming , Python , SQL , GraphX 和 MLlib 的 bug 修复。
20) Spark 1.3.0 版本发布( 2015-03-13 )。
21) Spark 1.4.0 版本发布( 2015-06-11 )。
22) Spark 1.4.1 版本发布( 2015-07-15 ), DataFrame API 及 Streaming , Python , SQL 和 MLlib 的 bug 修复。
要想对 Spark 有整体性的 了解 ,推荐读者阅读 Matei Zaharia 的 Spark 论文。此处笔者先介绍 Spark 中的一些概念:
q RDD ( resillient distributed dataset ):弹性分布式数据集。
q Task :具体执行任务。 Task 分为 ShuffleMapTask 和 ResultTask 两种。 ShuffleMapTask 和 ResultTask 分别类似于 Hadoop 中的 Map , Reduce 。
q Job :用户提交的作业。一个 Job 可能由一到多个 Task 组成。
q Stage : Job 分成的阶段。一个 Job 可能被划分为一到多个 Stage 。
q Partition :数据分区。即一个 RDD 的数据可以划分为多少个分区。
q NarrowDependency :窄依赖。即子 RDD 依赖于父 RDD 中固定的 Partition 。 NarrowDependency 分为 OneToOneDependency 和 RangeDependency 两种。
q ShuffleDependency : shuffle 依赖,也称为宽依赖。即子 RDD 对父 RDD 中的所有 Partition 都有依赖。
q DAG ( Directed Acycle graph ):有向无环图。用于反映各 RDD 之间的依赖关系。
Spark 为什么要选择 Java 作为开发语言?笔者不得而知。如果能对二者进行比较,也许能看出一些端倪。表 2-1 列出了对 Scala 与 Java 的比较。
表 2-1 Scala 与 Java 的比较
Scala | Java | |
语言类型 | 面向函数为主,兼有面向对象 | 面向对象( Java8 也增加了 lambda 函数编程) |
简洁性 | 非常简洁 | 不简洁 |
类型推断 | 丰富的类型推断,例如深度和链式的类型推断、 duck type 、隐式类型转换等,但也因此增加了编译时长 | 少量的类型推断 |
可读性 | 一般,丰富的语法糖导致的各种奇幻用法,例如方法签名 | 好 |
学习成本 | 较高 | 一般 |
语言特性 | 非常丰富的语法糖和更现代的语言特性,例如 Option 、模式匹配、使用空格的方法调用 | 丰富 |
并发编程 | 使用 Actor 的消息模型 | 使用阻塞、锁、阻塞队列等 |
通过以上比较似乎仍然无法判断 Spark 选择开发语言的原因。由于函数式编程更接近计算机思维,因此便于通过算法从大数据中建模,这应该更符合 Spark 作为大数据框架的理念吧!
整个 Spark 主要由以下模块组成:
q Spark Core : Spark 的核心功能实现,包括: SparkContext 的初始化( Driver Application 通过 SparkContext 提交)、部署模式、存储体系、任务提交与执行、计算引擎等。
q Spark SQL :提供 SQL 处理能力,便于熟悉关系型数据库操作的工程师进行交互查询。此外,还为熟悉 Hadoop 的用户提供 Hive SQL 处理能力。
q Spark Streaming :提供流式计算处理能力,目前支持 Kafka 、 Flume 、 Twitter 、 MQTT 、 ZeroMQ 、 Kinesis 和简单的 TCP 套接字等数据源。此外,还提供窗口操作。
q GraphX :提供图计算处理能力,支持分布式, Pregel 提供的 API 可以解决图计算中的常见问题。
q MLlib :提供机器学习相关的统计、分类、回归等领域的多种算法实现。其一致的 API 接口大大降低了用户的学习成本。
Spark SQL 、 Spark Streaming 、 GraphX 、 MLlib 的能力都是建立在核心引擎之上,如图 2-4 。
图 2-4 Spark 各模块依赖关系
Spark Core 提供 Spark 最基础与最核心的功能,主要包括:
q SparkContext :通常而言, Driver Application 的执行与输出都是通过 SparkContext 来完成的,在正式提交 Application 之前,首先需要初始化 SparkContext 。 SparkContext 隐藏了网络通信、分布式部署、消息通信、存储能力、计算能力、缓存、测量系统、文件服务、 Web 服务等内容,应用程序开发者只需要使用 SparkContext 提供的 API 完成功能开发。 SparkContext 内置的 DAGScheduler 负责创建 Job ,将 DAG 中的 RDD 划分到不同的 Stage ,提交 Stage 等功能。内置的 TaskScheduler 负责资源的申请、任务的提交及请求集群对任务的调度等工作。
q 存储体系: Spark 优先考虑使用各节点的内存作为存储,当内存不足时才会考虑使用磁盘,这极大地减少了磁盘 I/O ,提升了任务执行的效率,使得 Spark 适用于实时计算、流式计算等场景。此外, Spark 还提供了以内存为中心的高容错的分布式文件系统 Tachyon 供用户进行选择。 Tachyon 能够为 Spark 提供可靠的内存级的文件共享服务。
q 计算引擎:计算引擎由 SparkContext 中的 DAGScheduler 、 RDD 以及具体节点上的 Executor 负责执行的 Map 和 Reduce 任务组成。 DAGScheduler 和 RDD 虽然位于 SparkContext 内部,但是在任务正式提交与执行之前将 Job 中的 RDD 组织成有向无关图(简称 DAG )、并对 Stage 进行划分决定了任务执行阶段任务的数量、迭代计算、 shuffle 等过程。
为了扩大应用范围, Spark 陆续增加了一些扩展功能,主要包括:
q Spark SQL :由于 SQL 具有普及率高、学习成本低等特点,为了扩大 Spark 的应用面,因此增加了对 SQL 及 Hive 的支持。 Spark SQL 的过程可以总结为:首先使用 SQL 语句解析器( SqlParser )将 SQL 转换为语法树( Tree ),并且使用规则执行器( RuleExecutor )将一系列规则( Rule )应用到语法树,最终生成物理执行计划并执行的过程。其中,规则包括语法分析器( Analyzer )和优化器( Optimizer )。 Hive 的执行过程与 SQ 类似。
q Spark Streaming : Spark Streaming 与 Apache Storm 类似,也用于流式计算。 Spark Streaming 支持 Kafka 、 Flume 、 Twitter 、 MQTT 、 ZeroMQ 、 Kinesis 和简单的 TCP 套接字等多种数据输入源。输入流接收器( Receiver )负责接入数据,是接入数据流的接口规范。 Dstream 是 Spark Streaming 中所有数据流的抽象, Dstream 可以被组织为 DStream Graph 。 Dstream 本质上由一系列连续的 RDD 组成。
q GraphX : Spark 提供的分布式图计算框架。 GraphX 主要遵循整体同步并行计算模式( Bulk Synchronous Parallell ,简称 BSP )下的 Pregel 模型实现。 GraphX 提供了对图的抽象 Graph , Graph 由顶点( Vertex )、边( Edge )及继承了 Edge 的 EdgeTriplet (添加了 srcAttr 和 dstAttr 用来保存源顶点和目的顶点的属性)三种结构组成。 GraphX 目前已经封装了最短路径、网页排名、连接组件、三角关系统计等算法的实现,用户可以选择使用。
q MLlib : Spark 提供的 机器学习框架。机器学习是一门涉及概率论、统计学、逼近论、凸分析、算法复杂度理论等多领域的交叉学科。 MLlib 目前已经提供了基础统计、分类、回归、决策树、随机森林、朴素贝叶斯、保序回归、 协同过滤、聚类、维数缩减、特征提取与转型、频繁模式挖掘、预言模型标记语言、管道等多种数理统计、概率论、数据挖掘方面的数学算法 。
Spark 应用程序从编写到提交、执行、输出的整个过程如图 2-5 所示,图中描述的步骤如下:
1) 用户使用 SparkContext 提供的 API (常用的有 textFile 、 sequenceFile 、 runJob 、 stop 等)编写 Driver application 程序。此外 SQLContext 、 HiveContext 及 StreamingContext 对 SparkContext 进行封装,并提供了 SQL 、 Hive 及流式计算相关的 API 。
2) 使用 SparkContext 提交的用户应用程序,首先会使用 BlockManager 和 BroadcastManager 将任务的 Hadoop 配置进行广播。然后由 DAGScheduler 将任务转换为 RDD 并组织成 DAG , DAG 还将被划分为不同的 Stage 。最后由 TaskScheduler 借助 ActorSystem 将任务提交给集群管理器( Cluster Manager )。
3) 集群管理器( Cluster Manager )给任务分配资源,即将具体任务分配到 Worker 上, Worker 创建 Executor 来处理任务的运行。 Standalone 、 YARN 、 Mesos 、 EC2 等都可以作为 Spark 的集群管理器。
图 2-5 代码执行过程
RDD 可以看做是对各种数据计算模型的统一抽象, Spark 的计算过程主要是 RDD 的迭代计算过程,如图 2-6 。 RDD 的迭代计算过程非常类似于管道。分区数量取决于 partition 数量的设定,每个分区的数据只会在一个 Task 中计算。所有分区可以在多个机器节点的 Executor 上并行执行。
图 2-6 RDD 计算模型
从集群部署的角度来看, Spark 集群由以下部分组成:
q Cluster Manager : Spark 的集群管理器,主要 负责资源的分配与管理。 集群管理器分配的资源属于一级分配,它将各个 Worker 上的内存、 CPU 等资源分配给应用程序,但是并不负责对 Executor 的资源分配。 目前, Standalone 、 YARN 、 Mesos 、 EC2 等都可以作为 Spark 的集群管理器。
q Worker : Spark 的工作节点。对 Spark 应用程序来说,由集群管理器分配得到资源的 Worker 节点主要负责以下工作:创建 Executor ,将资源和任务进一步分配给 Executor ,同步资源信息给 Cluster Manager 。
q Executor :执行计算任务的一线进程。主要负责任务的执行以及与 Worker 、 Driver App 的信息同步。
q Driver App :客户端驱动程序,也可以理解为客户端应用程序,用于将任务程序转换为 RDD 和 DAG ,并与 Cluster Manager 进行通信与调度。
这些组成部分之间的整体关系如图 2-7 所示。
图 2-7 Spark 基本架构图
每项技术的诞生都会由某种社会需求所驱动, Spark 正是在实时计算的大量需求下诞生的。 Spark 借助其优秀的处理能力,可用性高,丰富的数据源支持等特点,在当前大数据领域变得火热,参与的开发者也越来越多。 Spark 经过几年的迭代发展,如今已经提供了丰富的功能。笔者相信, Spark 在未来必将产生更耀眼的火花。
[1] 图 2-1 和图 2-2 都来源自 http://blog.chinaunix.net/uid-28311809-id-4383551.html 。
后记:自己牺牲了7个月的周末和下班空闲时间,通过研究Spark源码和原理,总结整理的《深入理解Spark:核心思想与源码分析》一书现在已经正式出版上市,目前亚马逊、京东、当当、天猫等网站均有销售,欢迎感兴趣的同学购买。我开始研究源码时的Spark版本是1.2.0,经过7个多月的研究和出版社近4个月的流程,Spark自身的版本迭代也很快,如今最新已经是1.6.0。目前市面上另外2本源码研究的Spark书籍的版本分别是0.9.0版本和1.2.0版本,看来这些书的作者都与我一样,遇到了这种问题。由于研究和出版都需要时间,所以不能及时跟上Spark的脚步,还请大家见谅。但是Spark核心部分的变化相对还是很少的,如果对版本不是过于追求,依然可以选择本书。
京东(现有满100减30活动): http://item.jd.com/11846120.html
当当: http://product.dangdang.com/23838168.html
亚马逊: http://www.amazon.cn/gp/product/B01A5G5LHK/sr=8-1/qid=1452505597/ref=olp_product_details?ie=UTF8&me=&qid=1452505597&sr=8-1