在阅读本文之前,读者最好已经阅读了《Spark2.1.0之初识Spark》一文,本文将对Spark的基础知识进行介绍。但在此之前,读者先跟随本人来一次简单的时光穿梭,最后还将对Java与Scala在语言上进行比较。
版本变迁
经过5年多的发展,Spark目前的版本是2.3.0。Spark主要版本的发展过程如下:
- Spark诞生于UCBerkeley的AMP实验室(2009)。
- Spark正式对外开源(2010)。
- Spark 0.6.0版本发布(2012-10-15),大范围的性能改进,增加了一些新特性,并对Standalone部署模式进行了简化。
- Spark 0.7.0版本发布(2013-02-27),增加了更多关键特性,例如:PythonAPI、Spark Streaming的alpha版本等。
- Spark接受进入Apache孵化器(2013-06-21)。
- Spark 0.8.0版本发布(2013-09-25),一些新功能及可用性改进。
- Spark 0.8.1版本发布(2013-12-19),支持Scala 2.9,YARN 2.2,Standalone部署模式下调度的高可用性,shuffle的优化等。
- Spark 0.9.0版本发布(2014-02-02),增加了GraphX、机器学习、流式计算等新特性,对核心引擎的优化(外部聚合、加强对YARN的支持)等。
- Spark 1.0.0版本发布(2014-05-30),增加了Spark SQL。对MLlib、GraphX和Spark Streaming都增加了新特性并进行了优化。Spark核心引擎还增加了对安全YARN集群的支持。
- Spark 1.1.0版本发布(2014-09-11)。对MLlib andSpark SQL进行了显著的扩展等。
- Spark 1.2.0版本发布(2014-12-18),Spark SQL增加了对HIVE 13、动态分区的支持,SparkStreaming增加了Python语言的API等。
- Spark 1.3.0版本发布(2015-03-13),在Spark SQL 中增加了DataFrameAPI。
- Spark 1.4.0版本发布(2015-06-11),增加了R语言的API,对Spark核心引擎的可用性进行了改进,对MLlib和Spark Streaming进行了扩展。
- Spark 1.5.0版本发布(2015-09-09),对各种功能和API进行了修改或改进。
- Spark 1.6.0版本发布(2016-01-04),对Spark Core、Spark SQL、Spark Streaming、MLlib的API进行了改进,对SparkCore和Spark SQL的性能进行了优化。
- Spark 2.0.0版本发布(2016-07-26),增加API的稳定性,对SQL 2003标准的支持,性能的优化,结构化的Streaming,R语言UDF的支持等。
- Spark 2.1.0版本发布(2016-12-28),主要对结构化的Streaming进行了改进。
- Spark 2.2.0版本发布(2017-07-11),正式提供非实验性质的结构化的Streaming。
- Spark 2.3.0版本发布(2018-02-28),增加结构化Streaming的连续处理,Kubernetes的调度后端。
基本概念
要想对Spark有整体性的了解,推荐读者阅读Matei Zaharia的Spark论文。此处笔者先介绍Spark中的一些概念:
- RDD(resillient distributed dataset):弹性分布式数据集。Spark应用程序通过使用Spark的转换API可以将RDD封装为一系列具有血缘关系的RDD,也就是DAG。只有通过Spark的动作API才会将RDD及其DAG提交到DAGScheduler。RDD的祖先一定是一个跟数据源相关的RDD,负责从数据源迭代读取数据。
- DAG(Directed Acycle graph):有向无环图。在图论中,如果一个有向图无法从某个顶点出发经过若干条边回到该点,则这个图是一个有向无环图(DAG图)。Spark使用DAG来反映各RDD之间的依赖或血缘关系。
- Partition:数据分区。即一个RDD的数据可以划分为多少个分区。Spark根据Partition的数量来确定Task的数量。
- NarrowDependency:窄依赖。即子RDD依赖于父RDD中固定的Partition。NarrowDependency分为OneToOneDependency和RangeDependency两种。
- ShuffleDependency:Shuffle依赖,也称为宽依赖。即子RDD对父RDD中的所有Partition都可能产生依赖。子RDD对父RDD各个Partition的依赖将取决于分区计算器(Partitioner)的算法。
- Job:用户提交的作业。当RDD及其DAG被提交给DAGScheduler调度后,DAGScheduler会将所有RDD中的转换及动作视为一个Job。一个Job由一到多个Task组成。
- Stage:Job的执行阶段。DAGScheduler按照ShuffleDependency作为Stage的划分节点对RDD的DAG进行Stage划分(上游的Stage将为ShuffleMapStage)。因此一个Job可能被划分为一到多个Stage。Stage分为ShuffleMapStage和ResultStage两种。
- Task:具体执行任务。一个Job在每个Stage内都会按照RDD的Partition 数量,创建多个Task。Task分为ShuffleMapTask和ResultTask两种。ShuffleMapStage中的Task为ShuffleMapTask,而ResultStage中的Task为ResultTask。ShuffleMapTask和ResultTask类似于Hadoop中的 Map任务和Reduce任务。
Scala与Java的比较
目前越来越多的语言可以运行在Java虚拟机上,Java平台上的多语言混合编程正成为一种潮流。在混合编程模式下可以充分利用每种语言的特点和优势,以便更好地完成功能。Spark同时选择了Scala和Java作为开发语言,也是为了充分利用二者各自的优势。表2-1对这两种语言进行比较。
表2-1 Scala与Java的比较
|
Scala |
Java |
语言类型 |
面向函数为主,兼有面向对象 |
面向对象(Java8也增加了lambda函数编程) |
简洁性 |
非常简洁 |
不简洁 |
类型推断 |
丰富的类型推断,例如深度和链式的类型推断、 duck type 、隐式类型转换等,但也因此增加了编译时长 |
少量的类型推断 |
可读性 |
一般,丰富的语法糖导致的各种奇幻用法,例如方法签名、隐式转换 |
好 |
学习成本 |
较高 |
一般 |
语言特性 |
非常丰富的语法糖和更现代的语言特性,例如 Option 、模式匹配、使用空格的方法调用 |
丰富 |
并发编程 |
使用Actor的消息模型 |
使用阻塞、锁、阻塞队列等 |
注意:虽然Actor是Scala语言最初进行推广时,最吸引人的特性之一,但是随着Akka更加强大的Actor类库的出现,Scala已经在官方网站宣布废弃Scala自身的Actor编程模型,转而全面拥抱Akka提供的Actor编程模型。与此同时,从Spark2.0.0版本开始,Spark却放弃了使用Akka,转而使用Netty实现了自己的Rpc框架。遥想当年Scala“鼓吹”Actor编程模型优于Java的同步编程模型时,又有谁会想到如今这种场面呢?
Scala作为函数式编程的代表,天生适合并行运行,如果用Java语言实现相同的功能会显得非常臃肿。很多介绍Spark的新闻或文章经常以Spark内核代码行数少或API精炼等内容作为宣传的“法器”,这应该也是选择Scala的原因之一。另一方面,由于函数式编程更接近计算机思维,因此便于通过算法从大数据中建模,这也更符合Spark作为大数据框架的理念吧!
由于Java适合服务器、中间件开发,所以Spark使用Java更多的是开发底层的基础设施或中间件。