Spark的简介
Spark 萌芽于加州大学伯克利分校,基于底层Mesos(Nexus)跨平台调度器构建,可视为Hadoop的内存变体,主要的区别有:
- 有向无环图(DAG):Spark应用程序形成一个有向无环图,而Mapreduce则是严格的两阶段并行计算。
- 内存分析:Spark最核心的技术在于弹性分布式数据集(RDDs),这些数据集缓存在内存中,每个RDD还存储血统谱系图用于容错,这个谱系图包含了一系列转换,但需要部分或全部执行转换才能重新生成RDD,RDDs加速了迭代和交互式应用程序的性能:
- 迭代应用程序:迭代时可以重复使用RDD缓存,无须每次从磁盘读取缓存。
- 交互式应用程序:可以对同一个RDD运行多重查询
RDDs也可以作为文件在HDFS和其他存储上持久化。因此,Spark需要依赖Hadoop分布才能使用HDFS。
- 数据优先:RDD抽象是Spark API最主要的数据。通过操作RDD生成另一个RDD来执行计算,一次类推。而Mapreduce正好相反,只是在键值对的基础上分析数据集,重点在于计算。
- 简洁的API:Spark实在Scala语言中实现的,Spark默认API也支持Scala。Scala的函数式抽线天然适合RDD转换,如map,groupBy,filter等。此外,应用匿名函数或者lambda抽象可以简化标准的数据操作任务。
- REPL分析:Scala允许Scala解释器当作交互式数据分析工具使用。例如,在实现完整分析之前,可以用Scala shell命令解释器了解数据集的分布和特征。
Spark相关的项目:
- Spark SQL(前身Shark)支持在Spark上执行SQL查询。这项功能与DataFrame抽象相结合,将Spark打造成为一款数据分析的强大工具。
- MLlib是一套备受欢迎的机器学习和数据挖掘算法。另外,它包含有抽象可用于辅助特征提取。
- Spark Streaming将Spark转化为实时流处理系统,将输入视为微批处理,同时保留熟悉的Spark语法。、、
Spark的安装
Spark
Spark控制进程
守护进程 | 描述 |
---|---|
Driver(驱动程序) | 包含SparkContext实例的应用程序入口 |
Master(主进程) | 负责调度和资源编排 |
Worker(子进程) | 负责节点状态和运行执行器 |
Executor(执行器) | 根据作业分配,负责执行该作业派发的任务 |
- 每一个Spack应用程序都会用到一个SparkConf类型的配置对象。通过这个配置提供集群部署的应用程序名称和JAR。
- 通过SparkContext对象与Spark集群保持连接,该对象以SparkConf对象作为输入。驱动程序用于创建SparkContext对象。SparkContext也用于创建输入RDD。
- 每一个RDD对象都有一组转换函数,在应用一个操作后返回新的RDD。
- Spark应用程序包含转换操作和行动操作。行动通常是触发执行的输出操作-Spark作业只在执行输出行动时才提交执行。换而言之,Spark的转换懒惰无比,需要对其施加行动才可以完成转换。
Spark分层执行结构
实体(Entity) | 描述 |
---|---|
Application(应用程序) | SparkContext的一个实例 |
Job(作业) | 一个行动后执行的一组阶段 |
Stage(阶段) | 在shuffe内的一组转换 |
Task set(任务组) | 来自同一组阶段的任务组 |
Task(任务) | 一个阶段里的执行单元 |
SparkContext
RDDs创建
SparkContext提供的RDD创建方法
Signature(函数签名) | 描述 |
---|---|
parallelize[t](seq: Seq[t]):RDD[t] | 将一个Scala集合转换为一个RDD |
range(start: Long,stop:Long,step:Long):RDD[Long] | 创建一个RDD[Long],从start到stop(不包含stop),以step为步长增加元素 |
hadoopFile[K,V](path:String,inputFormatClass:Class[_ <:InputFormat[K,V]],keyClass:Class[K],valueClass:Class[V]):RDD[(K,V)] | 返回一个RDD用于path上的一个Hadoop文件,参数为K,V,并用inputFormatClass进行读取 |
textFile(path:String):RDD[String] | 返回一个RDD用于path上的一个Haoop文件。在后台,用TextInputFormat作为inputFormatClass,LongWriteable为keyClass,而Text为valueClass,调用hadoopFile()。在这种情况下Key是文件中的位置,而Value是一行 |
sequenceFile[K,V](path:String,keyClass:Class[K],valueClass:Class[V] ):RDD[(K,V)] | 返回一个RDD用于path上的一个Hadoop SequenceFile。在内部,将SequenceFileInputFormat作为inputFormatClass传递,调用hadoopFile() |
newAPIHadoopFile[K,V,F<: NewInputFormat[K,V]](path:String,inputFormatClass:Class[F], keyClass:Class[K], valueClass:Class[V]):RDD[(K,V)] | 使用Hadoop版本中的Hadoop API返回一个RDD用于Path上的一个Hadoop文件,参数为K,V和F |
wholeTextFile(path: String):RDD[(String, String)] | 返回一个RDD用于path上的全部Hadoop文件。在后台,用String作为Key和Value,而WholeTextFileInputFormat作为InputFormatClass。Key是文件路径,而Value是文件的整个内容。如果文件很小,可用此方法。如果文件较大,使用textFile() |
union[T](rdds: Seq[RDD[T]]):RDD[T] | 返回一个RDD,它是同一类型的所有输入RDD的并集 |
每个函数返回一个具有关联对象类型的RDD,例如,parallelize()返回一个ParallelCollectionRDD,而textFile()返回一个HadoopRDD。
外部依赖关系
由于Spark的分布式特性,Spark任务是跨多个Worker节点进行并行处理的。通常,数据(RDDs)和代码(如闭包)由Spark发出;但在某些情况下,任务代码可能要求访问外部文件或Java库。SparkContext也用于处理这些外部依赖关系
SparkContext依赖关系处理功能
Signature(函数签名) | 描述 |
---|---|
addFile(path:String):Unit | 将path上存在的文件下载到每个节点。可以通过SparkFiles.get(filename:String)访问该文件。除了指向本地或HDFS位置,path还可以指向远程HTTP/FTP位置 |
addJar(path:String):Unit | 添加位于path上的文件作为一个JAR依赖关系,用于被SparkContext执行的所有任务 |
创建共享变量
Spark转换操作worker机器上的数据独立副本,因此它们之间不存在共享状态。在某些情况下,在workers和驱动程序之间可能需要共享某些状态,例如,如果需要计算一个全局值。SparkContext提供两种类型的共享变量:
- 广播变量:顾名思义,广播变量是数据的只读副本,由驱动程序向worker任务进行广播,例如共享大变量的一个副本。Spark默认发出一个阶段中每个任务所需的数据。在执行每个任务之前,这个数据进行序列化和反序列化。另一方面,广播变量中的数据通过有效的P2P通信传播,并以反序列化的形式缓存。因此,只有在一个作业中跨多个阶段需要时,广播变量才大有作为。
Signature(函数签名) | 描述 |
---|---|
broadcast(v: T): Broadcast[t] | 广播v到所有节点,并返回一个广播变量引用。在任务中,通过引用对象的value属性访问此变量的值。创建广播变量后,请勿在worker中使用原始变量v |
- 累加器:支持关联函数的变量。主要特征是,在Worker任务中仅能写入累加器,而且只有驱动程序可以读取它们的值。因此累加器用于实现计数器或者操作允许”+=“和/或”add“运算的对象非常方便。
Signature(函数签名) | 描述 |
---|---|
accumulator[T](initialValue: T):Accumulator[T] | 返回T类型的累加器。任务可以直接引用它,并执行add和+=运算。只有驱动程序可以返回累加器value属性来读取它的值 |
accumulatorT:Accumulator[T] | 附加name参数让一个累加器可以在UI中显示。 |
accumulableCollectionR,T:Accumulable[R,T] | 返回一个累加器,用于一个R类型的Collection。R应该实施+=和++=运算。标准选择包括mutable.HashSet,mutable.ArrayBuffer和mutable.HashMap |
作业执行
SparkContext也负责将作业提交给调度器,这个过程是透明的。每次调用RDD操作时都会提交这些作业。一个作业分成几个阶段,然后分解成数个任务。这些任务随后又分发给各个worker。
RDD
Spark的核心为弹性分布式数据集(RDDs)。Spark应用程序中几乎所有的数据都存放在RDD中。
来自外部数据源或多个数据源的数据,首先被摄取并转换为RDD,接着将RDD转换成潜在的其他一系列RDD,最后再写入外部数据池或多个数据池。
RDD的本质是将数据分区(Partition)封装起来,犹如一个信封。每个RDD的持久化级别是可配置的;默认的行为是再失败时重新生成。保留血统信息——即RDD所依赖的父RDD——可以让RDD重新生成。RDD是Spark中最重要的部分:应用程序通过转换或执行RDDs才取得进展。RDD基础类提供有简单的转换方法(map, filter)等。RDD派生类在此基础上,通过扩展和实现三种主要方法来构建:compute(),getPartitions()和getDependencies()。
RDD特殊用法示例
RDD类型 | 描述 |
---|---|
CartesianRDD | 对两个RDD进行笛卡尔乘积操作而取得的结果 |
HadoopRDD | 表示存储在任何Hadoop兼容存储区中的数据:本地FS,HDFS,S3和HBase |
JdbcRDD | 包含通过一个JDBC连接执行一个SQL查询的结果 |
NewHadoopRDD | 与HadoopRDD相同,但使用新的Hadoop API |
ParallelCollectionRDD | 包含Scala Collections对象 |
PipedRDD | 将一个RDD的内容输送给一个外部命令,例如bash命令或Python脚本 |
UnionRDD | 将多个RDD封装,并将封装后的这些RDD视为单个RDD |
RDD也可以通过隐式转换进行转换
RDD转换类 | 描述 |
---|---|
DoubleRDDFunctions | 可应用于Double型RDD的函数。这些函数包括mean(),variance(),stdev()和histogram() |
OrderedRDDFunctions | 可应用于键值对RDD的排序函数。这些函数包括sortByKey()在内 |
PairRDDFunctions | 可应用于键值对RDD的函数,这些函数包括combineByKey(),aggregateByKey(),reduceByKey()和groupByKey()在内 |
SequenceFileRDDFunctions | 将键值对RDD转换为Hadoop SequenceFiles的函数。例如,saveAsSequenceFile() |
持久化
RDD持久化级别探讨了CPU,IO和内存开销之间的不同点。通过调用每个RDD提供的persist()方法来设置这个值。cache()方法默认为MEMORY_ONLY的持久化级别。持久化由BlockManager处理,BlockManager内部维护一个内存存储区和一个磁盘存储区。RDD也可应用checkPoint()函数来做检查点,与缓存不同的是,检查点直接将RDD保存到HDFS,并且不保存血统信息。
存储级别 | 描述 |
---|---|
NONE | 默认持久化级别,失败后,RDD重新生成 |
DISK_ONLY | RDD持久存储在磁盘上 |
DISK_ONLY_2 | 与前面相同,但是存储在两台机器上 |
MEMORY_ONLY | RDD以非序列化形式持久存储在内存中。内存空间不足时,分区需要重新计算 |
MEMORY_ONLY_2 | 与前面相同,但是在两台机器上 |
MEMORY_ONLY_SER | RDD以序列化的形式持久存储在内存中 |
MEMORY_ONLY_SER_2 | RDD以序列化的形式持久存储在两台机器的内存中 |
MEMORY_AND_DISK | RDD首先以非序列化格式持久存储在内存中,内存空间不足时,未存储在内存的分区被溢出到磁盘 |
MEMORY_AND_DISK_2 | 与前面相同,但是在两台机器上 |
MEMORY_AND_DISK_SER | 与前面相同,但分区以序列化存储 |
MEMORY_AND_DISK_SER_2 | 与前面相同,但是在两台机器上 |
OFF_HEAP | 将RDD转移到Alluxio,一个内存数据存储,这大大减少了Worker JVM的内存占用和频繁的GC问题。 |
转换
应用程序通过转换RDD才能取得进展,这些转换可分为四大类
- 映射:通过调用用户定义的函数,将输入RDD转换为输出RDD。大多数情况下,在数据点数量方面,输出RDD与输入RDD相比,前者数据点数量较小或相当。
- 变化算子可以增加或减少RDD中的分区数,或者执行RDD的裂变或聚变
- 键值转换:仅适用于具有键值对的RDD,调用返回二元组的map函数,通常会创建这些键值对。
- 混合算子:执行各种任务,例如对RDD中的元素抽样
转换(映射) | 描述 |
---|---|
mapU: RDD[U] | 函数式编程的标准map函数,调用用户提供的funtion作用于输入RDD中的每一个数据点,以创建相同长度的输出RDD |
flatMapU:RDD[U] | 与map相同,但是使RDD扁平化,因此输出的大小可能与输入RDD的大小不同 |
mapPartitionsU:RDD[U] | 调用传递的function作用于RDD中的每个分区。与之相对的,调用标准map()则是作用于每个数据点。提供的函数function应该能够以分区中数据点的迭代器作为输入。因为它减少了函数调用的次数,这比map()更有效率。 |
mapPartitionsWithIndexU:RDD[U] | 与mapPartitions()类似,但与迭代器一起,该函数也应该接受一个分区索引。如果逻辑需要跟踪正被处理的分区的索引,这是非常有用的 |
filterU:RDD[U] | 过滤掉符合特定条件的值。为此,提供的function需要返回与给定条件对应的布尔值 |
转换(变化) | 描述 |
---|---|
coalesce(numPartitions: Int, shuffle: Boolean): RDD[T] | 减少一个RDD中的分区数。在后台,将分区作为一个集体,有逻辑地合并分区。将shuffle设置为false,确保能够合并分区。而将shuffle设置为true时,会让使用哈希分区的shuffle对RDD进行强制的物理上的重新分区。这也意味着可以通过这种机制减少和增加分区的数量 |
repartition(numPartitions:Int):RDD[t] | 总是强制执行shuffle,以此来增加或减少RDD中的分区数。在后台,它调用coalesce(),并将shuffle设置为true。 |
union(other:RDD[T]):RDD[T] | 返回另一个RDD,是一个RDD与传递的RDD的并集。由UnionRDD具体实现返回的RDD |
intersection(other: RDD[T]): RDD[T] | 返回调用RDD和传递RDD的交集 |
转换(键值) | 描述 |
---|
转换(混合) | 描述 |
---|
Spark的作业是由行动开启执行的。每个行动要么是外部数据存储区的数据出口点,要么是驱动程序的入口点。在后台,没个行动调用SparkContext实例来调度其执行。与转换类似,这些行动因RDD类型而异。
行动 | 描述 |
---|
Spark是一个函数式的大数据处理框架,其中RDD是不可变的数据集合,可进行转换去实现应用程序逻辑。应用程序在大多数情况下是无边界效应的:行动的次数通常小于转换次数。而转换是非常懒惰的,仅在调用下游的行动时才触发转换。
RDD时构成Spark的夯实基石,而且RDD也充当容错,数据摄取和存储以及转换的单元。这些数据与转换、行动和少量其他原语相结合,可以用来整合几乎任何易并行应用程序。Spark让你能够专注于分析,同时由底层引擎处理所有分布式,最大限度的提高生产力。