本文是阅读《Resilient Distributed Datasets: A Fault-Tolerant Abstraction for In-Memory Cluster Computing》过程中,抽了局部一些关注点翻译出来的文章,没有将全文都翻译。希望这些碎片化甚至不通顺的记录,可以帮助读者取代阅读原论文。
论文地址http://www.cs.berkeley.edu/~matei/papers/2012/nsdi_spark.pdf
第一节
主要介绍了现有的集群计算框架存在的问题,然后介绍了RDD的几个设计亮点,对比了Spark的不同之处和优势点
解决现存迭代式算法和交互式数据挖掘俩问题 iterative algorithms and interactive data mining tools.
严格的内存分享,粗粒度而非细粒度 based on coarsegrained transformations rather than fine-grained updates to shared state
Dryad 和 MR 是现有的集群计算框架 他们能分布式 能容错 但是没能利用好分布式内存
导致他们在多次利用中间数据的计算上不够效率
有很多迭代式机器学习和图算法 PageRank K-means LR
还有一点 就是帮助交互式数据挖掘
现有的解决计算之间(如两个MR job下面)重用数据的方法 是存到一个外部分布式文件系统里
这带来了很大的间接消耗在 数据备份,磁盘IO,以及序列化 而这些主宰了执行时间
已经出现的针对这一问题的特殊框架:Pregel 迭代式图计算 把中间数据存内存 HaLoop提供迭代MR接口
但他们只针对一部分特殊计算组件,对数据分享做的很含蓄,没有提供更抽象,通用的重用
RDD的一个原文定义:
RDDs are fault-tolerant, parallel data structures that let users explicitly persist intermediate results in memory, control their partitioning to optimize data placement, and manipulate them using a rich set of operators.
第二节
介绍RDD
RDD是只读,分区记录的集合
transformations:从现有的存储文件生成RDD或由别的RDD生成新RDD
RDD不用一直物化,他充分知道自己是怎么被计算出来的,他的lineage血缘
persistence and partitioning 是RDD另两个方面
持久化指用户可以指定他们要哪个RDD,存储策略是什么
分区指用户可以决定key来让RDD分区在机器之间,这对于 位置优化(placement optimizations)很有用
Spark编程API,对RDD进行的actions,如:count(),collect(),save()
且是懒式的,所以可以管道式变换 (pipeline transformations)
用户可以持久化RDD,Spark默认是放内存里,RAM不够大就放磁盘上
用户可以指定持久化策略,比如只存硬盘上或在不同机器上复制,设置flag,最后用户可以设定优先级,哪个内存里的数据先放到硬盘上
RDD与分布式内存(DSM)的对比:
DSM是一个很通用的抽象,他的普遍性导致了比较难实现容错和高效
RDD不适合大块的写,但是更高效和容错,且没有检查的开销,因为可以借助血缘恢复,且丢失的分区在重新计算的时候,也是在不同节点上并行的,比回滚整个程序快很多
对于大块数据的操作,RDD会调度到本地数据执行,提高效率。数据太大就存硬盘。
RDD不支持的场景:
RDD适合批处理,具体是对数据集里所有元素进行同一个操作,这样他执行转换和恢复就很快,原因是不记录大量log
不适合异步细粒度更新,比如web引用的存储和增量web爬虫
第三节
Spark编程接口
选用Scala是因为他的简明(交互式)和高效(静态类型),而RDD并没有函数式编程的需求
RDD是静态类型对象
join适合key-value的RDD
拿逻辑回归里的梯度下降和PageRank,介绍了下迭代式计算的方式和RDD上的写法
第四节
RDD的表示
如何表示RDD之间的依赖:窄依赖,一个父RDD最多被一个子RDD用(map);宽依赖,多个子RDD(join)
窄依赖:在一个集群节点上管道式执行,计算所有父分区。如map->filter;错误恢复很高效
宽依赖:要求所有父分区可用,在所有节点上进行类mapreduce操作
第五节
实现
每个Spark程序在Mesos上是一个独立的应用,有自己的驱动,worker以及Mesos分配的共享资源
下面讲几个实现中的有趣部分:任务调度,允许交互式的Spark解释器,内存管理,检查支持(checkpointing)
任务调度
Spark的任务调度类似Dryad
每当用户进行一次action,首先会检查RDD的血缘图,建立一个stages的DAG,按stage来执行这次action
每个stage都尽量包括尽可能多的管道式变换的窄依赖,stages之间的界限,就是宽依赖的洗操作,或者任何已经准备好了的分区。调度器就一个个执行这些stage,直到目标RDD生成出来
调度器布置任务是基于数据本地化分配的,采用延迟调度。
对于宽依赖,目前是物化中间结果在节点上,保持住父分区来简化错误恢复,和MR的map outputs相似
任务失败则重跑,如果丢失了如shuffle这样的中间结果会重新计算,目前不容忍调度失败
lookup操作提供随机访问元素,根据key在哈希分区上找
解释器整合
在scala解释器的基础上,做了改变:
class shipping:让工作节点通过http获得class的bytecode
修改了的code generation逻辑:为了让work节点能拿到引用,直接指向每行的实例(可能翻译不准确,理解不是很到位)
内存管理
Spark提供三种持久化RDD的存储选择:以反序列化java对象的方式存内存,以序列化数据的方式存内存,存磁盘
第一种最快,jvm可以本地化取RDD元素,第二种是高效内存化的方式
内存策略是LRU,新的RDD不够空间的时候,贯彻最近最少原则。同时,给予用户对RDD“持久性优先级”的控制
目前RDD有自己单独的内存空间,将来打算调研用一个统一的内存管理者在不同的实例之间分享式RDD
支持检查(checkpointing, 翻译为检查可能不太妥当,见谅)
尽管从RDD错误恢复的时候会使用血缘,但如果血缘链很长,就会很耗时,所以有必要对一些RDD进行 稳定存储时的检查
所以是为了长血缘链提供的,比如PageRank,短的不值得。
API是persist里的一个REPLICATE flag,主导权在用户手里,也在调研自动化检查。因为调度器知道每个数据集大小,第一次计算出来的耗时,所以应该可以做到优化最少恢复时间的选择。
同时,RDD的只读特性有利于检查,不需要中止和影响别的程序
第六节
评估
在迭代式机器学习和图计算中,节省的主要耗时在于避免了IO开销和反序列化开销
迭代式机器学习评估
kmeans和逻辑回归,前者重在计算,后者重在反序列化和IO开销
Spark在第一次之后的迭代中速度是非常快的。而在第一次迭代中,也稍快于hadoop,因为hadoop在master和slave之间的心跳机制
等等别的对比,以及已有的应用场景介绍
第七节
讨论
介绍了一些相关的系统
RDD的设计还有利于debug
第八节
相关工作
详细归类了各种类似系统
第九节
总结
略
(全文完)