Spark是UCBerkeleyAMPLab开源的通用分布式并行计算框架,目前已成为Apache软件基金会的顶级开源项目。
基于以下原因,Spark已经成为数据行业从业者(尤其是大数据领域)绕不开的必学技术栈中的一员:
下面对Spark的特性、对比Hadoop的优势、组成模块及运行原理等基础知识进行学习。
1
Spark的特性
图1:Apache官网描述的Spark特性
Apache在改版后的Spark官网中用了四个单词描述Spark的特性:Simple.Fast.Scalable.Unified.
1.1
Simple(简单易用)
Spark提供了丰富的高级运算操作,支持丰富的算子,并支持Java、Python、Scala、R、SQL等语言的API,使用户可以快速构建不同的应用。
1.2
Fast(高效快速)
Spark将处理的每个任务都构造成一个DAG(DirectedAcyclicGraph,有向无环图)来执行,实现原理是基于RDD(ResilientDistributedDataset,弹性分布式数据集)在内存中对数据进行迭代计算,以实现批量和流式数据的高性能快速计算处理。
之前的官方数据表明:如果计算数据是从磁盘中读取,Spark计算速度是MapReduce的10倍以上;如果计算数据是从内存中读取,Spark计算速度则是MapReduce的100倍以上。
目前的官网已经撤下这一数据,估计是统计的场景和数据存在偏颇,不够全面。但这也从侧面说明,Spark拥有出色的计算性能已经是深入人心的不争事实,无需再用数据来佐证。
1.3
Scalable(可融合性)
Spark可以非常方便地与其他的开源产品进行融合。比如:Spark可以使用Hadoop的YARN和ApacheMesos作为它的资源管理和调度器;可以处理所有Hadoop支持的数据,包括HDFS、HBase和Cassandra等
这对于已经部署Hadoop集群的用户特别重要,因为不需要做任何数据迁移就可以使用Spark强大的计算处理能力。
Spark也可以不依赖于第三方的资源管理和调度器,它实现了Standalone作为其内置的资源管理和调度框架,这样进一步降低了Spark的使用门槛。
用户可以根据现有的大数据平台灵活地选择运行模式,使得所有人都可以非常容易地部署和使用Spark。
Spark还提供了在EC2上部署Standalone的Spark集群的工具。
此外,由于Spark是使用Scala这种函数式编程语言开发的,因此Spark也继承了Scala的可扩展性,可对类型数据结构、控制体结构等进行自定义的扩展。
1.4
Unified(统一通用)
大数据处理的传统方案需要维护多个平台,比如,离线任务是放在HadoopMapRedue上运行,实时流计算任务是放在Storm上运行。
而Spark提供了一站式的统一解决方案,可用于批处理、交互式查询(SparkSQL)、实时流处理(SparkStreaming)、机器学习(SparkMLlib)和图计算(GraphX)等。这些不同类型的处理都可以在同一个应用中无缝组合使用。
Spark统一的解决方案非常具有吸引力,毕竟任何公司都想用统一的平台去处理遇到的问题,减少开发和维护的人力成本和部署平台的物理成本。
2
Spark的优势
这里说的Spark的优势,是对比Hadoop的MapReduce而言,因此,我们需要先看看MapReduce的局限与不足:
比如说,用MapReduce实现两个表的Join都是一个很有技巧性的过程,如下图所示:
图2-1:使用MapReduce实现Join的过程
在Spark中,MapReduce的这些局限与不足都能找到很好的解决方案,而且Spark具备MapReduce的所有优点,这就是Spark的优势所在。
2.1
高性能(★)
HadoopMapReduce每次计算的中间结果都会存储到HDFS的磁盘上;而Spark的中间结果可以保存在内存,在内存中进行数据处理,内存放不下了会写入本地磁盘,而不是HDFS。
Spark可以通过将流拆成小的batch,来提供DiscretizedStream处理交互式实时数据。
因此,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:SparkCheckPoint机制
如图2-2所示,对RDD-b做快照缓存处理,那么当RDD-n在用到RDD-b的数据时,就无需再重新计算RDD-b,而是直接从Cache(缓存)处取RDD-b的数据进行计算。
CheckPoint通过冗余数据和日志记录更新操作两种方式,对“血统”检测进行容错辅助,避免“血统”过长造成容错成本过高。
通过Spark的以上机制,就能提高迭代计算的性能和容错率,解决上面列出的MapReduce的第7个问题。
3
Spark的生态圈(组成模块)
Spark生态中包含多个紧密集成的模块组件,这些模块结合密切并且可以相互调用。目前,Spark的生态圈已经从大数据计算和数据挖掘,扩展到机器学习、NLP、语音识别等领域。
图3-1:ApacheSpark生态圈
Spark有多组件的支持应用场景,其在SparkCore的基础上提供了SparkSQL、SparkStreaming、MLlib、GraphX、SparkR等核心组件。
SparkSQL旨在将熟悉的SQL数据库查询语言与更复杂的基于算法的分析相结合,SparkStreaming用于实时流计算,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、AliCloud等不同的存储系统、大数据库、关系型数据库中读入和写出数据,在实时流计算中可以从Flume、Kafka等多种数据源获取数据并执行流式计算。
Spark也支持非常丰富的数据文件格式,比如txt、json、csv等。同时也支持parquet、orc、avro等格式,这几种格式在数据压缩和海量数据查询上优势较为明显。
3.1
SparkCore
SparkCore实现了Spark基本的核心功能,其包含以下几个部分:
图3-2:SparkCore结构
3.1.1.Spark基础配置
3.1.2.Spark存储系统
Spark存储系统用于管理Spark运行中依赖的数据的存储方式和存储位置。存储系统会优先考虑在各节点的内存中存储数据,内存不足时将数据写入磁盘中,这也是Spark计算性能高的重要原因。
数据存储在内存和磁盘之间的边界可以灵活控制,同时可以通过远程网络调用将结果输出到远程存储中,比如HDFS、HBase等。
3.1.3.Spark调度系统
Spark调度系统主要由DAGScheduler和TaskScheduler组成。
主要的调度算法有FIFO、FAIR。
3.1.4.Spark计算引擎
Spark计算引擎主要由内存管理器、TaskSet管理器、Task管理器、Shuffle管理器等组成。
3.2
SparkSQL
SparkSQL是Spark用来操作结构化数据的程序包,其提供了基于SQL、HiveSQL、与传统的RDD编程的数据操作结合的数据处理方法,使得分布式的数据集处理变得更加简单,这也是Spark被广泛使用的重要原因。
3.3
SparkStreaming
SparkStreaming提供了对实时数据进行流式计算的API,支持流数据的可伸缩和容错处理,可以与Kafka、Flume、TCP等多种流式数据源集成。
SparkStreaming的实现,也使用RDD抽象的概念,使得在为流数据编写Application时更为方便。
3.4
MLlib
SparkMLlib作为一个提供常见机器学习(ML)功能的程序库,包括分类、回归、聚类等多种机器学习算法的实现,其简单易用的API接口降低了机器学习的门槛。
3.5
GraphX
GraphX用于分布式图计算,比如可以用来操作社交网络的朋友关系图,能够通过其提供的API快速解决图计算中的常见问题。
3.6
PySpark
为了用Spark支持Python,ApacheSpark社区发布了一个工具PySpark。使用PySpark,就可以使用Python编程语言中的RDD。
PySpark提供了PySparkShell,它将PythonAPI链接到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的集群架构主要由ClusterManager(集群资源管理器)、Worker(工作节点)、Executor(执行器)、Driver(驱动器)、Application(应用程序)共五部分角色组成,如下图所示:
图4-2-1:Spark集群架构
下面简要介绍每部分角色。
4.2.1.ClusterManager
ClusterManager是Spark的集群资源管理器,存在于Master进程中,主要用于对整个集群资源进行管理和分配,根据其部署模式的不同,可以分为Local、Standalone、YARN、Mesos、Cloud等模式。
4.2.2.Worker
Worker是Spark的工作节点,用于执行提交的任务,其主要的工作职责有以下几点:
图4-2-2:SparkWorker节点工作机制
在YARN集群模式下运行Worker节点一般指的是NodeManager节点,Standalone模式下运行一般指的是slave节点。
4.2.3.Executor
Executor是真正执行计算任务的组件,是Application运行在Worker上的一个进程。这个进程负责Task的运行,并将数据保存在内存或磁盘存储中,也能够将结果数据返回给Driver。
4.2.4.Application
Application是基于SparkAPI编写的应用程序,包括实现Driver功能的代码和在集群中各个Executor上要执行的代码。
一个Application由多个Jobs组成。
其中Application的入口为用户所定义的main()方法。
4.2.5.Driver
Driver是Spark的驱动器节点,可以运行在Application节点上,也可以由Application提交给ClusterManager,再由ClusterManager安排Worker进行运行,其主要的工作职责有:
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(ResilientDistributedDataset,弹性分布式数据集)是Spark中最重要的一个概念,是Spark对所有数据处理的一种基本抽象,它代表一个不可变、可分区、元素可并行计算的数据集合。
RDD具有数据流模型的特点:自动容错、位置感知性调度和可伸缩性。
在Spark中可以通过一系列的算子对RDD进行操作,主要分为Transformation(转换)和Action(执行)两种操作:
图4-3-2:RDD操作处理过程
由于RDD是只读的弹性分区数据集,如果对RDD中的数据进行改动,就只能通过Transformation操作,由一个或多个RDD计算生成一个新的RDD,所以RDD之间就会形成类似流水线(Pipeline)的前后依赖关系,前面的称为父RDD,后面的称为子RDD
当计算过程中出现异常情况导致部分Partition数据丢失时,Spark可以通过这种依赖关系从父RDD中重新计算丢失的分区数据,而不需要对RDD中的所有分区全部重新计算,以提高迭代计算性能。
RDD之间的依赖关系又分为NarrowDependency(窄依赖)和WideDependency(宽依赖)。
图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,然后依次类推递归执行,ChildStage需要等待所有的ParentStages执行完成后才可以执行。
这样每个Stage内的RDD都尽可能在各个节点上并行地被执行,以提高运行效率。
图4-3-4:Stage的划分过程(黑色表示前置计算分区)
以图4-3-4作为例子,使用这个思路进行Stage划分,从后往前倒推:
RDDC,RDDD,RDDE,RDDF之间均为窄依赖,因此被构建在同一Stage2中;
RDDA与RDDB之间是宽依赖关系,因此被构建在不同的Stage中,RDDA在Stage1中,RDDB在Stage3中;
RDDB与RDDG之间为窄依赖,因此被构建在同一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)。