Spark :工作组上的集群计算的框架

Spark :工作组上的集群计算的框架

Spark: Cluster Computing with Working Sets

Matei Zaharia, Mosharaf Chowdhury, Michael J.Franklin, Scott Shenker, Ion Stoica

University of California, Berkeley


翻译:Esri 卢萌

本文翻译自加州伯克利大学AMP lab的Matei大神发表的关于Spark框架的第一篇论文,限于本人英文水平很烂,所以翻译中肯定有很多错误,请发现了错误的直接与我联系,感谢。

(括号中,斜体字部分是我自己做的解释)

摘要:

       MapReduce以及其的各种变种,在商业集群上进行的对大规模密集型数据集的应用上已经取得了很大的成功。然而大多数这类系统都是围绕着一个非迭代型的数据流模型,这种模型不适用于目前很多主流的应用程序。本文的研究侧重于介绍其中这样一类应用:重复使用跨多个并行操作的数据的工作流集合。这一类应用,包括了各种机器学习算法以及交互性数据分析工具。我们提出了一个名为“spark”的新框架,用以支持这类应用,同时保留了MapReduce的可扩展性和容错性。为了实现这些目标,Spark将引入所谓的弹性分布式数据集(resilient distributeddatasets (RDDs))这一概念。所谓的RDD,指的是一个只读的,可分区的分布式数据集,这个数据集合如果丢失,也可以通过其他副本对丢失分区进行重建。Spark在迭代式机器学习作业上,可以超过hadoop 10倍以上的速度,并且可以实现对39GB的数据集以亚秒级的响应时间进行交互式查询。 

1. 引言

        集群计算作为一种新的模式已经广为流传,可以在由不可靠的机器组成的集群上进行并行计算,并且能够自动提供局部性调度,容错和负载均衡。谷歌率先推出了MapReduce这种模型,而类似Dryad系统(微软的一种分布式计算框架)和Map-reduce-Merge框架(雅虎的MapReduce开源实现)在广义上支持了这种模型的数据流(data flow:这里指的是MapReduce的非循环数据流向模式,以下名词中,数据流的解释都相同)。这些系统实现了其的可扩展性和容错性,提供了一套编程模型,让用户方便的创建非循环的数据处理流程,来对输入数据的进行操作。并且允许底层系统来管理任务的调度,且无需用户干预。

         该类型数据流编程模型在很大一类应用中是很有效的,但是不能有效的处理那些非周期性的数据流的应用。在本文中,我们专注其中的这样一类应用:那些需要重复使用,并且跨多个任务节点和数据集进行并行操作的工作。在此类应用中,我可以看到hadoop的诸多用户以及学术界产业界各种报告所指出的MapReduce框架本身无法很好实现的两个例子: 

       A、迭代作业:许多常见的机器学习算法需要应用一个方法重复的在同一数据集上进行参数迭代优化工作(例如:梯度下降算法)。而在MapReduce或者Dryad中,每一次迭代都会被表示为一个新的任务,而每一个任务都必须重新从磁盘上加载数据,导致了非常明显的性能损失。

        B、交互式分析:Hadoop经常通过SQL接口,如hive或者pig,在超大的数据集上执行即席探索性数据查询,理想的情况下,是用户将感兴趣的数据集加载到多台计算机的内存中,并反复查询。然而在hadoop中,每一个查询都会被视为一个独立的MapReduce作业,去从磁盘中读取数据,所以都会被显著的延迟(起码几秒钟)。

        本文提出了一种名叫:“Spark”的集群计算框架,此框架支持应用程序处理并行作业,同时像MapReduce一样提供了高可伸缩性和容错性。

        在Spark中设计实现了一种称之为:弹性分布式数据集(RDD)的概念,它表示了一种只读的数据集合,这组数据集合在一组在存储在机器中的分区数据丢失之后,可以用来进行分区重建。用户可以通过显式的定义在内存中缓存一个RDD,这个RDD可以像在MapReduce中处理数据一样的并行操作,还跨多个工作节点进行重用。RDD沿袭了传统的容错概念:如果存储RDD的设备的分区数据丢失,丢失的RDD能够从其他的RDDS上推导出足够重建这个分区的各种信息。RDDS采用的不是传统的共享内存的方式,它们代表了一种新的概念,主要体现它们在可靠性和可扩展性中间找到了最佳平衡点,所以我们发现它们可以适用于许多的应用场景。

        Spark是采用Scala语言进行集成开发的,这是一种静态类型的高级语言,通过JAVA虚拟机运行,并且开放了函数式的编程接口,还提供了类似于DryadLINQ(微软的大数据并行处理框架中的一部分,类似于hive)这样的命令脚本执行方式。此外,Spark还可以使用交互式命令行的执行方式,这是使用一种改进了的Scala解释器,它允许用户通过脚本方式自定义RDDS、函数、变量和类,并利用他们并行操作集群。我们相信,Spark是第一个允许使用一个高效、通用的交互式编程语言来对集群上的大型数据集进行处理的系统。 

       虽然我们目前实现的Spark只是一个原型系统,但是该系统前期的表现令人鼓舞。我们在实验表明,Spark在迭代式机器学习算法上有优于hadoop近10倍的工作能力,并且可以在交互式查询中,对39GB数据集的全文扫描中达到亚秒级的响应速度。

        本论文的组织结构如下:第二节介绍了Spark的编程模型和RDDS。第三节给出了一些示例,第四节描述是我们如何实现这个系统,包括了如何集成到Scala环境及其解释器中。第五节提出了前期研究的结果和调查的相关工作,第六节和第七节讨论了结论和未来的发展。 

2. 编程模型

        使用Spark,需要开发人员编写、实现其应用的高层控制流程以及并行操作的驱动程序。Spark提供了并行运算编程的两个主要的架构:在数据集上创建弹性分布式数据集和并行运算操作(通过传递函数的方式对数据集进行调用)。此外,Spark可以用函数的方式在集群上支持两种受限制的共享变量类型的运行,我们将在后面的文章中进行解释。

2.1.弹性分布式数据集(RDDS)        

       弹性分布是数据集(RDD)是指在一组存储计算机中的只读数据集合,这个数据集合可以在分区对象丢失后进行重建。RDD的元素不一定需要存储在物理介质中,相反,一个RDD的处理进程包含了如何从可靠的数据存储中去获取足够的信息来对这个RDD进行处理。这意味着,如果RDDS的任务节点失败,总可以进行重建。 

       在Spark中,每个RDD都由Scala对象来表示,Spark可以让程序员从以下四个方面来构建RDDS:

        1. 从共享文件系统中获取,如可以从Hadoop 的分布式文件系统(HDFS)中读取数据来构建RDDS。

        2. 通过驱动程序中的用于并行计算的Scala集合进行创建(例如:在驱动程序中构建的一个array对象)。这意味着这个集合将被划分成若干片段发送到多个节点上。

        3. 通过转换现有的RDDS。如有一个类型为Atype的数据集Adata,我们就可以通过一个名为flatMap的操作,可以将每个要素转换为Btype类型,通过用户定义的函数进行如下转换:Atype-> List(Btype)。使用flatMap还可以进行其他转换,包括映射(通过类似Atype –> Btype这样的函数)以及过滤(要素谓词匹配)。

        4.  通过进行持久化来改变现有的RDD。默认情况下,RDDS都是具有延时性(延时性指对象初始化的时候是不进行填充内容的,只有当调用的时候,才去进行数据填充)和暂时性的。也就是说,数据集分区一般是在并行操作的时候才进行实例化,(如通过Map函数来传递一个文件块),使用完之后就会从内存中丢弃。但是用户可以通过以下两种方式来对RDD进行持久化:

        Ø  放到缓存中的数据集将进行延时处理,但会暗示它在第一次计算后,应该被保存在在内存中,而不是丢弃,因为它将被重用。

        Ø 将对操作处理过的数据集进行验证,并且将其写入到一个分布式的文件系统中(如HDFS等)。采取版本的方式进行保存,以便未来进行操作。

       我们注意到,缓存的作用仅仅是作为一个提示:如果没有足够的内存来缓存集群中所有分区的数据集,那么Spark将暂时不填充这些数据集,只在计算的时候才使用它们。我们选择这种设计,是为了如果节点出现故障或者数据集太大,能够使Spark不中断工作(性能降低时),这种想法类似于松散的虚拟存储器。

       我们还计划扩展Spark对其他级别的持久化的支持(例如在内存中复制到多个节点)。我们的目标是让用户能够在存储在RDD中、快速访问,或者在一定的概率下失去部分数据,以及在重新计算它几者之间进行成本的权衡。

2.2. 并行处理

       以下几种并行操作可以在RDDS上实现:

        1、聚合:在驱动程序中使用相关函数对数据集进行聚合。

        2、收集:发送该数据集的所有元素到驱动程序中。

        3、流程:例如,一种用来更新并行数据的简单方法,就是并行化映射和收集整个数组。

        4、遍历(foreach):通过用户自定义的函数遍历每个元素。只是这种函数的副作用很明显(这可能是用来将数据复制到另外一个系统中的功能,如下面所解释的用来更新的共享变量)。(注意:这里的foreach的副作用,应该是说foreach只能用来遍历,不能用来对集合进行修改否则会发生很多奇怪的情况)

        注意,Spark目前还不支持MapReduce中的分组聚合操作,我们在一个驱动进程中,只能收集一个reduce结果。我们计划在未来支持分组reduce以减少在分布式数据集中进行“shuffle”变换。在第七节中会进行说明。然而,即使使用单一的reduce操作就足以满足多种实用的算法了。例如在MapRedcue最近的一篇关于在多核系统上进行机器学习算法的实现中,指出了起码有10种算法不支持并行化,但是在Spark上能够解决其中的绝大部分问题。

2.3. 共享变量

       程序员可以在Spark中通过传递闭包(闭包是自包含的功能代码块,可以在代码中使用或者用来作为参数传值。)调用如映射、过滤和聚合等一类的操作。作为典型的函数式编程方式,这些闭包可以按照参考变量的作用域在任何地方进行创建。通常情况下,当Spark的工作节点运行闭包的时候,这些变量被复制到工作空间中。然而Spark也可以让程序员创建两个受限的共享变量来支持以下两种简单而常见的使用场景:      

       广播变量:如果有一个很大的只读数据片段(如一个查询表),需要用于多个并行的操作,最好是把它一次分发给每个工作空间,而不是每一个闭包里面都去做一个封装。

       累加器:用作累加器的变量,工作空间只能对它进行“add”及其相关操作,并且只有驱动程序可以访问它。他们可以被用来提供在MapReduce上以并行计算的方式实现计数器的累加功能。累加器可被定义为任何类型,有一个“add”的操作以及一个“0”值。由于其“只加”的操作,所以有很好的容错性。

3. 示例

        我们展示一些作为示例的Spark程序,请注意,我们忽略了预先声明变量的类型,因为Scala使用的是类型推导的方式,但是Scala是属于静态类型的语言,执行方式等同于JAVA。

3.1.    文本查询

       假设在HDFS上面保存有一批超大数量日志文件,我们需要查找其中的表示错误的行。那么可以通过以下方式,一般从创建一个文件数据集对象的方式开始。

val file = spark.textFile("hdfs://...")

val errs = file.filter(_.contains("ERROR"))

val ones = errs.map(_ => 1)

val count = ones.reduce(_+_)

       我们首先用HDFS中的文件创建一个分布式数据集,作为一个行的集合。接着用这个数据集来创建包含“error”的行的集合(errs),然后把每一行都映射为1(找到一行,就计数为1),最后采用聚合函数将这些数据累加起来。其中filter、map和reduce都是Scala的功能函数名称。

       需要注意的是,因为RDDS是延时执行的,所errs和ones在初始化的时候没有进行实现。相反的是,当reduce被调用的时候,每个工作节点都去扫描输入模块,以流的方式来读取数据,并将计数累加到驱动程序。通过这种延时处理数据集方式,让Spark对MapReduce进行精确模拟。

       但是Spark不同于其他框架的是,他可以让一些中间数据集实现持续的跨越性操作。例如,如果我们想重新使用errs数据集,只需要采用如下语句来从缓存中创建RDD即可:

val cachedErrs = errs.cache()

       做完这一步之后,我们就可以从缓存中调用errs了,就像调用其他的数据集一样对他进行并行操作。但是errs的内容是在我们第一次计算后的缓存在内存中的结果,所以调用该缓存之后会大大加快后续的操作。

3.2. 逻辑回归

       下面的程序实现了逻辑回归,通过迭代分类算法,试图找到一个超平面w把两个点集合分隔开。

       该算法采用梯度下降法,开始给w赋一个随机值,并在每次迭代中对w的结果进行总结,以移动w的方向对结果进行优化。这里不解释逻辑回归的细节,但是我们可用它来展现一些Spark的特性。因为在Spark上实现此算法受益于能够重复迭代内存中的数据。    

// Read points from a text file and cache them

val points = spark.textFile(...).map(parsePoint).cache()

// Initialize w to random D-dimensional vector

var w = Vector.random(D)

// Run multiple iterations to update w

for (i <- 1 to ITERATIONS) {

val grad = spark.accumulator(new Vector(D))

for (p <- points) { // Runs in parallel

val s = (1/(1+exp(-p.y*(w dot p.x)))-1)*p.y

grad += s*p.x

}

w -= grad.value

}

       首先创建一个名为points的RDD节点,我们通过运行一个循环来处理它。For关键字是Scala里面用于表示调用循环的语法,里面的循环体类似于foreach方法。也就是说,对于代码for(p <- points){body}等同于points.foreach(p =>{body})(这种写法是Scala特有的,将所有的操作都当成对象的方法来调用),在此,我们调用了Spark的并行foreach操作。

       其次,我们定义了一个名为grad的梯度累加器(类型为Vector)。需要注意的是,在循环累加中使用一个重载了的操作符+=,这种在for循环中使用累加的语法,看起来很像是串行操作的程序。实际上,这个例子不同于传统的只有三行代码串行执行的逻辑回归的版本。

3.3. 交替最小二乘法

       我们最后一个例子是使用所谓的交替最小二乘法(ALS)的算法。ALS用于处理协同过滤的问题,例如我们要通过用户对电影观看历史和评分来预测他们喜欢的电影(如在Netfix挑战赛中的例子)。不像前面的例子,ALS的算法是CPU密集型的,而不是数据密集型的。

       我们简要的描述一下ALS算法供读者参考。假设我们需要预测用户u对电影m的评分,而我们已经有了很多以往用户对电影的观看数据矩阵R。ALS模型R是两个矩阵M和U的运算结果,M和U的尺寸分别是 M * U 和 K * U。也就是说,每个用户和影片都有一个K维的“特征向量”,描述了它的特点和用户给予它的评价,该特征向量就是用户评级和电影的特点的内积。ALS解决了使用已知的观看评价的M和U,然后计算M*U矩阵的未知值的预测算法。以下使用迭代过程来实现:

        1、使用随机值初始化M

        2、计算优化U给定M的预测模型R,最大限度的减少错误。

        3、计算优化M给定U的预测模型R,最大限度的减少错误。

        4、重复2、3两步,直到收敛。

        ALS可以在通过在每个节点上并行运行步骤2和步骤3来更新不同用户\电影的信息。然而,所有的步骤都有使用模型矩阵R,所以我们可以将R变成广播变量,这样做是很有效的。这样它就不会在各个节点的所有操作步骤中,都要求被重新发送到每个节点上。通过Spark来实现ALS,如下图所示。请注意,我们通过parallelize方法,获取了0 until U(until是Scala的范围处理方法)以及collect方法(用于把RDD中的所有元素倒入 Scala集合类型),用来更新每个数组。

val Rb = spark.broadcast(R)

for (i <- 1 to ITERATIONS) {

U = spark.parallelize(0 until u)

.map(j => updateUser(j, Rb, M))

.collect()

M = spark.parallelize(0 until m)

.map(j => updateUser(j, Rb, U))

.collect()

}

4.实现

       Spark是建立在Mesos上的,所以它是一个“集群操作系统”,可以让多个并行应用程序以细粒度的方式共享集群资源,并且提供了一个可以在集群上启动应用程序任务的API。这使得Spark可以运行在任何现有的集群计算框架上,如hadoop的Mesos接口以及MPI,并且能够与他们共享数据存储。此外,把Spark建立在Mesos上,还大大降低了编程的难度,让大家更加容易的使用Spark。

       Spark的核心是弹性分布是数据集。如下示例,假设我们定义一个缓存数据集CachedErrs来代表日志中的错误信息,而我们使用的map和reduce内容,如3.1节中所示: 

val file = spark.textFile("hdfs://...")

val errs = file.filter(_.contains("ERROR"))

val cachedErrs = errs.cache()

val ones = cachedErrs.map(_ => 1)

val count = ones.reduce(_+_)

       这些数据集被存储为一个对象链,用以连接每个RDD,如图1。每个数据对象包含i个指向其父对象及其相关信息以及父对象到自身的转换信息。

Spark :工作组上的集群计算的框架_第1张图片

        在内部,每个RDD对象实现了三个相同的简要接口,包括如下三个操作:

        Ø  getPartitions,返回数据分块ID的列表。

        Ø  getIterator(partition),迭代一个数据分块

        Ø  getPreferredLocations(partition),用来进行任务调度,以实现数据局部特性。

        当数据集被调用进行并行操作,Spark创建一个任务,并将这些任务分发到每个节点处理数据集的每一个数据分块。我们设法把每个任务都发送到其首选的位置(最优位置),这种技术称之为“延迟调度”(delay scheduling)。一旦进行一个工作集作业,那么每个任务都需要用getIterator方法来对数据分块进行读取。

        不同类型的RDD是如何实现的呢?不同的RDD之间只是接口不同而已。例如对于一个HdfsTextFile,该数据分块就是HDFS块上的ID,首选的位置就是block的位置。getIterator打开一个数据流用以读取block。在映射数据集中,所述的数据分块和首选的位置有相同的父对象,而map函数迭代每一个父对象的元素。最后,如果是CachedDataset,会通过getIterator方法会查找数据分块转换后的本地缓存的副本,并且每个数据分块的首选位置的起始点等于其父对象的首选位置,但是更新后,该数据分块会被缓存到一些倾向于需要重用的节点上。这种设计使故障更容易被处理:如果一个节点发生故障时,就从他们的父对象数据集重新读取数据分块,并最终缓存到其他节点上即可。

        最后,数据传递任务的工作作业要求发送闭包给他们——这个闭包既可以用来定义一个分布式数据集,也可以使用来操作reduce。为了实现这一目标, Scala可以通过闭包对java对象进行序列化。这也是Scala的特性,可以让它相对简单的将各种计算处理的过程直接发送到另外一台机器上。

        Scala内建的闭包实现的封装并不理想,因为我们已经发现了闭包对象的在其范围之外被引用,而非直接作用在它本身。我们已经提交了这个bug的报告,但是在修正之前,我们需要通过对闭包类的字节码进行静态分析来检查这些无效的变量,并且在闭包内将相应的对象设置为null。

        共享变量:Spark中有两种类型的共享变量,分别是广播变量和累加器。可以使用自定义序列化的格式类来实现。如果一个人创建了一个广播变量b,并且赋值为v,而v是保存在一个共享文件系统中的文件。那么b的序列化格式就是一个表示这个文件的路径。当b的值在工作节点上被查询时,Spark首先检查v是否在本地的缓存中,如果不是,就到路径中指向的文件系统中去读取它。我们最初使用的是HDFS的来作为广播变量,但是现在我们正在开发更有效的流式全局广播系统。

        累加器使用的是实现不同的“序列化的戏法”。每个累加器被创建的时候都被赋予一个唯一ID。当累加器被保存时,他的序列化形式包含了其的ID和“0”值两种类型。在工作节点上,累加器是使用每个线程创建的一个独立副本,使用线程局部变量的运行任务,并且在任务开始的时候,将值重置为0。

        对解释器的集成:由于论文篇幅的问题,我们只简单描述我们如何将Spark整合到Scala的解释器上。Scala的解释器的操作过程一般是被用户每键入一行就编译一行的命令行执行方式。下面这个例子中包含了一个编写在一行代码中的变量或者函数,我们在一行中使用代码来构造一个单独的对象,如果用户定义一个对象:

                 var x = 5

然后紧接着

                 println(x)

解释器定义这样一个类:

                 class(参数:line1)

这里line1就是含有x的第一行的代码,并且在第二行编译为

                 println(line1.getInstance().x)

这些类的每一行都会被载入到JVM并且运行。为了让Spark在解释器进行工作,我们做了以下两个变化:

        1、我们做了一个定义为一个共享文件系统的解释器输出类。从中可以让用户通过自定义的java类加载器加载相应的工作作业集。

         2、我们改变了代码的生成方式,使每行的单一对象都能直接引用之前的单一对象,而不是通过getInstance这样的静态方法。这就使得闭包在获得他们时,能够引用他们的当前状态,并且这些内容将被序列化后发送到一个工作作业集中。如果我们不这样做,随后若有一个单一对象(如:上面例子中,在一行代码中定义x=7)被更新了,就也不会传递到工作作业集中。

5.结论

        虽然Spark还处于初创阶段,但是我们通过以上三个示例证明了Spark作为一个集群计算框架是有很大发展潜力的。

        逻辑回归:我们在3.2中的示例,使用spark实现逻辑回归算法,然后我们在亚马逊EC2节点上,采用一个4核处理器,处理29GB的数据,设置20个m1.xlarge(一个m1.xlarge表示拥有15GB内存,八个EC2计算单位(四个虚拟核,每个核两个 EC2 计算单位),1.69TB实例存储,64 位平台,高I/O性能平台。),进行逻辑回归计算。根据图2所示,在hadoop上每次迭代任务耗时127秒,因为每个MapReduce任务都是独立运行。而在Spark中,第一次迭代用了174秒(可能是因为使用了Scala来代替java),但是以后每次迭代都只需要6秒,因为每个缓存中的数据多可以服用,这使得运行速度加快了10倍以上。

Spark :工作组上的集群计算的框架_第2张图片

         我们还尝试了对节点进行破坏的测试。在10次迭代测试的情况下,这让工作平均减慢了50s(21%)。数据分块的丢失,使得该节点的计算和缓存转移恢复到了其他的平行节点上,但是恢复所用的时间比较长,这是因为我们使用的HDFS采用了较大的block(128M)设置,所以每个节点分块只有12块,在恢复的过程中可能没有使用到集群中所有内核。较小的block尺寸会产生更快的恢复速度。

       交替最小二乘法:我们在3.3节实现了交替最小二乘法,我们对将共享数据集通过广播变量拷贝到各个节点上的效率继续了测量。我们发现,不使用广播变量的时候,耗时最长的操作是以迭代重新发送的评分矩阵R为主的作业。此外,一个简单的广播实现(如使用HDFS或者NAS),广播的时间随着节点的数据呈线性增长,这样限制了作业的可扩展性。所以我们采用了一个应用层的多播系统来解决这个问题。然后即使采用快速广播,在每次迭代中重新发送R开销也非常大。缓存在内存中的R使用广播变量,在5000部电影以及15000个用户的数据量下,在30个节点的ec2集群工作集中,使性能提供了2.8倍。

       Spark交互式查询:我们使用Spark解释器加载了39GB的维基百科数据,大约跨越了15台m1.xlarge 的EC2节点进行交互式查询。第一次查询,大约需要35秒,几乎和在hadoop上工作差不多了。而且随后的查询,只需要0.5到1秒就可以完成了,即便是要求它们扫描整个数据集。这是一个质的飞跃,体验之佳,堪比在本地数据集上工作。

6. 一些相关的工作

          分布式存储器:Spark的弹性分布式数据集可以被看做一个抽象的分布式共享存储器(DSM),这种概念已被广泛的研究。RDDS是由DSM的两种不同的接口方式发展而来。首先,RDDS提供了更加严格的编程模型,如果发生了集群节点故障,能够有效的重构相关的数据集。虽然一些DSM系统,可以通过检查点的方式来实现容错,但是Spark通过使用RDD对象获取其沿袭的父对象的信息来对丢失的RDD数据分块进行重建。这意味着,仅仅是丢失的数据分块需要被重新计算,并且他们可以在不同的节点上并行重新计算,而不需要将程序恢复到检查点。此外,如果没有节点失败,就不存在额外开销。第二,RDDS将数据推送到MapReduce进行计算,而不是让任意的节点都去访问全局地址空间。

            虽然还有其他的系统也提供了DSM编程模型,但是在性能、可靠性和可编程上都有很多的限制。Munin(Munin也是一个DSM系统,允许共享内存,并且在多处理器上执行并行程序的系统)要求程序员们使用的变量与访问模式,都要选一个一致性的协议。而Linda(一种分布式编程的语言,主要以网络计算机为基础)要求,使用元组空间的编程模型来实现容错。Thor(一种面向对象的数据库存储系统,设计为在一个异构的分布式环境下使用,提供了高度可靠且高可用的持久存储对象,并支持安全的共享这些对象)是通过提供了一个接口来持久化共享对象。

           集群计算框架:Spark的并行操作继承了MapRedcue模型。然而它使用RDDS,可用实现跨工作作业持续执行操作。

           Twister也扩展了MapReduce对迭代操作的支持,它使MapReduce能够保存长期map任务,用以使工作之间能在存储中保存静态数据。然而Twister目前不支持容错。Spark的弹性分布式数据的概念既有容错,又有可以使MapReduce拥有迭代的能力。Spark程序可以在其上定义和运行多个RDD和候补应用,而Twister只有一个map函数和一个reduce函数。这也使得Spark在交互式数据分析应用中,可以让用户定义多个数据集,然后对他们进行查询。

            Spark的广播变量提供了类似hadoop的分布式缓存,可以分发文件到运行特定作业的所有节点。然而,广播变量可以在并行操作中重复使用。

            语言集成:Spark的语言集成类似于DryadLINQ,他可以使用.NET支持的语言集成查询,通过表达式定义查询以获取树形结构的目录列表,并且可以在集群上运行。但是不同于DryadLINQ,Spark允许RDD在内存中跨越并行作业。此外,Spark通过支持共享变量(广播变量和累加器),丰富了语言的集成模型,并且可以用类的方式进行自定义序列化。

           我们被启发,在Scala语言中融合了SMR(Scala MapReduce),hadoop可以使用Scala的接口使用闭包定义map和reduce任务。我们的贡献使SMR能够通过共享变量和通过闭包执行一个更强大的序列化的功能。

           最后,虽然IPython是一个为科学工作者推出的,可以在计算集群上使用容错任务队列接口的python解释器。但是Spark也推出了类似的人机交互界面,而我们认为,侧重于数据密集型计算的应用更需要采用更高效的编程语言(如Scala)。

           追根溯源:寻找数据的沿袭或者出处一直是科学计算和数据库领域中的一个重要研究课题。如一个应用的结果特地说明,允许他们被其他应用转载并且重新计算,那么对于这个应用程序的结果,如果有一个错误是在工作流的步骤中或者数据丢失中被发现了,那么要处理的话,我们建议读者参考《Lineage retrieval forscientific data processing: a survey》、《A survey of dataprovenance in e-science》、《Map-reduce for machine learning onmulticore.》等论文。Spark提供一个严格的并行程序设计模型,其中提供了一个简单的方式对数据的细粒度的追根溯源和获取,这使得该信息可以用来被重建丢失的数据集中的元素。

7. 未来的工作与探讨

          Spark提供了三个简单的用于集群计算编程的抽象数据模型:弹性分布式数据集(RDDS),广播变量和累加器。虽然这些抽象数据模型还很有限,但是我们发现他们与现有的集群计算框架相比,有足够强大的优势,包括迭代和交互计算都可以挑战很多应用。此外,我们认为,RDDS背后的核心思想,是从可靠的存储设备中获取足够多的信息来对丢失的数据分块进行重建,并且可以证明在开发其他的抽象编程集群也是有用的。



你可能感兴趣的:(mapreduce,scala,spark,大数据,并行计算)