6.824 讲座12:Spark案例研究
弹性分布式数据集:内存群集计算的容错抽象
Zaharia,Chowdhury,Das,Dave,Ma,McCauley,Franklin,Shenker,Stoica
NSDI 2012
如今:更多分布式计算
案例研究:Spark
我们为什么要阅读Spark?
广泛用于数据中心计算
受欢迎的开源项目,热启动(Databricks)
比MapReduce更好地支持迭代应用程序
有趣的容错故事
ACM博士论文奖
MapReduce使程序员的生活变得轻松
它处理:
节点之间的通讯
分配代码
安排工作
处理失败
但是受限的编程模型
一些应用程序与MapReduce不太匹配
许多算法是迭代的
网页排名是典型的例子
根据指向多少文档计算每个文档的排名
等级用于确定文档在搜索结果中的位置
在每次迭代中,每个文档:
向其邻居发送r / n的贡献
其中r是其等级,n是其邻居数。
将等级更新为alpha / N +(1-alpha)* Sum(c_i),
总和超过收到的捐款
N是文件总数。
大计算量:
在世界上所有网页上运行
即使有很多机器也需要很长时间
MapReduce和迭代算法
MapReduce擅长算法的一次迭代
每个地图都包含部分文档
降低以更新特定文档的等级
但是下一次迭代该怎么做?
将结果写入存储
为下一次迭代启动新的MapReduce作业
昂贵
但是容错
挑战性
更好的迭代计算编程模型
良好的容错性故事
一种解决方案:使用DSM
适用于迭代编程
rank可以在共享内存中
工人可以更新和阅读
不利于容错
典型计划:内存的检查点状态
每小时存储一次的检查点
有两种昂贵的方式:
在计算过程中将共享内存写入存储
失败后重做自上一个检查点以来的所有工作
产生更多MapReduce风味
受限的编程模型,但比MapReduce更强大
良好的容错计划
更好的解决方案:将数据保留在内存中
Pregel,Dryad,Spark等
在Spark
数据存储在数据集(RDD)中
在内存中“持久化” RDD
下次迭代可以参考RDD
其他机会
互动数据探索
在持久的RDD上运行查询
喜欢有类似SQL的东西
RDD上的联接运算符
Spark的核心思想:RDD
RDD是不可变的—您无法更新它们
RDD支持转换和行动
转换:根据现有RDD计算新的RDD
映射,reduceByKey,过滤,联接,…
转换很懒:不要立即计算结果
只是对计算的描述
行动:何时需要结果
计算结果,收集结果,获得特定值
使用示例:
行= spark.textFile(“ hdfs:// …”)
错误= lines.filter(_。startsWith(“ ERROR”))//懒!
errors.persist()//尚无工作
errors.count()//计算结果的动作
//现在错误已在内存中实现
//跨多个节点
// Spark,将尝试保留在RAM中(RAM满时将溢出到磁盘)
重用RDD
errors.filter(_。contains(“ MySQL”))。count()
//这将很快,因为可重复使用由先前片段计算的结果
// Spark将在包含错误分区的机器之间调度作业
RDD的另一种重用
errors.filter(。contains(“ HDFS”))。map(。split(’\ t’)(3))。collect()
RDD沿袭
Spark在动作上创建沿袭图
图形使用转换描述计算
行->过滤器w错误->错误->过滤器w。HDFS->映射->定时字段
Spark使用沿袭来安排工作
在同一分区上的转换构成一个阶段
例如,连接是舞台边界
需要改组数据
作业运行一个阶段
阶段内的管道转换
计划RDD分区所在的作业
沿袭和容错
高效*容错的绝佳机会
假设一台机器发生故障
只想重新计算其状态
血统告诉我们要重新计算什么
按照血统确定所需的所有分区
重新计算
对于最后一个示例,请确定缺少的行的分区
从孩子追溯到父母世系
所有依赖项都是“狭窄的”
每个分区都依赖于一个父分区
需要读取缺少的行分区
重新计算转换
RDD实施
分区列表
(父RDD,宽/窄依赖项)列表
狭窄:取决于一个父分区(例如,映射)
宽:取决于多个父分区(例如,join)
计算功能(例如,映射,联接)
分区方案(例如,逐文件)
计算放置提示
每个转换获取(一个或多个)RDD,并输出转换后的RDD。
问:为什么RDD在分区时会携带元数据?答:如此转型
依赖多个RDD的用户知道是否需要重新整理数据
依赖性)(否)。允许用户控制位置并减少
洗牌。
问:为什么要区分狭窄和广泛依赖?答:
失败。窄依赖性仅取决于需要划分的几个分区
重新计算。广泛的依赖关系可能需要整个RDD
示例:PageRank(从纸上):
//将图作为(URL,出站)对的RDD加载
val links = spark.textFile(…)。map(…)。persist()//(URL,外链)
var ranks = //(URL,rank)对的RDD
表示(i <-1到ITERATIONS){
//建立(targetURL,float)对的RDD
//每个页面发送的贡献
val contribs = links.join(ranks).flatMap {
(网址,(链接,等级))=> links.map(目标=>(目标,等级/links.size))
}
//通过URL汇总贡献并获得新排名
排名= contribs.reduceByKey((x,y)=> x + y)
.mapValues(sum => a / N +(1-a)* sum)
}
PageRank的沿袭
见图3
每次迭代都会创建两个新的RDD:
ranks0,ranks1等
contribs0,contribs1等
长血统图!
容错风险大。
一个节点发生故障,重新计算
解决方案:用户可以复制RDD
程序员将“可靠”标志传递给persist()
例如,每N次迭代调用一次ranks.persist(RELIABLE)
复制内存中的RDD
带有REPLICATE标志,将写入稳定存储(HDFS)
对性能的影响
如果用户经常坚持使用REPLICATE,则恢复速度快,但执行速度较慢
如果很少,执行速度快但恢复缓慢
问:坚持是转变还是行动?答:都没有。它不会创建一个
新的RDD,并且不会实现。这是对
调度程序。
问:通过调用无标志的persist,可以确保在发生故障时
RDD不必重新计算吗?答:否。没有复制,因此是一个节点
持有分区可能会失败。复制(在RAM或稳定中
存储)是必要的
当前仅通过呼叫进行手动检查点以保持。问:为什么实施
检查点?(很贵)A:血统很长会导致大量恢复
时间。或者,当存在广泛的依赖性时,单个故障可能需要很多
分区重新计算。
问:Spark可以处理网络分区吗?答:无法与之通信的节点
调度程序将显示为已失效。可以从以下位置到达网络的一部分
只要有足够的数据来启动调度程序,调度程序就可以继续计算
(如果无法访问所需分区的所有副本,
集群无法取得进展)
没有足够的内存会怎样?
分区上的LRU(最近最少使用)
首先坚持不懈
然后保留(但它们将在磁盘上可用。请确保用户不能超额预订RAM)
用户可以通过“持久优先级”控制逐出顺序
没有理由不丢弃非持久分区(如果已经使用过)
性能
降级为“几乎” MapReduce行为
在图7中,对100个Hadoop节点进行逻辑回归需要76-80秒
在图12中,在25个Spark节点上进行逻辑回归(内存中不允许分区)
需要68.8
差异可能是因为MapReduce在reduce之后使用复制的存储,但是Spark通过
默认只溢出到本地磁盘
副本上没有网络延迟和I / O负载。
没有架构上的原因,对于非迭代工作,MR会比Spark慢
或需要去磁盘的迭代工作
没有任何架构上的原因,使得Spark不会比MR慢
讨论区
Spark针对批量,迭代应用程序
Spark可以表达其他模型
MapReduce,Pregel
不能合并新数据
但请参阅流星火
Spark不利于建立键/值存储
像MapReduce等
RDD是不可变的
参考文献
http://spark.apache.org/
http://www.cs.princeton.edu/~chazelle/courses/BIB/pagerank.htm