spark简介

来源:互联网 发布:pano2vr mac 5.0.1 编辑:程序博客网 时间:2024/05/20 19:49

本文将围绕以下几个问题展开讨论:
1. 什么是spark
2. spark有什么特点
3. spark框架结构及工作流程




一、什么是spark
        Spark是UC Berkeley AMP lab所开源的类Hadoop MapReduce的通用的并行计算框架,Spark基于map reduce算法实现的分布式计算,拥有Hadoop MapReduce所具有的优点;但不同于MapReduce的是Job中间输出结果可以保存在内存中,从而不再需要读写HDFS,因此Spark能更好地适用于数据挖掘与机器学习等需要迭代的map reduce的算法。
         Spark 是在 Scala 语言中实现的,它将 Scala 用作其应用程序框架。与 Hadoop 不同,Spark 和 Scala 能够紧密集成,其中的 Scala 可以像操作本地集合对象一样轻松地操作分布式数据集。

        尽管创建 Spark 是为了支持分布式数据集上的迭代作业,但是实际上它是对 Hadoop 的补充,可以在 Hadoo 文件系统中并行运行。通过名为 Mesos 的第三方集群框架可以支持此行为。Spark 由加州大学伯克利分校 AMP 实验室 (Algorithms, Machines, and People Lab) 开发,可用来构建大型的、低延迟的数据分析应用程序。



二、spark有什么特点

spark具有以下特点:

  • 快:spark具有支持数据环流和内存计算的DAG执行引擎。Spark对小数据集能达到亚秒级的延迟,这对于Hadoop MapReduce(以下简称MapReduce)是无法想象的(由于“心跳”间隔机制,仅任务启动就有数秒的延迟)。就大数据集而言,对典型的迭代机器 学习、即席查询(ad-hoc query)、图计算等应用,Spark版本比基于MapReduce、Hive和Pregel的实现快上十倍到百倍。其中内存计算、数据本地性 (locality)和传输优化、调度优化等该居首功,也与设计伊始即秉持的轻量理念不无关系。
  • 小:spark原生语言Scala短小精悍,被誉为未来可能取代java的语言,其具有相当简洁而富有表达力的语句使得spark的核心代码比hadoop少得多(Spark 0.6核心代码有2万行,Hadoop 1.0为9万行,2.0为22万行)。此外,Spark很好地利用了Hadoop和Mesos(伯克利 另一个进入孵化器的项目,主攻集群的动态资源管理)的基础设施。虽然很轻,但在容错设计上不打折扣。主创人Matei声称:“不把错误当特例处理。”言下之意,容错是基础设施的一部分。
  • 灵:Spark提供了不同层面的灵活性。在实现层,它完美演绎了Scala trait动态混入(mixin)策略(如可更换的集群调度器、序列化库);在原语(Primitive)层,它允许扩展新的数据算子 (operator)、新的数据源(如HDFS之外支持DynamoDB)、新的language bindings(Java和Python);在范式(Paradigm)层,Spark支持内存计算、多迭代批量处理、即席查询、流处理和图计算等多种 范式。
  • 巧:巧在借势和借力。Spark借Hadoop之势,通过Mesos等实现与Hadoop无缝结合;接着Shark(Spark上的数据仓库实现)借了Hive的势;图计算借用Pregel和PowerGraph的API以及PowerGraph的点分割思想。一切的一切,都借助了Scala(被广泛誉为Java的未来取代者)之势:Spark编程的Look'n'Feel就是原汁原味的Scala,无论是语法还是API。在实现上,又能灵巧借力。为支持交互式编 程,Spark只需对Scala的Shell小做修改(相比之下,微软为支持JavaScript Console对MapReduce交互式编程,不仅要跨越Java和JavaScript的思维屏障,在实现上还要大动干戈)。


三、spark框架结构及工作流程


1. 计算范式及抽象
        Spark首先是一种粗粒度数据并行(data parallel)的计算范式。
        数据并行跟任务并行(task parallel)的区别体现在以下两方面。
  • 计算的主体是数据集合,而非个别数据。集合的长度视实现而定,如SIMD(单指令多数据)向量指令一般是4到64,GPU的SIMT(单指令多线程)一般 是32,SPMD(单程序多数据)可以更宽。Spark处理的是大数据,因此采用了粒度很粗的集合,叫做Resilient Distributed Datasets(RDD)。
  • 集合内的所有数据都经过同样的算子序列。数据并行可编程性好,易于获得高并行性(与数据规模相关,而非与程序逻辑的并行性相关),也易于高效地映射到底层 的并行或分布式硬件上。传统的array/vector编程语言、SSE/AVX intrinsics、CUDA/OpenCL、Ct(C++ for throughput),都属于此类。不同点在于,Spark的视野是整个集群,而非单个节点或并行处理器。

        数据并行的范式决定了 Spark无法完美支持细粒度、异步更新的操作。图计算就有此类操作,所以此时Spark不如GraphLab(一个大规模图计算框架);还有一些应用, 需要细粒度的日志更新和数据检查点,它也不如RAMCloud(斯坦福的内存存储和计算研究项目)和Percolator(Google增量计算技术)。 反过来,这也使Spark能够精心耕耘它擅长的应用领域,试图粗细通吃的Dryad(微软早期的大数据平台)反而不甚成功。
        Spark的RDD,采用了Scala集合类型的编程风格。它同样采用了函数式语义(functional semantics):一是闭包,二是RDD的不可修改性。逻辑上,每一个RDD算子都生成新的RDD,没有副作用,所以算子又被称为是确定性的;由于所 有算子都是幂等的,出现错误时只需把算子序列重新执行即可。
        Spark的计算抽象是数据流,而且是带有工作集(working set)的数据流。流处理是一种数据流模型,MapReduce也是,区别在于MapReduce需要在多次迭代中维护工作集。工作集的抽象很普遍,如多 迭代机器学习、交互式数据挖掘和图计算。为保证容错,MapReduce采用了稳定存储(如HDFS)来承载工作集,代价是速度慢。HaLoop采用循环 敏感的调度器,保证前次迭代的Reduce输出和本次迭代的Map输入数据集在同一台物理机上,这样可以减少网络开销,但无法避免磁盘I/O的瓶颈。
        Spark的突破在于,在保证容错的前提下,用内存来承载工作集。内存的存取速度快于磁盘多个数量级,从而可以极大提升性能。关键是实现容错,传统上有两种方法:日 志和检查点。考虑到检查点有数据冗余和网络通信的开销,Spark采用日志数据更新。细粒度的日志更新并不便宜,而且前面讲过,Spark也不擅长。 Spark记录的是粗粒度的RDD更新,这样开销可以忽略不计。鉴于Spark的函数式语义和幂等特性,通过重放日志更新来容错,也不会有副作用。


2. 编程模型
        来看一段代码:textFile算子从HDFS读取日志文件,返回“file”(RDD);filter算子筛出带“ERROR”的行,赋给 “errors”(新RDD);cache算子把它缓存下来以备未来使用;count算子返回“errors”的行数。RDD看起来与Scala集合类型 没有太大差别,但它们的数据和运行模型大相迥异。


        下图给出了RDD数据模型,并将上例中用到的四个算子映射到四种算子类型。Spark程序工作在两个空间中:Spark RDD空间和Scala原生数据空间。在原生数据空间里,数据表现为标量(scalar,即Scala基本类型,用橘色小方块表示)、集合类型(蓝色虚线 框)和持久存储(红色圆柱)。

 

图1 两个空间的切换,四类不同的RDD算子

输入算子(橘色箭头)将Scala集合类型或存储中的数据吸入RDD空间,转为RDD(蓝色实线框)。输入算子的输入大致有两类:一类针对Scala集合类型,如parallelize;另一类针对存储数据,如上例中的textFile。输入算子的输出就是Spark空间的RDD。

因为函数语义,RDD经过变换(transformation)算子(蓝色箭头)生成新的RDD。变换算子的输入和输出都是RDD。RDD会被划分成很多的分区 (partition)分布到集群的多个节点中,图1用蓝色小方块代表分区。注意,分区是个逻辑概念,变换前后的新旧分区在物理上可能是同一块内存或存 储。这是很重要的优化,以防止函数式不变性导致的内存需求无限扩张。有些RDD是计算的中间结果,其分区并不一定有相应的内存或存储与之对应,如果需要 (如以备未来使用),可以调用缓存算子(例子中的cache算子,灰色箭头表示)将分区物化(materialize)存下来(灰色方块)。

一部分变换算子视RDD的元素为简单元素,分为如下几类:

    - 输入输出一对一(element-wise)的算子,且结果RDD的分区结构不变,主要是map、flatMap(map后展平为一维RDD);
    - 输入输出一对一,但结果RDD的分区结构发生了变化,如union(两个RDD合为一个)、coalesce(分区减少);
    - 从输入中选择部分元素的算子,如filter、distinct(去除冗余元素)、subtract(本RDD有、它RDD无的元素留下来)和sample(采样)。

另一部分变换算子针对Key-Value集合,又分为:

    - 对单个RDD做element-wise运算,如mapValues(保持源RDD的分区方式,这与map不同);
    - 对单个RDD重排,如sort、partitionBy(实现一致性的分区划分,这个对数据本地性优化很重要,后面会讲);
    - 对单个RDD基于key进行重组和reduce,如groupByKey、reduceByKey;
    - 对两个RDD基于key进行join和重组,如join、cogroup。

后三类操作都涉及重排,称为shuffle类操作。

        从RDD到RDD的变换算子序列,一直在RDD空间发生。这里很重要的设计是lazy evaluation:计算并不实际发生,只是不断地记录到元数据。元数据的结构是DAG(有向无环图),其中每一个“顶点”是RDD(包括生产该RDD 的算子),从父RDD到子RDD有“边”,表示RDD间的依赖性。Spark给元数据DAG取了个很酷的名字,Lineage(世系)。这个 Lineage也是前面容错设计中所说的日志更新。

        Lineage一直增长,直到遇上行动(action)算子(图1中的绿色箭头),这时 就要evaluate了,把刚才累积的所有算子一次性执行。行动算子的输入是RDD(以及该RDD在Lineage上依赖的所有RDD),输出是执行后生 成的原生数据,可能是Scala标量、集合类型的数据或存储。当一个算子的输出是上述类型时,该算子必然是行动算子,其效果则是从RDD空间返回原生数据 空间。

        行动算子有如下几类:生成标量,如count(返回RDD中元素的个数)、reduce、fold/aggregate(见 Scala同名算子文档);返回几个标量,如take(返回前几个元素);生成Scala集合类型,如collect(把RDD中的所有元素倒入 Scala集合类型)、lookup(查找对应key的所有值);写入存储,如与前文textFile对应的saveAsText-File。还有一个检 查点算子checkpoint。当Lineage特别长时(这在图计算中时常发生),出错时重新执行整个序列要很长时间,可以主动调用 checkpoint把当前数据写入稳定存储,作为检查点。

        这里有两个设计要点。首先是lazy evaluation。熟悉编译的都知道,编译器能看到的scope越大,优化的机会就越多。Spark虽然没有编译,但调度器实际上对DAG做了线性复 杂度的优化。尤其是当Spark上面有多种计算范式混合时,调度器可以打破不同范式代码的边界进行全局调度和优化。下面的例子中把Shark的SQL代码 和Spark的机器学习代码混在了一起。各部分代码翻译到底层RDD后,融合成一个大的DAG,这样可以获得更多的全局优化机会。

        另一个要点是一旦行动算子产生原生数据,就必须退出RDD空间。因为目前Spark只能够跟踪RDD的计算,原生数据的计算对它来说是不可见的(除非以后 Spark会提供原生数据类型操作的重载、wrapper或implicit conversion)。这部分不可见的代码可能引入前后RDD之间的依赖,如下面的代码:

        第三行filter对errors.count()的依赖是由(cnt-1)这个原生数据运算产生的,但调度器看不到这个运算,那就会出问题了。

        由于Spark并不提供控制流,在计算逻辑需要条件分支时,也必须回退到Scala的空间。由于Scala语言对自定义控制流的支持很强,不排除未来Spark也会支持。

        Spark 还有两个很实用的功能。一个是广播(broadcast)变量。有些数据,如lookup表,可能会在多个作业间反复用到;这些数据比RDD要小得多,不 宜像RDD那样在节点之间划分。解决之道是提供一个新的语言结构——广播变量,来修饰此类数据。Spark运行时把广播变量修饰的内容发到各个节点,并保 存下来,未来再用时无需再送。相比Hadoop的distributed cache,广播内容可以跨作业共享。Spark提交者Mosharaf师从P2P的老法师Ion Stoica,采用了BitTorrent(没错,就是下载电影的那个BT)的简化实现。有兴趣的读者可以参考SIGCOMM'11的论文 Orchestra。另一个功能是Accumulator(源于MapReduce的counter):允许Spark代码中加入一些全局变量做 bookkeeping,如记录当前的运行指标。

3.运行spark示
        运行 SparkPi 示例,它会计算 pi 的估值(通过单位平方中的任意点采样)。所显示的格式需要样例程序 (spark.examples.SparkPi) 和主机参数,该参数定义了 Mesos 主机(在此例中,是您的本地主机,因为它是一个单节点集群)和要使用的线程数量。注意,在 清单 7 中,执行了两个任务,而且这两个任务被序列化(任务 0 开始和结束之后,任务 1 再开始)。
$ bin/run-example SparkPi 4

0 0
原创粉丝点击