Spark论文理解

原文链接:http://nil.csail.mit.edu/6.824/2020/papers/zaharia-spark.pdf

摘要

Resilient Distributed Datasets (RDDs)把计算过程都在内存中进行,因此效率有很大的提升。同时,RDD可以更好地支持循环执行算法和交互式数据挖掘工具。RDD可以支持当前分布式大部分的算法范式,流入pregel等。

简介

在很多OLAP(Online Analytical Processing)过程中,数据都是可以复用的。很多机器学习或数据挖掘算法都会反复使用相同数据,例如PageRank(边连接关系)、K-means(各个点位置)、逻辑回归等。此外,用户在同一数据集上进行多次query查询也会复用数据。但是现有框架通常无法合理复用数据,例如Hadoop实现MR。

Spark最常见的对比对象就是hadoop。Hadoop实现MR的过程高度依赖文件系统,每一次MR处理都需要把中间结果存放在本机上,把最终结果放置到类似GFS的全局文件系统上。下一次MR处理相同数据得重新读取,很浪费时间(I/O问题是分布式里不可忽略的一个问题)。

RDD支持将多次复用的中间结果persist在内存里,另外通过粗粒度的转换方式(map、filter、join等)也方便在丢失后迅速重现。

RDD接口模型可以高效表达很多不同系统的编程模型,例如Haloop、Pregel、SQL等,应用范围极广。

Resilient Distributed Datasets(RDDs)

RDD概述

RDD是一个只读、分区记录的集合。RDD只可以通过指定方式在(1)data in stable storage or (2) other RDDs的基础上创建。

RDD无需时刻记录自身状态,它可以通过自身经历的lineage graph来快速恢复。

用户可以按照自己的意图控制RDDs的persist和partition信息。

Spark提供的接口可以分为两类:transformations和actions。Spark采用lazy compute的方式处理RDD,即每次transformation的时候都不会记录RDD的状态信息,直到action的时候才会一次性计算RDD的状态。

Spark通常把信息储存在内存中,但是内存不够的情况下Spark也会把信息存储在磁盘中。

RDD lineage图示例:

RDD的优势 

1、RDD不需要通过checkpoint之类的机制保存快照等状态信息,可以通过lineage快速恢复。

2、大部分情况只有出现问题的partition才需要重新计算,已完成的其他内容不受影响。

3、与MapReduce一样,如果有执行过慢的节点,可以启动另一个back-up节点执行相同的任务,来加快任务的执行。

4、系统可以调节各个partition的位置,通过data locality来提升执行速率。

5、内存不够时可以把信息储存在磁盘中,与现有数据流系统一致,不会表现更差。

RDD适用范围

RDD本身还是OLAP模型,不适合作为商业应用的直接存储数据库(即用于OLTP)。

Spark编程接口

Spark在运行时,用户的driver程序启动多个worker,worker从分布式文件系统中读取数据块,并将计算后的RDD分区缓存在内存中。

Spark设计时用的语言是scala,可以通过反射实现闭包。

RDD的主要操作分为transformations和actions,具体分类如下所示:

Spark不鼓励使用checkpoint,但在必要时可以将计算结果缓存下来以方便进行恢复。

例如在多次循环的PageRank算法中,其lineage图如下所示:

Spark便可以将中间某次的ranks结果给缓存起来,以减少发生错误重新计算的时间花销。

另外,可以通过控制Partitioning的方式来优化计算。本例中,可以将ranks和links想对应的部分partition到一起,这样可以在同一个节点内部进行处理而无需进行特殊的通信。

Spark使用Pregel模型实现PageRank功能的示例代码:

# Pagerank in the Pregel model 

from pyspark.sql.functions import coalesce, col, lit, sum, when, min
from graphframes.lib import Pregel

# Need to set up a directory for Pregel computation
sc.setCheckpointDir("checkpoint")


v = g.outDegrees
g = GraphFrame(v,e)
# withVertexCOlumn先定义一个初始值,然后用后面的coalesce顶替
ranks = g.pregel \
        .setMaxIter(5) \
        .sendMsgToDst(Pregel.src("rank") / Pregel.src("outDegree")) \
        .aggMsgs(sum(Pregel.msg())) \
        .withVertexColumn("rank", lit(1.0), \
            coalesce(Pregel.msg(), lit(0.0)) * lit(0.85) + lit(0.15)) \
        .run()
ranks.show()

# pyspark.sql.functions.coalesce(*cols): Returns the first column that is not null.
# Not to be confused with spark.sql.coalesce(numPartitions)

值得特别注意的是,一般Partition的数量要大于节点的数量,以便实现负载均衡(这样的话某个节点执行速度快可以快速接手剩余的内容)。 

Representing RDDs

RDD可以通过以下五项表述自身全部信息:

 值得特别一提的是depencies。我们可以将spark的依赖分为两种:窄依赖,即一个parent partition最多会被一个子partition使用;宽依赖,即一个parent partition可能会被多个子partitions使用。(这个定义需要注意,容易搞错理解为一个子partition依赖于多个parent partition)

与之相对,窄依赖恢复partition只需执行上面的那个partition即可;而宽依赖在恢复的时候可能需要重新执行全部的partition运行流程,时间开销很大。

另外实践中,关于宽依赖和窄依赖的优化执行逻辑也不一样。

一个指令通常对应于特定的依赖方式,示意图如下所示:

Implementation

Spark的scheduler会考虑哪些RDD在memory中。当用户执行某个action时,spark会计算出包含stage的有向无环图(DAG)。每个stage包含尽可能多的narrow transformation,不同stage间以宽依赖进行区分。

 最终spark也会以stage为子单位进行子任务重新运行。

scheduler在执行tasks的会延后执行并考虑数据locality。如果一个任务用到的partition知道在某一个node上,会将任务分发给该node,有倾向性描述也会运送过去。

值得特别注意的是,我们会把每一个stage(进行宽依赖转换)前的中间结果保存起来以便于错误恢复(就像MapReduce保存map outputs到内存上一样)。

当一个任务失败时,只要其parent stage没问题,就分发到另一个节点上重新执行。目前并不支持调度器失败,尽管这处理起来很简单。

内存管理

Spark为缓存RDD提供了3个选择:
 1、在内存中反序列化为Java对象。这种方式性能最快,因为Java VM可以在本地访问每一个RDD元素。
 2、在内存中反序列化为数据。这种方式允许用户在空间受限时,可以选择一种比Java对象更有效的内存策略。
 3、存储在磁盘上。这种方式对于太长的以致不能放在RAM中的RDD比较有用,但在使用时计算代价较高。

为了管理有限的内存空间,提出了RDD级别上的LRU策略(最近最少使用)。当计算一个新的RDD时所需空间不足,便将最近最少使用的RDD替换出去,除非如它与具有新分区的RDD是同一个RDD。这种情况下,在内存中记录旧分区,以防止同一个RDD循环的进来、出去。

另外,用户可以为每个RDD指定“缓存优先级”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值