文章目录
背景
RDDs(Resilient Distributed Datasets)
:弹性分布式数据集,一种分布式的内存抽象,允许程序员在大型集群中进行计算的容错方式。
发展背景:因为传统的并行计算(MapReduce
)采用的是非循环式的数据流模型,使得在迭代计算和交互式计算要进行大量的 I/O 操作,而 RDDs
正是解决这一点的抽象方法。
分布式集群概念图:
MapReduce
流程图:以 WordCount
为例
主要有两个操作:
Map
:将数据进行一系列操作转换得到结果的过程,在从节点执行Reduce
:将数据整合成最终结果,在主节点执行
流程:input -> splitting -> mapping -> shuffling -> reducing -> result
产生的原因:
MapReduce
和Drayd
虽然已经被广泛应用到大规模的数据分析,但是缺乏对分布式内存的运用,使得需要重用中间结果的计算十分低效
低效的计算类型:
- 如迭代式机器学习和图算法:PageRank、K-means聚类和逻辑回归。
- 如
Ad-hoc
查询:用户根据自己的需求,灵活的选择查询条件,系统能够根据用户的选择生成相应的统计报表
对传统 MapReduce
的改进:
- 迭代式图计算系统
Pregel
Hadoop
提供了一种迭代式MapReduce
接口
仍然存在的问题:
- 仅支持特定的计算模式(循环一系列的
MapReduce
操作),没有提供更加通用的数据重用的抽象能够让用户直接在内存中装载数据
已有解决方法的缺点:
- 基于集群的内存存储抽象:分布式共享内存,键值对存储,数据库等提供了基于细粒度更新可变状态的接口,运用这些接口时,达到数据容错的唯一方式是在不同的机器之间进行数据的冗余或者记录更新日志
- 细粒度:针对数据集中的某个元素进行转换操作
- 粗粒度:针对整个数据集进行转换操作,而不是针对数据集中的某个元素进行转换操作
- 对于数据密集型任务来说代价高昂,因为他们需要在集群网络间复制大量数据,同时带来大量的存储开销
- 数据密集型:大量的,复杂的、丰富多样的,快速变化的数据
本文的解决方法:
- 提出了一种能够广泛运用于各种应用中高效的数据重用抽象,弹性分布式数据集(
RDDs
)。RDDs
是一个容错的、并行的数据结构,能够让用户明确地在内存中持久化中间结果,控制其分区以优化数据的放置和使用丰富的操作符对其进行处理
Rdds 的特点:
- 读操作:粗粒度或细粒度
- 写操作:粗粒度
- 记录产生数据集的一系列转换操作(称为
lineage
),而不记录真实的数据 - 当
lineage
过长时,会设置检查点(checkpoint
) - 如果
Rdd
丢失了,有足够的信息知道自己是如何从其他Rdd
产生的,从而重新计算该分区,因此丢失的数据可以很快的回复,而不需要昂贵的代价
弹性分布式数据集(RDDs)
- 弹性:横向有多个分区,纵向当计算过程中内存不足时可以写到外存上,当需要使用时交换到内存中
- 分布式:分布在多台机器进行并行计算
- 数据集:只读的抽象的数据集合
可以看做 lineage
图追踪不同版本化数据
简略 rdd
理解图
RDD
有多个分区,每个分区指向数据或其他的RDD
- 计算节点存储
RDD
的一个或多个分区 - 分区就是依照特定规则,将具有相同属性的数据记录放在一起
如何产生 RDD
- 从稳定的物理存储中的数据集创建
- 由其他
RDDs
通过转换操作产生
用户可以对 RDD 的控制
- 持久化:选择需要重用的
RDD
的存储策略,设定特定的RDD
只存在硬盘上,或是设置优先级,当内存不够时,将内存中的哪个数据放到硬盘中 - 分区: 可以选择
RDDs
的分区方式,可以通过用户自定义的规则进行分区
Spark 提供的编程接口
- 转换(
transformations
):map
,filter
- 所有的转换操作都是懒惰的
- 转换操作会产生新的
RDDs
,每个RDDs
又包含多个分区,一系列的转换操作就构成了相互依赖的多个RDDs
组成的有向无环图(DAG
)
- 动作(
actions
):count
,collect
,save
- 动作立即执行,返回结果
lineage 图示
- 假设某一个分区数据丢失,则在相应的分区上重新进行转换重建丢失分区
RDDs 表示
- 基于图表示
RDD
之间的关系(lineage
) - 一个公共接口表示每个
RDD
的信息partitions()
:返回一组分区,是数据集的原子块preferredLocations(p)
:返回分区p
存放的位置dependencies()
:返回该RDD
的依赖(父亲)iterator(p,parentlters)
:基于父亲计算数据集partitioner()
:分区器,定义分区规则
依赖关系:
- 窄依赖(
narrow dependencies
):父RDD
的每个分区最多由子RDD
的一个分区使用 - 宽依赖(
wide dependencies
):一个子RDD
依赖多个父RDD
这样区分的原因:
- 窄依赖关系可以在一个集群节点上顺序执行,节点故障时只需要重新计算父
RDD
的分区 - 宽依赖关系需要所有父分区的数据计算完成后,再进行洗牌(
shuffle
),因此单个节点故障可能导致所有的子孙丢失数据,从而需要完全重新执行计算
实现
使用了 14000 的 scala
代码实现了 spark
- 系统运行在
Mesos
集群管理器上 - 可以与
hadoop
、MPI
和其他应用程序共享资源 - 每个
spark
程序作为单独的Mesos
应用程序运行,具有自己的驱动程序master
和工作程序workers
,应用程序之间的资源共享由Mesos
管理 - 可以不用修改任何代码从
hadoop
(hdfs 或 hbase
) 读取数据
作业调度
调度思想
- 调度程序会基于数据的位置使用使用延迟调度将任务分配给机器,实现任务所需的数据就在其运行的节点上
延迟调度:将任务发布到其所需数据的机器上,若机器已经被占用则进行延迟等待,直到可以运行该任务或者超过了等待时间则将任务运行到其他机器上
阶段(stage)
- 当用户执行
action
操作时,调度程序会检查该RDD
的lineage
图构建要执行阶段的DAG
- 每个阶段尽可能包含多的窄依赖关系转换,阶段的边界是宽依赖所需的洗牌操作或已经计算过得分区。
- 对于宽依赖关系,会在父
RDD
上记录中间结果以简化故障恢复 - 如果一个任务失败,一个阶段的父项仍然可用,则会在另一个节点重新运行;若某些阶段产生了数据丢失,则要重新提交任务计算丢失的分区
解释器整合
scala 解释器
scala
提供了Ruby
和Python
的交互式shell
Scala
解释器为用户键入的每一行编译一个类,该类包含一个单例对象,该对象包含该行上的变量或函数,并在初始化方法中运行代码
例子:
Line1: var x = 5
Line2: println(x)
- 解释器会编译一个包含
x
的Line1
类 - 第二行编译为
println(line1.getInstance().x)
spark 解释器
做了两点改动:
- 类传输:支持基于
HTTP
传输类字节码文件,这样worker
节点就能获取输入的每行代码对应的字节码 worker
直接引用各个行对象的实例
内存管理
- 序列化:将变量从内存中变成可存储或传输的过程
- 反序列化:把变量内容从序列化的对象重新读到内存里
Spark
提供了三种持久化的 RDD
选项:
- 反序列化
Java
对象的内存存储 - 序列化数据的内存存储
- 硬盘存储
内存替换:
- 当新的
RDD
分区没有足够的内存存储时触发 - 使用
LRU
(Least Recent Used
最近最少使用)策略替换 - 用户可以设定
RDD
持久化优先级提供进一步控制
检查点的支持
- 尽管在故障的时候可以通过
lineage
来恢复RDD
。但是对于具有非常长的lineage
的RDD
来说,则恢复是十分耗时的,因此将一些RDD
检查点存放到稳定存储中会减少恢复的耗时 - 检查点技术主要对具有宽依赖关系的
RDD
比较有用,对于窄依赖关系的RDD
用处不大
评估
迭代式机器学习应用
实现了两个迭代机器学习应用程序,逻辑回归和 k-means
,以比较以下系统的性能:
Hadoop
:0.20.2
稳定的发行版本HadoopBinMem
:在首轮迭代中执行预处理,通过将输入数据转换成为开销较低的二进制格式来减少后续迭代过程中文本解析的开销,在HDFS
中加载到内存。Spark
:基于RDDs
的实现。
- 由于
HadoopBinMem
首先要执行数据转换为二进制的任务,所以在首次迭代中速度最慢 - 而在后续的迭代中,由于
HadoopBinMem
解析文本的开销较小,因此速度优于Hadoop
- 而
Spark
均是速度最快的
- 对于逻辑回归,
Spark
在100
台机器上分别比Hadoop
和HadoopBinMem
快25.3
倍和20.7
倍 - 对于计算密集型的
k-means
应用程序来说,Spark
仍然实现了1.9
倍到3.2
倍的加速
理解速度提升
会发现 Spark
甚至比内存存储二进制数据的 Hadoop
(HadoopBinMem
)还要快 20
倍。在 HadoopBinMem
中,我们使用了 Hadoop
的标准二进制格式(SequenceFile
)和 256MB
大小的超大块,并且我们强制 HDFS
的数据目录位于内存文件系统中
由于以下因素,Hadoop
仍然运行缓慢:
Hadoop
软件堆栈的最小开销,运行一个Hadoop
空作业,仅仅执行作业的初始化、启动任务、清理工作就至少耗时25
秒- 提供数据时
HDFS
的开销,每一个HDFS
数据块,HDFS
进行了多次复制以及计算校验和操作(一致性问题) - 将二进制记录转换为内存中的
Java
对象的反序列化成本
PageRank
网页排名,是 Google
公司所使用的对其搜索引擎搜索结果中的网页进行排名的一种算法
Spark
在30
个节点上的速度比Hadoop
提高了2.4
倍,控制RDD
的分区以使其在迭代中保持一致,将提高到7.4
倍- 加速也能几乎线性地扩展到
60
个节点上
数据集在内存中不同百分比的表现
- 随着数据集在内存中的比重增加,迭代时间随之减小
交互式数据挖掘
为了演示 Spark
交互式查询大数据集的能力,我们用它来分析 1TB
的维基百科页面浏览日志(2年的数据),在输入的数据集上获取所查询页面的浏览总数:
- 全部页面
- 页面的标题能精确匹配给定的关键词
- 页面的标题能部分匹配给定的关键词
- 实验分别在整个, 1 2 \frac{1}{2} 21, 1 10 \frac{1}{10} 101 的数据集上查询的响应时间,结果表示比直接在磁盘上操作快了几个数量级
- 实验结果表明
RDD
缓存会使得Spark
成为一个交互式数据挖掘的强度工具
总结
RDDs
是一种高效、通用、容错的用于集群应用程序中共享数据的抽象,不记录真实数据,而是记录数据转换的关系和数据存放的位置RDDs
提供粗粒度转换的操作,使得能够用lineage
有效的恢复数据- 对于窄依赖关系直接计算父
RDD
的相对应的分区,宽依赖关系通过在父RDD
记录中间结果简化故障恢复,对于长的lineage
则设置检查点简化恢复操作 RDDs
支持用户对数据进行持久化和分区操作- 调度主要思想是移动计算而不是移动数据
- 内存不足时可以暂时存放到外存中,有需要再换进内存