大数据解决方案的强大在于他们可以快速处理大规模,复杂的数据集,可以比传统的方法更快,更好地生成洞见。
一套大数据解决方案通常包含多个重要组件,从存储,计算和网络等硬件层,到数据处理引擎,再到利用改良的统计和计算算法,数据可视化来获得商业洞见的分析层。这中间数据处理引擎起到了十分重要的作用。毫不夸张的说,数据处理引擎之于大数据就像CPU之于计算机,或大脑之人类。
Spark基于内存的基本类型为一些应用程序带来100倍的性能提升。Spark允许用户程序将数据加载到集群内存中用于反复查询,非常适用于大数据和机器学习。
目前,Spark正在促使hadoop和大数据生态系统发生演变,以更好地支持端到端的大数据分析需求,例如,Spark已经超越了Spark核心,发展到Spark Streaming,SQL,MLlib,GraphX,SparkR等模块。
作为MapReduce的继承者,Spark主要有三个优点:
1. Spark非常好用,由于高级API玻璃了对集群本身的关注。你可以专注于你所要做的计算本身。
2. Spark很快,支持交互式使用和复杂算法。
3. Spark是一个通用引擎,可以用它来完成各种各样的运算,包括SQL查询,文本处理,机器学习等。
Spark是一个用来实现快速而通用的集群计算的平台。
在速度方面,Spark扩展了广泛使用的mapreduce计算模型,而且高效地支持更多计算模型,包括交互式查询和流处理。在处理大规模数据集时,速度非常重要的。速度快就意味着我们可以进行交互式的数据操作,否则我们每次操作就需要等待数分钟甚至数小时。Spark的一个主要特点就是能够在内存中进行计算,因而更快。不过即使是必须在磁盘上进行复杂的运算,Spark依然比MapReduce更加高效。
总的来说,Spark适用于各种各样原先需要多种不同的分布式平台的场景,包括批处理,迭代算法,交互式查询,流处理。通过在一个统一的框架下支持这些不同的计算,Spark使我们可以简单而低耗地把各种流程整合在一起。这样的组合,在实际的数据分析过程是很有意义的。不仅如此,Spark的这种特性还大大减轻了原先需要对各种平台分别管理的负担。
Spark所提供的接口非常丰富,除了提供基于Python,Java,Scala和SQL的简单易用的API以及内件的丰富的程序库意外,Spark还能和其他大数据工具紧密结合。例如,Spark可以运行在hadoop集群上,访问包括Cassandra在内的任意Hadoop数据源。
Spark项目包含多个紧密集成的组件。Spark的核心是一个对由很多计算任务组成的,运行在多个工作机器或者一个计算集群上的应用进行调度,分发以及监控的计算引擎。由于Spark的核心引擎有着速度快和通用的特点,因此Spark还支持为各种不同应用场景专门设计的高级组件,比如SQL和机器学习等。
Spark SQL结构化数据 |
Spark Streaming实时学习 |
Mlib机器学习 |
GraphX图计算 |
Spark Core |
|||
独立调度器 |
YARN |
Mesos |
SparkCore实现了Spark的基本功能,包括任务调度,内存管理,错误恢复,与存储系统交互等模块。Spark Core中还包含了对弹性分布式数据集(resilient distributed dataset,简称RDD)的API定义。RDD表示分布在多个计算节点上可以并行操作的元素集合,是Spark主要的编程抽象。SparkCore提供了创建和操作这些集合的多个API。
SparkSQL是Spark用来操作操作结构化数据的程序包。通过Spark SQL,我们可以使用SQL或者Apache Hive版本的SQL方言(HQL)来查询数据。SparkSQL支持多种数据源,比如Hive表,Parquet以及JSON等。除了为Spark 提供一个SQL接口,Spark SQL还支持开发者将SQL和传统的RDD编程的数据操作方式相结合,不论是使用Python,Java还是Scala,开发者都可以在单个的应用中同时使用SQL和复杂的数据分析。通过与Spark所提供的丰富的计算环境进行如此紧密的结合,SparkSQL得以从其它开源数据仓库工具中脱颖而出。SparkSQL是在Spark 1.0中被引入的。
SparkStreaming是Spark提供的对实时数据进行流式计算的组件。比如生产环境中的网页服务器日志,或是网络服务中用户提交的状态更新组成的消息队列,都是数据流。Spark Streaming提供了用来操作数据流的API,并且与Spark Core中的RDD API高度对应。这样一来,程序员编写应用时的学习门槛就得以降低,不论是操作内存或硬盘中的数据,还是操作实时数据流,程序员都能应对自如。从底层设计来看,Spark Streaming支持于Spark Core同级别的容错性,吞吐量以及可伸缩性。
Spark中还包括一个提供常见的机器学习(ML)功能的程序库,叫做MLib。MLib提供了很多种机器学习的算法,包括分类,回归,聚类,协同过滤等,还提供模型评估,数据导入等额外功能。MLib还提供了一些更底层的机器学习源语,包括一个统一的梯度下降优化算法。所有这些方法都被设计为可以在集群上轻松伸缩的架构。
GraphX
GraphX是用来操作图的程序库,可以进行并行的图计算。与Spark Streaming和Spark SQL类似,GraphX也扩展了Spark的RDD API,能用来创建一个顶点和边都包含任意属性的向量图。GraphX还支持针对图的各种操作(比如进行图分隔的subgraph和操作所有顶点的mapVertices),以及一些常用图算法(比如PageRank和三角计数)。
就底层而言,Spark设计为可以高效地在一个计算节点到数千个计算节点之间伸缩计算。为了实现这样的要求,同时获得最大灵活性,Spark支持在各种集群管理器上运行,包括Hadoop YARN,Apache Mesos,以及Spark自带的一个简易调度器,叫做独立调度器。如果要在没有预装任何集群管理器的集群上安装Spark,那么Spark自带的独立调度器可以让你轻松入门;而如果你有了一个装有Hadoop YARN或Mesos的集群,通过对Spark这些集群管理器的支持,应用通用能运行在这些集群上。
Spark通过一些列组件支持各种数据科学任务。Spark shell通过提供Python,Scala的接口,是我们方便地进行交互式数据分析。Spark SQL也提供一个独立的SQLshell,我们可以在这个shell中使用SQL探索数据,也可以通过标准的Spark程序或者Spark shell来进行SQL查询。
机器学习和数据分析则通过MLlib程序库提供支持。另外,Spark还支持调用R或者Matlab写成的外部程序。
通过封装,Spark不需要开发者关注如何在分布式系统上编程这样复杂的问题,也无需过多关注网络通信和程序容错性。Spark已经为工程师提供了足够的接口来快速实现常见的任务,以及对应用进行监控,审查和性能调优。其API模块化的特性使得利用程序库进行开发以及本地测试大大简化。
Spark用户之所以选择Spark来开发他们的数据处理应用,正式因为Spark提供了丰富的功能,容易学习和使用,并且成熟稳定。
Spark是由一个强大而活跃的开源社区开发和维护的,社区中很多开发者来自许许多多不同的机构。Spark是于2009年作为一个研究项目在加州伯克利大学分校RAD实验室(AMPLab的前身)诞生。实验室中的一些研究人员层级用过Hadoop MapReduce。他们发现MapReduce在迭代计算和交互计算的任务上表现得效率低下。因为,Spark从一开始就为交互式查询和迭代算法设计的,同时还支持内存式存储和高效的容错机制。
2009年,关于Spark的研究论文在学术会议上发表,同年Spark项目正式诞生。其后不久,相比于MapReduce,Spark在某些任务上已经获得了10 ~ 20 倍的性能提升。
除了加州大学伯克利分校,对Spark做出贡献的主要机构有Databricks,雅虎还有Intel。
2011年,AMPLab开始基于Spark开发更高层的组件,比如Shark(Spark上的Hive)和SparkStreaming。这些组件和其它一些组件一起被称为伯克利数据分析工具栈(BDAS)。
Spark最早在2010年3月开源,并且在2013年6月交给了Apache基金会,现在已经成为Apache开源基金会的顶级项目。
Spark不仅可以将任何Hadoop分布式文件系统(HDFS)上的文件读取为分布式数据集,也可以支持其他支持Hadoop接口的系统,比如本地文件,亚马逊S3,Cassandra,Hive,HBase等。Hadoop并非Spark的必要条件,Spark支持任何实现了Hadoop接口的存储系统。Spark支持的Hadoop输入格式包括文本文件,SequenceFile,Avro,Parquet等。
Spark可以通过Python,Java或Scala来使用。
Spark本身是用Scala来写的,运行在Java虚拟机(JVM)上。
Spark能够在工作节点上把数据读取到内存中,所以许多分布式计算都可以在几秒钟之内完成,哪怕是那种在十几个节点上处理TB级别的数据的计算。这就使得一般需要在shell中完成的那些交互式的即时探索性分析变得非常适合Spark。Spark提供Python以及Scala的增强版shell,支持与集群的连接。
在Spark中,我们通过对分布式数据集的操作来表达我们的计算意图,这些计算会自动地在集群上并行执行。这样的数据集被称为弹性分布式数据集(Resilient distributed dataset),简称RDD。RDD是Spark对分布式数据和计算的基本抽象。
从上层看,每个Spark应用都有一个驱动器程序(driver program)来发起集群上各种并行操作。驱动器程序包含应用的main函数,并且定义了集群上的分布式数据集,还对这些分布式数据集应用了相关操作。
驱动器程序通过一个SparkContext对象来访问Spark。这个对象代表对计算集群的一个连接。
Shell启动时已经自动创建了一个SparkContext对象,是一个叫做sc的变量。一旦有了SparkContext,就可以用它来创建RDD。
要执行这些操作,驱动器程序一般要管理多个执行器(Executor)节点。比如,如果我们在集群上运行count()操作,那么不同的节点会统计文件的不同部分的行数。
驱动器程序 SparkContext |
工作节点 |
执行器
|
工作节点 |
Spark对数据的核心抽象---弹性分布式数据集RDD。RDD其实就是分布式的元素集合。在Spark中,对数据的所有操作不外乎创建RDD,转化以后RDD以及调用RDD操作进行求值。而在这一切背后,Spark会自动将RDD中的数据分发到集群上,并将操作并行化执行。
Spark中的RDD就是一个不可变的分布式对象集合。每个RDD都被分为多个分区,这些分区运行在集群中的不同节点上。RDD可以包含Python,Java,Scala中任意类型的对象,甚至可以包含用户自定义的对象。
用户可以使用两种方法创建RDD:读取一个外部数据集,或在驱动器程序里分发驱动器程序中的对象集合。
创建出RDD后,RDD支持两种类型的操作:转化操作和行动操作。转化操作会由一个RDD生产一个新的RDD,比如map()和filter(),转化出来的RDD是惰性求值的,只有在行动操作中用到这些RDD时才会被计算。许多转化操作都是针对各个元素的。
行动操作会对RDD计算出一个结果,并把结果返回到驱动器程序中,或把结果存储到外部存储系统(如HDFS)中,这会触发实际的计算,比如count()和first()。
转化操作和行动操作的区别在于Spark计算RDD的方式不同。
转化操作返回的是RDD,而行动操作返回的是其他的数据类型。
通过转化操作,你从以有的RDD中派生出新的RDD,Spark会使用谱系图(lineage Graph)来记录这些不同RDD之间的依赖关系。Spark需要用这些信息来按需计算出每个RDD,也可以依靠谱系图在持久化的RDD丢失部分数据时恢复所丢失的数据。
由于行动操作需要生产实际的输出,他们会强制执行那些求值必须用到的RDD的转换操作。
Spark支持很多种输入输出源。一部分原因是Spark本身是基于Hadoop生态圈而构建,特别是Spark可以通过HadoopMapReduce所使用的Inputformat和Outformat接口访问数据,而大部分场景的文件格式与存储系统都支持这种接口。
Spark以及生态系统提供了很多可选方案,下面是三类常见的数据源。
1. 文件格式与文件系统
对于存储在本地文件系统或分布式文件系统中的数据,Spark可以访问很多种不同的文件格式,包括文本文件,JSON,Sequencefile,以及protocol buffer。
2. Spark SQL中的结构化数据源
3. 数据库与键值对
Spark对很多种文件格式的读取和保存方式都很简单。从诸如文本文件的非结构化的文件,到诸如JSON格式的半结构化的文件,再到诸如SequenceFile这样的结构化的文件,Spark都可以支持。Spark会根据文件扩展名选择对应的处理方式。
格式名称 |
结构化 |
备注 |
文本文件 |
否 |
普通的文本文件,每行一条记录 |
JSON |
半结构化 |
常见的基于文本的格式,半结构化,大多数库都要求每行一条记录。 |
CSV |
是 |
非常常见的基于文本的格式,通常在电子表格应用中使用。 |
SequenceFile |
是 |
一种基于键值对数据的场景Hadoop文件格式 |
Protocol buffers |
是 |
一种快速,节约空间的跨语言格式 |
对象文件 |
是 |
用来将Spark作业中的数据存储下来已让共享的代码读取。改变类的时候它会失效,因为它依赖于Java序列化。 |
在Spark中读写文本文件很容易。当我们将一个文本文件读取为RDD时,输入的每一行都会成为RDD的一个元素。也可以将多个完整的文本文件一次性读取为pair RDD,其中键是文件名,值是文件内容。
JSON是一种使用较广的半结构化数据格式。读取JSON数据的最简单的方式是将数据作为文本文件读取,然后使用JSON解析器来对RDD中的值进行映射操作。在Java和Scala中也可以使用一个自定义Hadoop格式来操作JSON数据。三种编程语言中有大量可用的JSON库,Python中使用的是内建的库,而在Java和Scala中则会使用Jackson。
逗号分隔符(Comma SeparatedValues,CSV) ,有时也称为字符分隔值,因为分隔字符也可以不是逗号,其文件以纯文本形式存储表格数据(数字和文本)。纯文本意味着该文件是一个字符序列,不含必须像二进制数字哪有被解读的数据。CSV文件由任意数目的记录组成,记录间以某种换行符分隔;每条记录由字段组成,字段间的分隔符是其它字符或字符串,最常见的是逗号或制表符。通常,所有记录都有完全相同的字段序列。
CSV是一种通用的、相对简单的文件格式,被用户、商业和科学广泛应用。最广泛的应用是在程序之间转移表格数据,而这些程序本身是在不兼容的格式上进行操作的(往往是私有的和/或无规范的格式)。因为大量程序都支持某种CSV变体,至少是作为一种可选择的输入/输出格式。
例如,一个用户可能需要交换信息,从一个以私有格式存储数据的数据库程序,到一个数据格式完全不同的电子表格。最可能的情况是,该数据库程序可以导出数据为“CSV”,然后被导出的CSV文件可以被电子表格程序导入。
“CSV”并不是一种单一的、定义明确的格式(尽管RFC 4180有一个被通常使用的定义)。因此在实践中,术语“CSV”泛指具有以下特征的任何文件:
1. 纯文本,使用某个字符集,比如ASCII、Unicode、EBCDIC或GB2312;
2. 由记录组成(典型的是每行一条记录);
3. 每条记录被分隔符分隔为字段(典型分隔符有逗号、分号或制表符;有时分隔符可以包括可选的空格);
4. 每条记录都有同样的字段序列。
与JSON一样,CSV也有很多不同的库。Python中使用自带的CSV库。在Scala和Java中则使用opencsv库。
SequenceFile是由没有相对关系结果的键值对文件组成的常用Hadoop格式。
SequenceFile文件有同步标记,Spark可以用它来定位到文件中的某个点,然后再与记录的边界对齐。这可以让Spark使用多个节点高效地并行读取SequenceFile文件。SequenceFile也是Hadoop MapReduce作业中常用的输入输出格式,所以如果你在使用一个已有的hadoop
系统,数据很有可能是以SequenceFile的格式供你使用的。
由于Hadoop使用了一套自定义的序列化框架,因此SequenceFile是由实现hadoop的Writable接口的元素组成。
对象文件看起来就像是对SequenceFile的简单封装,它允许存储只包含值的RDD。和SequenceFile不一样的是,对象文件是使用Java序列化写出的。
除了Spark封装的格式外,也可以与任何Hadoop支持的格式交互。Spark支持新旧两套Hadoop文件API,提供了很大的灵活性。
KeyValueTextInputFormat是最简单的Hadoop输入格式之一,可用于从文本文件中读取键值对数据。每一行都会被独立处理,键和值之间用制表符隔开。
可以使用自定义hadoop输入格式读取JSON数据。
在大数据工作中,我们经常需要对数据进行压缩以节省存储空间和网络传输开销。对于大多数Hadoop输出格式来说,我们可以指定一些压缩编码器来压缩数据。
Spark支持从本地文件系统中读取文件,不过它要求文件在集群中所有节点的相同路径下都可以找到。
Hadoop分布式文件系统(HDFS)是一种广泛使用的文件系统,Spark能够很好地使用它。HDFS被设计为可以在廉价的硬件上工作,有弹性应对节点失败,同时提高吞吐量。Spark和HDFS可以部署在同一批机器上,这样Spark可以数据分布来尽量避免一些网络开销。
Spark SQL是在Spark 1.0中新加入Spark的组件,并快速成为了Spark中较受欢迎的操作结构化和半结构化数据的方式。结构化数据指的是有结构信息的数据---也就是所有的数据记录都具有一致字段结构的集合。Spark SQL支持多种结构化数据源作为输入,而且Spark SQL知道数据的结构信息,它还可以从这些数据源中只读出所需字段。
Apache Hive是Hadoop上的一种常见的结构化数据源。Hive可以在HDFS内或者其它存储系统上存储多种格式的表。这些格式从普通文本到列式存储格式,应有尽有。Spark SQL可以读取Hive支持的任何表。
Spark可以从任何支持Java数据库连接(JDBC)的关系型数据库中读取数据,包括MySQL,Postgre等系统。
Spark的一大好处就是可以通过增加集群数量并使用集群模式运行,来扩展程序的计算能力。
Spark可以在各种各样的集群管理器(HadoopYARN,Apache Mesos,还有Spark自带的独立集群管理器)上运行,所以Spark应用既能够适应专用集群,又能用于共享的云计算环境。
在分布式环境下,Spark集群采用的是主/从结构。在一个Spark集群中,有一个节点负责中央协调,调度各个分布式工作节点。这个中央协调节点被称为驱动器(Driver)节点,与之对应的工作节点被称为执行器(Executor)节点。驱动器节点可以和大量的执行器节点进行通信,它们也都作为独立的Java进程运行。驱动器节点和所有执行器节点一起被称为一个Spark应用(Application)。
Spark驱动器程序 |
集群管理器Mesos,YARN或独立集群管理器 |
集群工作节点 执行器进程 |
集群工作节点 执行器进程 |
集群工作节点 执行器进程 |
Spark应用通过一个叫做集群管理器(ClusterManager)的外部服务在集群中的机器上启动。Spark自带的集群管理器被称为独立集群管理器。Spark也能运行在Hadoop YARN和Apache Mesos这两大开源集群管理器上。
Spark驱动器是执行你的程序中的main()方法的进程。它执行用户编写的用来创建SparkContext,创建RDD,以及进行RDD的转化操作和行动操作的代码。其实,当你启动Spark Shell时,你就启动了一个Spark驱动器程序(Spark shell总是会预先加载一个叫做sc的SparkContext对象)。驱动器程序一旦终止,Spark应用也就结束了。
驱动器程序在Spark应用中有下述两个职责:
1. 把用户程序转为任务
Spark驱动器程序负责把用户程序转化为多个物理执行的单元,这些单元也被称为任务。从上层看,所有的Spark程序都遵循同样的结构:程序从输入数据创建一系列RDD,在使用转化操作派生出新的RDD,最后使用行动操作收集或存储结果RDD中的数据。Spark程序其实是隐士地创建出一个由操作组成的逻辑上的有向无环图(Directed Acyclic Graph,简称DAG)。当驱动器程序运行时,它会把这个逻辑图转化为物理执行计划。
Spark会对逻辑执行做一些优化,比如将连续的映射转化为流水线化执行,将多个操作合并到一个步骤中等。这样Spark就把逻辑计划转为一些列步骤(stage)。而每个步骤又由多个任务组成。这些任务会被打包并送到集群中。任务是Spark中最小的工作单元,用户程序通常要启动成百上千的独立任务。
2. 为执行器节点调度任务
有了物理执行计划之后,Spark驱动器程序必须在各种执行器进程间协调任务的调度。执行器进程启动后,会向驱动器进程注册自己。因此,驱动器进程始终对应用中所有的执行器节点有完整的记录。每个执行器节点代表一个能够处理任务和存储RDD数据的进程。
Spark驱动器程序会根据当前的执行器节点集合,尝试把所有任务基于数据所在位置分配给合适的执行器进程。当任务执行时,执行器进程会把缓存数据存储起来,而驱动器进程通用会跟着这些缓存数据的位置,并且利用这些位置信息来调度以后的任务,以尽量减少数据的网络传输。
驱动器程序会将一些Spark应用的运行时的信息通过网页界面呈现出来,默认在端口4040上。
Spark执行器节点是一种工作进程,负责在Spark作业中运行任务,任务间相互独立。Spark应用启动时,执行器节点就被同时启动,并且始终伴随着整个Spark应用的生命周期而存在。如果有执行器节点发生了异常或崩溃,Spark应用也可以继续执行。执行器进程有两大作用:第一,它们负责运行组成Spark应用的任务,并将结果返回给驱动器进程;第二,它们通过自身的块管理器(Block Manager)为用户程序中要求缓存的RDD提供内存式存储。RDD是直接缓存在执行器进程内的,因此任务可以在运行时充分利用缓存数据加速运算。
驱动器节点和执行器节点是如何启动的那?Spark依赖于集群管理器来启动执行器节点,而在某些特殊情况下,也依赖于集群管理器来启动驱动器节点。集群管理器是Spark中的可插拔式组件。这样,除了Spark自带的独立集群管理器,Spark也可以运行在其他外部集群管理器上,比如YARN或Mesos。
1. 用户通过Spark-submit脚本提交应用。----Spark为各种集群管理器提供了通用的工具来提交作业。
2. Spark-submit脚本启动驱动器程序,调用用户定义的main()方法。
3. 驱动器程序与集群管理器通信,申请资源以启动执行器节点。
4. 集群管理器为驱动器程序启动执行器节点。
5. 驱动器进程执行用户应用中的操作。根据程序中所定义的对RDD的转化操作和行动操作,驱动器节点把工作以任务的形式发送到执行器进程。
6. 任务在执行器程序中进行计算并保存结果。
7. 如果驱动器程序的main()方法退出,或者调用了SparkContext.stop(),驱动器程序会终止执行器进程,并且通过集群管理器释放资源。
Java和Scala中使用最广泛的构建工具是Maven和sbt。Maven通常用于Java工程,而sbt则一般用于Scala工程。
现实中,许多集群是在多个用户间共享的。共享的环境会带来调度方面的挑战:如果两个用户都启动了希望使用整个集群所有资源的应用,该如何处理?Spark有一系列调度策略来保证资源不会被过度使用,还允许工作负载设置优先级。
在调度多用户集群时,Spark主要依赖集群管理器来在Spark应用间共享资源。当Spark应用向集群管理器申请执行器节点时,应用收到的执行器节点个数可能比申请的更多或者更少,这取决与集群的可用性与争用。许多集群管理器支持队列,可以为队列定义不同的优先级或容量限制,这样Spark就可以把作业提交到相应的队列中。
Spark应用有一种特殊情况,就是那些长期运行(long lived)的应用。这意味着这些应用从不主动退出。Spark SQL中的JDBC服务器就是一个长期运行的Spark应用。当JDBC服务器启动后,它会从集群管理器获得一系列执行器节点,然后就成为用户提交SQL查询的永久入口。由于这个应用本身就是为多用户调度工作的,所以它需要一种细粒度的调度机制来强制共享资源。Spark提供了一种用来配置应用内调度策略的机制。Spark内部的公平调度器(FairScheduler)会让长期运行的应用定义调度任务的优先级队列。
Spark可以运行在各种集群管理器上,并通过集群管理器访问集群中的机器。如果你只想在一堆机器上运行Spark,那么自带的独立模式是部署该集群最简单的方法。然而,如果你有一个需要与别的分布式应用共享的集群(比如即可以运行Spark作业又可以运行Hadoop Mapreduce作业),Spark也可以运行在两个光伏使用的集群管理器---Hadoop YARN与Apache Mesos上面。
Spark独立集群管理器提供在集群上运行应用的简单方法。这种集群管理器由一个主节点和几个独立工作节点组成,各自都分配有一定量的内存和CPU核心。当提交应用时,你可以配置执行器进程使用的内存量,以及所有执行器进程使用的CPU核心总数。
YARN是在Hadoop 2.0中引入的集群管理器,它可以让多种数据处理框架运行在一个共享的资源池上,并且通常安装在于Hadoop文件系统(HDFS)相同的物理节点上。在这样配置的YARN集群上运行Spark是有意义的,它可以让Spark在存储数据的物理节点上运行,以快速访问HDFS中的数据。
Apache Mesos是一个通用集群管理器,既可以运行分析型工作负载又可以运行长期运行的服务。要在Mesos上使用Spark,需要把一个mesos://的URL传给Spark-submit:
Mesos调度模式
和别的集群管理器不同,Mesos提供了两种模式来在一个集群内的执行器进程间共享资源。在“细粒度”模式(默认)中,执行器进程占用的CPU核心数会在他们执行任务时动态变化,因此一台运行了多个执行器进程的机器可以动态共享CPU资源。而在“粗粒度”模式中,Spark提前为每个执行器进程分配固定数量的CPU数目,并且在应用结束前绝不释放这些资源,哪怕执行器进程当前不在运行任务。
当多用户共享的集群运行shell这样的交互式的工作负载时,由于应用会在它们不工作时候降低它们所占用的核心数,以允许别的用户程序使用集群,所以这种情况下细粒度模式显得非常合适。然而,在细粒度模式下调度任务会带来更多的延迟(这样的话,一些像Spark Streaming这样需要低延迟的应用就会表现很差),应用需要在用户输入新的命令时,为重新分配CPU核心等待一段时间。
如果是从零开始,可以先选择独立集群管理器。独立模式安装起来最简单,而且如果你只是使用Spark的话,独立集群管理器提供与其它集群管理器完全一样的全部功能。
如果你在使用Spark的同时使用其他应用,或者是要用到更丰富的资源调度功能(例如队列),那么YARN或Mesos都能满足你的需求。而在这两者中,对于大多数hadoop发行版来说,一般YARN已经预装好了。
Mesos相对于YARN和独立模式一大优点在于其细粒度共享的选项,该选项可以将类似Spark Shell这样的交互式应用中的不同命令分配到不同的CPU上。因此对于多用户同时运行交互式shell的用例更有用处。
在任何时候,最好把Spark运行在HDFS的节点上,这样能快速访问存储。
Spark SQL用来操作结构化和半结构化数据的接口---SparkSQL。结构化数据是指任何有结构信息的数据。所谓结构信息,就是每条记录共有的已知的字段集合。当数据符合这样的条件时,Spark SQL就会使得针对这些数据的读取和查询变得更加简单高效。
Spark SQL提供了三大功能:
(1) Spark SQL可以从各种结构化数据源(例如JSON,Hive,Parquet等)中读取数据。
(2) Spark SQL不仅支持在Spark程序内使用SQL语句进行数据查询,也支持从类似商业智能软件Tableau这样的外部工具中通过标准数据库连接器(JDBC/ODBC)连接Spark SQL进行查询。
(3) 当在Spark程序内使用Spark SQL时,Spark SQL支持常规的Python/Java/Scala代码高度整合,包括连接RDD与SQL表,公开的自定义SQL函数接口等。
为了实现这些功能,Spark SQL提供了一种特殊的RDD,叫做SchemaRDD。SchemaRDD是存放Row对象的RDD,每个row对象代表一行记录。SchemaRDD还包含记录的机构信息(即字段信息)。SchemaRDD看起来和普通的RDD很像,但在内部,SchemaRDD可以利用结构信息更加高效地存储数据。此外,SchemaRDD还支持RDD上所没有的一些新操作,比如运行SQL查询。SchemaRDD可以从外部数据源创建,也可以从查询结果或者普通RDD中创建。
----后续版本中SchemaRDD已经被DataFrame所取代。
… |
Tableau |
JDBC/ODBC |
Spark SQL shell |
Spark SQL |
Hive |
JSON |
Parquet |
… |
Your Application |
Apache Hive是hadoop上的SQL引擎,Spark SQL编译时可以包含Hive支持,也可以不包含。包含Hive支持的Spark SQL可以支持Hive表访问,UDF(用户自定义函数),SerDe(序列化格式和反序列化格式),以及Hive查询语言(HiveQL/HQL)。需要强调的一点是,如果要在Spark SQL中包含Hive的库,并不需要事先安装Hive。
Spark SQL支持很多种结构化数据源,可以让你跳过复杂的读取过程,轻松从各种数据源中读取到Row对象。这些数据源包含Hive表,JSON和Parquet文件。此外,当你使用SQL查询这些数据源中的数据并且只用到了一部分字段时,Spark SQL可以智能地只扫描这些用到的字段,而不像SparkContext.hadoopFile中那样简单粗暴地扫描全部数据。
当从Hive中读取数据时,Spark SQL支持任何Hive支持的存储格式(SerDe),包括文本文件,RCFiles,ORC,Parquet,Avro以及Protocol Buffer。
Parquet是一种流行的列式存储格式,可以高效地存储具有嵌套字段的记录。Parquet格式经常在Hadoop生态圈中被使用,它也支持Spark SQL的全部数据类型。Spark SQL提供了直接读取和存储Parquet格式文件的方法。
如果你有一个JSON文件,其中的记录遵循同样的结果信息,那么Spark SQL就可以通过扫描文件推测出结果信息。
Spark SQL也提供JDBC连接支持,这对于让商业智能(BI)工具连接到Spark集群上以及在多用户间共享一个集群的场景都非常有用。JDBC服务器作为一个独立的Spark驱动器程序运行,可以在多用户之间共享。任意一个客户端都可以在内存中缓存数据表,对表进行查询。集群的资源以及缓存数据都在所有用户之间共享。
Spark SQL的JDBC服务器与Hive中的HiveServer2相一致。由于使用了Thrift通信协议,它也被称为“ThriftServer”。注意,JDBC服务器支持需要Spark在打开Hive支持的选项下编译。
许多应用需要即时处理收到的数据,例如用来实时追踪页面访问统计的应用,训练集群学习模型的应用,还有自动检测异常的应用。Spark Streaming是Spark为这些应用而设计的模型。它允许用户使用一套和批处理非常接近的API来编写流式计算应用,这样就可以大量重用批处理应用的技术甚至代码。
和Spark基于RDD的概念很相似,SparkStreaming使用离散化流作为抽象表示,叫做DStream。DStream是随时间推移而收到的数据的序列,在内部,每个时间区间收到的数据都做为RDD存在,而DStream是由这些RDD所组成的序列(因此得名离散化)。DSteam可以从各种输入源创建,比如Flume,Kafka,或者HDFS。创建出来的DStream支持两种操作,一种是转换操作(transformation),会生成一个新的DStream,另一种是输出操作(output operation),可以把数据写入外部系统中。
和批处理程序不同,SparkStreaming应用需要进行额外配置来保证24*7不间断工作。检查点(checkpointing)机制,也就是把数据存储到可靠文件系统上的机制,这也是Spark Streaming用来实现不间断工作的主要方式。
Spark Streaming只可以在Java和Scala中使用。
Spark Streaming程序最好以使用Maven或者sbt编译出来的独立应用的形式运行。
1. 创建StreamingContext,它是流计算功能的主要入口。
2. StreamingContext会在底层创建出SparkContext,用来处理数据。其构造函数还接收用来指定多长时间处理一次新数据的批次间隔(batch interval)作为输入。
Spark Streaming提供了一个叫做DStream(Discretized Stream)的高级抽象,DStream表示一个持续不断输入的数据流,可以基于Kafka、TCP Socket、Flume等输入数据流创建。在内部,一个DStream实际上是由一个RDD序列组成的。Sparking Streaming是基于Spark平台的,也就继承了Spark平台的各种特性,如容错(Fault-tolerant)、可扩展(Scalable)、高吞吐(High-throughput)等。
在Spark Streaming中,每个DStream包含了一个时间间隔之内的数据项的集合,我们可以理解为指定时间间隔之内的一个batch,每一个batch就构成一个RDD数据集,所以DStream就是一个个batch的有序序列,时间是连续的,按照时间间隔将数据流分割成一个个离散的RDD数据集,如图所示(来自官网):
我们都知道,Spark支持两种类型操作:Transformations和Actions。Transformation从一个已知的RDD数据集经过转换得到一个新的RDD数据集,这些Transformation操作包括map、filter、flatMap、union、join等,而且Transformation具有lazy的特性,调用这些操作并没有立刻执行对已知RDD数据集的计算操作,而是在调用了另一类型的Action操作才会真正地执行。Action执行,会真正地对RDD数据集进行操作,返回一个计算结果给Driver程序,或者没有返回结果,如将计算结果数据进行持久化,Action操作包括reduceByKey、count、foreach、collect等。关于Transformations和Actions更详细内容,可以查看官网文档。
同样、Spark Streaming提供了类似Spark的两种操作类型,分别为Transformations和Output操作,它们的操作对象是DStream,作用也和Spark类似:Transformation从一个已知的DStream经过转换得到一个新的DStream,而且Spark Streaming还额外增加了一类针对Window的操作,当然它也是Transformation,但是可以更灵活地控制DStream的大小(时间间隔大小、数据元素个数),例如window(windowLength, slideInterval)、countByWindow(windowLength,slideInterval)、reduceByWindow(func, windowLength, slideInterval)等。Spark Streaming的Output操作允许我们将DStream数据输出到一个外部的存储系统,如数据库或文件系统等,执行Output操作类似执行Spark的Action操作,使得该操作之前lazy的Transformation操作序列真正地执行。
Spark Streaming使用“微批次”的架构,把流式计算当做一些列连续的小规模批处理来对待。Spark Streaming从各种输入源中读取数据,并把数据分组为小的批次。新的批次按均匀的时间间隔创建出来。在每个时间区间开始的时候,一个新的批次就创建出来,在该区间内收到的数据都会被添加到这个批次中。在时间区间结束时,批次停止增长。时间区间的大小是由批次间隔这个参数决定的。批次间隔一般设在500毫秒到几秒直接,由应用开发者配置。每个输入批次都形成一个RDD,以Spark作业的方式处理并生成其它的RDD。处理的结果可以以批处理的方式传给外部系统。高层次的架构如图所示:
Spark Streaming |
输入数 |
输入数据流 |
Spark Streaming的编程抽象是离散化流,也就是DStream。它是一个RDD序列,每个RDD代表数据流中一个时间片内的数据。
DStream的转化操作可以分为无状态和有状态两种。
在无状态转化操作中,每个批次的处理不依赖于之前的批次的数据。例如map(),filter(),reduceBykey()等,都是无状态转化操作。
有状态转化操作需要使用之前批次的数据或者是中间结果来计算当期批次的数据。有状态转化操作包括基于滑动窗口的转化操作和追踪状态变化的转化操作。
MLlib是Spark中提供集群学习函数的库。它是专为在集群上并行运行的情况而设计的。MLlib中包含许多机器学习算法,可以在Spark支持的所有编程语言中使用。
MLlib的设计理念非常简单:把数据以RDD的形式表示,然后在分布式数据集上调用各种算法。MLlib引入了一些数据类型(比如点和向量),不过归根结底,MLlib就是RDD上一系列可供调用的函数的集合。
MLlib中只包含能够在集群上运行良好的并行算法,这点很重要,有些经典的集群学习算法没有包含在其中,就是因为它们不能并行执行。相反地,一些较新的研究得出的算法适用于集群,也被包含在MLlib中,例如分布式随机森林算法,K-means||聚类,交替最小二乘法等。
机器学习算法尝试根据训练数据使得表示算法行为的数学目标最大化,并以此来进行预测或作出决定。机器学习分几种,包括分类,回归,聚类,每种都有不一样的目标。
所有的学习算法都需要定义每个数据点的特征集,也就是传给学习函数的值。
大多数算法都只是专为数值特质定义的,因此提取特征并转化特征向量是机器学习过程中很重要的一步。
当数据已经成为特征向量的形式后,大多数机器学习算法都会根据这些向量优化一个定义好的数学函数。
大多数机器学习算法都有多个会影响结果的参数,所以现实中的机器学习流水线会训练出多个不同版本的模型,然后分别对其进行评估。要这么做的话,通常需要把输入数据分为“训练集”和“测试集”,并且只使用前者进行训练,这样就可以用后者来检验模型是否过度拟合(overfit)了训练数据。
分类与回归是监督式学习的两种主要形式。监督式学习指算法尝试使用有标签的训练数据(也就是已知结果的数据点)根据对象的特征预测结果。分类和回归的区别在于预测的变量的类型:在分类中,预测出的变量是离散的。在回归中,预测出的变量是连续的。
MLlib包含多种分类和回归的算法,其中包括简单的线性算法以及决策树和森林算法。
线性回归是回归中最常用的方法之一,是指特征的线性组合来预测输出值。
逻辑回归是一种二元分类方法,用来寻找一个分隔阴性和阳性实例的线性分隔平面。
支持向量机(简称SVM)算法是另一种使用线性分隔平面的二元分类算法,同样只预期0或1。
朴素贝叶斯(NaïveBayes)算法是一种多元分类算法,它使用基于特征的线性函数计算将一个点分到各类中的得分。
聚类算法是一种无监督学习任务,用于将对象分到具有高度相似性的聚类中。
协同过滤是一种根据用户对各种产品的交互与评分来推荐新产品的推荐系统技术。
由Berkeley实验室开发,使用scala语言开发。
全新的,高速的,类似Map/Reduce的分布式计算引擎。----支持将数据缓存在内存中,为迭代式查询进行了优化。
通用DAG执行和调度,支持延迟计算和高效优化。
轻量级的调度框架和多线程计算模型,极低的调度和启动开销。
比Hadoop快10x~100x
兼容Hadoop存储API。----可以读写存储在HDFS或HBase上的数据。支持其他数据源,需要实现Hadoop InputFormat。
只能Map+Reduce,不适合描述复杂数据处理过程,如Machine Learning,join
完成单个Map/Reduce后数据写到磁盘,在读出,数据不能共享,不适合循环模型
基于内存计算,使用RDD和DAG优化任务流程。
更低的框架开销
丰富的API支持,如Scala,Java,Python
Map端不强制sort,降低部分应用场景的时间开销。
机器学习和图应用中常用的迭代算法(每一步对数据执行相似的函数)
交互式数据挖掘工具(用户反复查询一个数据子集)
Spark Application的运行架构由两部分组成:driverprogram(SparkContext)和executor。
Spark Application一般都是在集群中运行,比如SparkStandalone,YARN,mesos,这些集群给Spark Application提供了计算资源和这些资源管理,这些资源既可以给executor运行,也可以给driver program运行。
根据Spark Application的driver program是否在资源集群中运行,Spark Application的运行方式又可以分为Cluster模式和Client模式。
Spark 1.0.0提供了一个SparkApplication的部署工具bin/spark-sumbit.
Application:基于Spark的用户程序,包含了一个driverprogram和集群中多个的executor。
Driver Program:运行Application的main()函数并且创建SparkContext,通常用SparkContext代表DriverProgram。
Cluster Manager:在集群上获取资源的外部服务(例如:Standalone,Mesos,Yarn)
Worker Node:集群中任何可以运行Application代码的节点
Executor:是为某Application运行在Workernode上的一个进程,该进程负责运行Task,并且负责将数据存在内存或者磁盘上。每个Application都有各自独立的executors。
Task:被送到某个executor上的工作单元。
Job:包含多个Task组成的并行运算,往往由Sparkaction催生,该术语可以经常在日志中看到。
Stage:每个job会被拆分很多组task,每组任务被称为Stage,也可称为TaskSet,该术语可以经常在日志中看到。
RDD:Spark的基本计算单元,可以通过一系列算子进行操作(主要有Transformation和Action操作)。
DAG Schduler:根据Job构建基于Stage的DAG。并提交Stage给TaskScheduler。
Task Scheduler:将Taskset提交给worker(集群)运行并回报结果。
•构建Spark Application的运行环境(启动SparkContext)
•SparkContext向资源管理器(可以是Standalone、Mesos、Yarn)申请运行Executor资源,并启动StandaloneExecutorBackend,executor向SparkContext申请Task。
•SparkContext将应用程序代码发放给executor
•SparkContext构建成DAG图、将DAG图分解成Stage、将Taskset发送给TaskScheduler、最后由Task Scheduler将Task发放给Executor运行。
•Task在Executor上运行,运行完毕释放所有资源。
每个任务生成一个实例
SparkContext
每个SparkContext调度一部分Executor
应用程序的任务容器,任务在Executor内部运行。
Spark on yarn在Spark 1.0版本中比较成熟,但如果运行在线上环境中,面临很多挑战:
挑战1:应用程序日志如何获取?
Spark on yarn提供了应用程序运行的web界面,可以通过web界面查看spark作业的stage,task等详细信息。但无法获取应用程序的运行日志。这些日志通常保存在YARN的NodeManager节点上,一旦运行完成后可能会被聚集后保存到HDFS上。对于运行完成的作业,可以通过命令“bin/yarn logs -applicationId application_2323_xxxx”将日志打印出来,但是当日志量非常大时,显然不会很好地方法。因此,对于想把spark运行在yarn上的公司,第一个需要做的工作可能是为用户提供一个好的日志查看工具,可以查看正在运行的,或者运行完成(成功和失败)的spark作业的,在yarn-client和yarn-cluster模式下地日志。
挑战2:如何为spark作业设置资源需求?
YARN允许spark作业为driver和executor设置需要的cpu和内存资源量,但是到底设置多少最合适,不好确定。因此,最好能够提供一个资源获取工具,可以查看spark作业实际占用的内存和cpu资源量,以便修正用户的资源参数。
挑战3:yarn资源调度器对spark这类作业的水土不服。
对于yarn而言,spark仍然是一种比较特殊的作业,这使得spark难以与其他类型的应用程序(比如MapReduce)友好地运行在一个集群中。
(1) YARN中的资源调度器采用的是基于资源预留的调度机制,这种机制会使得大资源需求的作业获取资源非常慢,而spark正式这类大资源需求的作业。Spark采用的是多线程方案,这使得一个executor可能会占用很大资源,对于yarn而言,可能是资源利用率的灾难。
(2) YARN的这种资源预留机制在运行sparkstreaming作业时,可能产生饿死现象。如果你在yarn集群中运行了sparkstreaming作业,可能会产生资源无限预留但是永远得不到满足的情况,导致spark streaming作业用于得不到运行。这个在spark streaming与其他短类型的作业,比如spark和mapreduce作业共享集群时很容易发生。
•每个Application获取专属的executor进程,该进程在Application期间一直驻留,并以多线程方式运行tasks。这种 Application隔离机制有其优势的,无论是从调度角度看(每个Driver调度它自己的任务),还是从运行角度看(来自不同 Application的Task运行在不同的JVM中)。当然,这也意味着Spark Application不能跨应用程序共享数据,除非将数据写入到外部存储系统。
•Spark与资源管理器无关,只要能够获取executor进程,并能保持相互通信就可以了。
•提交SparkContext的Client应该靠近Worker节点(运行Executor的节点),最好是在同一个Rack里,因为SparkApplication运行过程中SparkContext和Executor之间有大量的信息交换;如果想在远程集群中运行,最好使用RPC将 SparkContext提交给集群,不要远离Worker运行SparkContext。
•Task采用了数据本地性和推测执行的优化机制。
RDD,全称为ResilientDistributed Datasets
RDD代表一个被分区的只读数据集。
可以对RDD进行持久化,让用户显示地将数据存储到磁盘和内存中,并能控制数据的分区。
RDD还提供了一组丰富的操作来操作这些数据。
发生错误时可以自动重构。
RDD不需要物化。----RDD含有如何从其他RDD衍生(即计算)出本RDD的相关信息(即Lineage),据此可以从物理存储的数据计算出相应的RDD分区
RDD的初始创建都是由SparkContext来负责的。将内存中的集合或者外部文件系统作为输入源。
将一个RDD通过一定的操作变换成另外一个RDD;
转换时,只创建依赖关系Lineage,不实际执行;
Map,fileter,groupby,join等
对RDD进行持久化,可以让RDD保存在磁盘或者内存中
由于Spark是惰性计算(lazy computing)
对于RDD的行动操作都是触发Spark作业的运行,从而产生最终结果
Count,collect,save,cache等
RDD是一个分区的数据集
分区的多少涉及对这个RDD进行并行计算的力度
每一个RDD分区的计算操作都在一个单独的任务中被执行
对于RDD的分区多少,用户可以自行制定,如果没有指定,将采用默认值
目前Spark的分区函数:
HashPartitionser(哈希分区)
RangePartitioner(区域分区)
•RDD优先位置属性与Spark中的调度相关,返回的是此RDD的每个partition所存储的位置
•在Spark进行任务调度的时候,尽可能地将任务分配到数据块所存储的位置
–以从Hadoop中读取数据生成RDD为例,preferredLocations会返回每一个数据块所在的机器名或者IP地址
•窄依赖narrowdependencies
–子RDD的每个分区依赖常数个父分区
•宽依赖wide dependencies
–子RDD的每个分区依赖所有父RDD
•从执行的角度考虑
–narrow dependencies可以支持在同一个clusternode上以管道形式执行多条命令
•例如在执行了map后,紧接着执行filter。
–wide dependencies需要所有的父分区都是可用的,可能还需要调用类似MapReduce之类的操作进行跨节点传递。
•从失败恢复的角度考虑
–narrow dependencies的失败恢复更有效,因为它只需要重新计算丢失的parent partition即可,而且可以并行地在不同节点进行重计算。
–而wide dependencies,一个节点的失败将会导致父RDD的多个分区重新计算 ,牵涉到RDD各级的多个ParentPartitions。
Spark中的数据的存储和管理都由spark的存储管理模块来负责的。
从功能上看,spark的存储管理模块分为两个主要部分:
(1) RDD缓存
包括基于内存和磁盘的缓存
(2) Shuffle数据的持久化
Shuffle的中间结果由存储管理模块负责管理
Shuffle性能的好坏直接影响spark应用程序的整体性能,所有存储管理模块对于Shuffle的持久化管理有别于RDD的缓存
---对于RDD的操作(如:转换,执行操作等)最终都是基于RDD中的分区进行的。
---但是在存储管理模块中,对于数据的存取是以数据块为单位的
---分区是逻辑上的概念,而数据块是物理上的数据实体
(1) 分区与数据块是一一对应的。一个RDD中的分区就对应存储管理模块中的一个数据块。
(2) 如何映射RDD的分区与数据块。Spark中每个RDD都有独立的ID号,而RDD中的每个分区也有独立的索引号,所以可以根据RDD的ID号和RDD内分区的索引号建立RDD分区与数据块的映射关系。
RDD基于内存的持久化缓存是由存储管理模块中的内存缓存(MemoryStore) 模块完成的。
内存缓存在其内部维护了一个以数据库名为键,块内容为值的哈希表。
内存缓存是由Java虚拟机进行管理的,对于内存还款可以使用的内存阈值可以进行配置。
配置参数:spark.storage.memoryFraction(默认值0.6)
如果大约60%的内存缓存空间占用,spark会:
丢弃一些数据块或将一些数据块存储到磁盘上
取决于:持久化选项,如果是磁盘缓存,则写道磁盘,否则直接删除。
•磁盘缓存的步骤:
–1、数据块放置在磁盘的特定目录下
•由spark.local.dir指定
–2、在磁盘缓存中,一个数据块对应着文件系统中的一个文件
Lineage恢复故障:
Lineage链过长,恢复很耗时
宽依赖,需要冲洗计算所有父分区
检查点
–Lineage链过长和宽依赖的分区采用检查点机制
–将RDD物化到磁盘
Spark Application在遇到action操作时,SparkContext会生成job,并将构成DAG图将给DAGScheduler解析成Stage。
Dag(有向无环图)
--一个有向图无法从人一丁点触发经过若干条边回到改点,则这个图是一个有向无环图。
-- 受制于某些任务必须必另一些任务较早执行的限制,必须排序为一个队列的任务集合,可以由一个DAG图呈现
---每个顶点表示一个任务
---每条边表示一种限制约束
---Spark任务可以用DAG表示
scheduler 模块主要分为两大部分:
–TaskSchedulerListener
•TaskSchedulerListener部分的主要功能是监听用户提交的job,将job分解为不同的类型的stage以及相应的task,并向TaskScheduler提交task。
–TaskScheduler
•TaskScheduler 接收用户提交的task并执行。而TaskScheduler根据部署的不同又分为三个子模块:
–ClusterScheduler
–LocalScheduler
–MesosScheduler
Stage有两种:
–ShuffleMapStage
•这种Stage是以Shuffle为输出边界
•其输入边界可以是从外部获取数据,也可以是另一个ShuffleMapStage的输出
•其输出可以是另一个Stage的开始
•ShuffleMapStage的最后Task就是ShuffleMapTask
•在一个Job里可能有该类型的Stage,也可以能没有该类型Stage。
–ResultStage
•这种Stage是直接输出结果
•其输入边界可以是从外部获取数据,也可以是另一个ShuffleMapStage的输出
•ResultStage的最后Task就是ResultTask
•在一个Job里必定有该类型Stage。
•一个Job含有一个或多个Stage,但至少含有一个ResultStage。
1. 每一个Mapper会根据Reducer的数量创建出相应的bucket。
--bucket是一个抽象概念,在实现中每个bucket可以对应一个文件,可以对应文件的一部分或者其它等
--bucket的数量是M*R。其中M是Map的个数,R是Reduce的个数。
2. Mapper产生的结果会根据设置的partitin算法填充到每个bucket中去。
--partition算法是可以自定义的。
--默认的算法是根据key哈希到不同的bucket中去。
3. 当Reducer启动时,会根据自己task的id和所依赖的Mapper的id从远端或是本地的blockmanager中取得相应的bucket作为Reducer的输入进行处理。
与RDD持久化的不同在于:
首先,Shuffle数据块必须是在磁盘上进行缓存,不能选择在内存中缓存。
其次,在RDD基于磁盘的持久化中,每一个数据块对应着一个文件,而在Shuffle数据块持久化中,Shuffle数据块表示的只是逻辑上的概念,不同的实现方式可以决定Shuffle数据块的不同存储方式。
早期的Shuffle write有两个比较大的问题:
1. Map的输出必须先全部存储到内存中,然后写入磁盘。这对内存是一个非常大的开销,当内存不足以存储所有的Map output时就会出错OOM。
2. 每一个Mapper都会产生Reducer number个shuffle文件,如果Mapper个数是1k,Reducer个数也是1k,那么就会产生1M个shuffle文件,这对于文件系统是一个非常大的负担。同时在shuffle数据量不大而shuffle文件又非常多的情况下,随机写也会严重降低IO的性能。
问题一的解决:
在Spark 0.8版本中,shuffle write采用了与RDD block write不同的方式,同时也为shuffle write单独创建了ShuffleBlockManager,部分解决了0.6和0.7版本中遇到的问题。
在这个版本中为shuffle write添加了一个新的类ShuffleBlockManager,由ShuffleBlockManager来分配和管理bucket。同时ShuffleBlockManager为每一个bucket分配一个DiskObjectWriter,每个write handler拥有默认100KB的缓存,使用这个write handler将Map output写入文件中。可以看到现在的写入方式变为buckets.writers(bucketId).write(pair),也就是说Map output的key-valuepair是逐个写入到磁盘而不是预先把所有数据存储在内存中在整体flush到磁盘中去。
问题二的解决:
为了解决shuffle文件过多的情况,Spark 0.8.1引入了新的shuffle consolidation,以期显著减少shuffle文件的数量。
假定该job有4个Mapper和4个Reducer,有2个core,也就是能并行运行两个task。我们可以算出Spark的shuffle write共需要16个bucket,也就有了16个write handler。在之前的Spark版本中,每一个bucket对应的是一个文件,因此在这里会产生16个shuffle文件。
•而在shuffleconsolidation中每一个bucket并非对应一个文件,而是对应文件中的一个segment,同时shuffle consolidation所产生的shuffle文件数量与Spark core的个数也有关系。
•在上面的图例中,job的4个Mapper分为两批运行,在第一批2个Mapper运行时会申请8个bucket,产生8个shuffle文件;而在第二批Mapper运行时,申请的8个bucket并不会再产生8个新的文件,而是追加写到之前的8个文件后面,这样一共就只有8个shuffle文件,而在文件内部这有16个不同的segment。因此从理论上讲shuffle consolidation所产生的shuffle文件数量为C×R,其中C是Spark集群的core number,R是Reducer的个数。
Spark是UC Berkeley AMP lab所开源的类HadoopMapReduce的通用并行架构,Spark拥有Hadoop MapReduce所具有的优点,但不同于MapReduce的是Job中间结果可以保存在内存中,从而不需要读写HDFS,因此Spark能更好地适用于数据挖掘与机器学习等需要迭代的MapReduce的算法。
Spark 是一种与 Hadoop 相似的开源集群计算环境,但是两者之间还存在一些不同之处,这些有用的不同之处使 Spark 在某些工作负载方面表现得更加优越,换句话说,Spark 启用了内存分布数据集,除了能够提供交互式查询外,它还可以优化迭代工作负载。
Spark是在Scala语言中实现的,他将Scala用作其应用程序框架。与Hadoop不同,Spark和Scala能够紧密基础,其中Scala 可以像操作本地集合对象一样轻松地操作分布式数据集。
尽管创建Spark是为了支持分布式数据集上的迭代作业,但是实际上它是对 Hadoop 的补充,可以在 Hadoop 文件系统中并行运行。通过名为 Mesos 的第三方集群框架可以支持此行为。Spark 由加州大学伯克利分校 AMP 实验室 (Algorithms, Machines, and People Lab) 开发,可用来构建大型的、低延迟的数据分析应用程序。
Shark基本上就是在Spark的框架基础上提供的和Hive一样的HiveQL命令接口,为了最大程度的保持和Hive的兼容性,Shark使用了Hive的API来实现Query Parsing和Logic Plan Generation,最后的PhysicalPlan execution阶段用Spark代替Hadoop MapReduce。通过配置Shark参数,Shark可以自动在内存中缓存特定的RDD,实现数据重用,进而加快特定数据集的检索。同时,Shark通过UDF用户自定义函数实现特定的数据分析学习算法,使得SQL数据查询和运算分析能结合在一起,最大化RDD的重复使用。
SparkR是一个为R提供了轻量级的Spark前端的R包。SparkR提供了一个分布式的DataFrame数据结构,解决了R中的Data Frame只能在单机中使用的瓶颈,它和R中的Data Frame一样支持许多操作,比如select,filter,aggregate等等。(类似dplyr包中的功能)这很好的解决了R的大数据级瓶颈问题。 SparkR也支持分布式的机器学习算法,比如使用MLib机器学习库。 SparkR为Spark引入了R语言社区的活力,吸引了大量的数据科学家开始在Spark平台上直接开始数据分析之旅。
Spark Streaming:构建在Spark上处理Stream数据的架构,基本的原理是将Stream数据分成小的时间片段(几秒),以类似batch批量处理的方式处理这小部分数据。Spark Streaming构架在Spark上,一方面是因为Spark的低延迟执行引擎(100ms+),虽然比不上专门的流式数据处理软件,也可以用于实时计算,另一方面相比基于Record的其它处理框架(如Storm),一部分窄依赖的RDD数据集可以从源数据重新计算达到容错处理目的。此外小批量处理的方式使得它可以同时兼容批量和实时数据处理的逻辑和算法。方便了一些需要历史数据和实时数据联合分析的特定应用场合。