Spark RDD运行原理

概述

本文主要介绍Saprk中重要的概念-----RDD.
RDD是为了处理迭代算法和数据发掘应运而生的,keep 数据在内存,显著提升性能。
RDD基于lineage实现容错,而不是shared state的update。

1. 简介

1.1 设计背景

  • 许多迭代式算法(比如机器学习、图算法等)和交互式数据挖掘工具,共同之处是,不同计算阶段之间会重用中间结果
  • 目前的MapReduce框架都是把中间结果写入到HDFS中,带来了大量的数据复制、磁盘IO和序列化开销
  • RDD就是为了满足这种需求而出现的,它提供了一个抽象的数据架构,我们不必担心底层数据的分布式特性,只需将具体的应用逻辑表达为一系列转换处理,不同RDD之间的转换操作形成依赖关系,可以实现管道化避免中间数据存储

1.2 挑战

RDD设计中的主要挑战是提供高效的“fault tolerance”编程接口:

  • 现有的对于内存存储的容错都是细粒度的对于可变状态的update,具体为跨机器的数据备份(replicate data cross machines )或者跨机器的日志更新( log updates cross machines),需要跨机器传输备份数据,效率低。
  • 不同的,RDD提供基于粗粒度的transformations(例如map,filter等)构建的lineage,如果一个RDD丢失数据,则可根据lineage找出丢失数据的来源,从新计算,达到容错,而不需要数据备份。

2. RDD概念

2.1 RDD相关简介

  • 一个RDD就是一个分布式对象集合,本质上是一个只读的分区记录集合,每个RDD可分成多个分区,每个分区就是一个数据集片段,并且一个RDD的不同分区可以被保存到集群中不同的节点上,从而可以在集群中的不同节点上进行并行计算
  • RDD提供了一种高度受限的共享内存模型,即RDD是只读的记录分区的集合,不能直接修改,只能基于稳定的物理存储中的数据集创建RDD,或者通过在其他RDD上执行确定的转换操作(如map、join和group by)而创建得到新的RDD
  • RDD提供了一组丰富的操作以支持常见的数据运算,分为“动作”(Action)和“转换”(Transformation)两种类型
  • RDD提供的转换接口都非常简单,都是类似map、filter、groupBy、join等粗粒度的数据转换操作,而不是针对某个数据项的细粒度修改(不适合网页爬虫)
  • 表面上RDD的功能很受限、不够强大,实际上RDD已经被实践证明可以高效地表达许多框架的编程模型(比如MapReduce、SQL、Pregel)
  • Spark用Scala语言实现了RDD的API,程序员可以通过调用API实现对RDD的各种操作

2.2 RDD典型执行过程

RDD典型的执行过程如下:

  1. RDD读入外部数据源进行创建
  2. RDD经过一系列的转换(Transformation)操作,每一次都会产生不同的RDD,供给下一个转换操作使用
  3. 最后一个RDD经过“动作”操作进行转换,并输出到外部数据源
    在这里插入图片描述
    这一系列处理称为一个Lineage(血缘关系),即DAG拓扑排序的结果
    spark的实现中,使用对象存储数据,transformation是操作这些对象的方法,RDD可以通过外部数据源或者parallelize操作数组的方式初始化,执行transformation获得新RDD,最后是action操作(例如count,collect,save),spark中RDD的计算是lazy的,此外程序可以使用persist和cache(cache是调用persist(StorageLevel.MEMORY_ONLY) )缓存数据达到重复利用。

优点
惰性调用、管道化、避免同步等待、不需要保存中间结果、每次操作变得简单

2.3 RDD和shared state对比

在这里插入图片描述
上图有几个重要信息,因为RDD是只读的(不可变的)和使用了lineage,一致性和容错得到了保障,对于stragglers(是指long-running tasks,slow nodes)的策略和mapreduce相似,使用了备份任务,同时执行的方式,此外,跟性能相关性大的一点是计算跟着数据走。

3. RDD 特性

3.1 RDD模型优势

  1. 容错方式:
    RDD只能通过粗粒度的transformation操作,这确保RDD可以bulk write,不需要通过checkpoint就能实现数据容错,此外,只有丢失数据的partition需要重算,根据lineage。
  2. 任务备份:
    通过对运行慢的任务进行备份,提高任务的执行速度。
  3. 就近执行:
    计算跟着数据走,提高执行效率。
  4. 不只是内存:
    内存不足,数据可以存储在硬盘,并通过压缩的方式获得和内存处理相近的性能。

3.2 RDD计算优势

Spark采用RDD以后能够实现高效计算的原因主要在于:

  1. 高效的容错性
    # 现有容错机制:数据复制或者记录日志
    # RDD:血缘关系、重新计算丢失分区、无需回滚系统、重算过程在不同节点之间并行、只记录粗粒度的操作
  2. 中间结果持久化到内存,数据在内存中的多个RDD操作之间进行传递,避免了不必要的读写磁盘开销
  3. 存放的数据可以是Java对象,避免了不必要的对象序列化和反序列化

3.3 RDD不是万能

RDD适合批处理,不适合细粒度的对于shared state的update,如web程序的DB,持续增长的web爬虫数据存储等。

4. RDD之间的依赖关系

4.1 RDD依赖关系之Shuffle操作

在这里插入图片描述

一个关于Shuffle操作的简单实例

4.2 RDD依赖关系之宽依赖和窄依赖

  • 窄依赖表现为一个父RDD的分区对应于一个子RDD的分区或多个父RDD的分区对应于一个子RDD的分区(也就是一个父RDD只能有一个子RDD,但是反之不然)
  • 宽依赖则表现为存在一个父RDD的一个分区对应一个子RDD的多个分区

在这里插入图片描述

一个关于Shuffle操作的简单实例

5. RDD任务调度

5.1 stage划分

Spark 根据DAG 图中的RDD 依赖关系,把一个job分成多个stage。stage划分的依据是窄依赖和宽依赖。对于宽依赖和窄依赖而言,窄依赖对于job的优化很有利,宽依赖无法优化
逻辑上,每个RDD 操作都是一个fork/join(一种用于并行执行任务的框架),把计算 fork 到每个RDD 分区,完成计算后对各个分区得到的结果进行join 操作,然后fork/join下一个RDD 操作。 (fork–分叉)

5.1.1 fork/join的优化原理

举例:一个学校(含2个班级)完成从北京到厦门的长征
在这里插入图片描述
窄依赖可以实现“流水线”优化
宽依赖无法实现“流水线”优化
在这里插入图片描述
在这里插入图片描述
说明:
Spark根据DAG图中的RDD依赖关系,把一个作业分成多个阶段。对于宽依赖和窄依赖而言,窄依赖对于作业的优化很有利。只有窄依赖可以实现流水线优化,宽依赖包含Shuffle过程,无法实现流水线方式处理。

5.1.2 Stage划分策略

Spark通过分析各个RDD的依赖关系生成了DAG,再通过分析各个RDD中的分区之间的依赖关系来决定如何划分Stage,具体划分方法是:

  1. 在DAG中进行反向解析,遇到宽依赖就断开
  2. 遇到窄依赖就把当前的RDD加入到Stage中
  3. 将窄依赖尽量划分在同一个Stage中,可以实现流水线计算
5.1.3 Stage划分示例

如下图,被分成三个Stage,在Stage2中,从map到union都是窄依赖,这两步操作可以形成一个流水线操作
在这里插入图片描述

图 根据RDD分区的依赖关系划分Stage

流水线操作实例说明:
分区7通过map操作生成的分区9,可以不用等待分区8到分区10这个map操作的计算结束,而是继续进行union操作,得到分区13,这样流水线执行大大提高了计算的效率

当调用action方法时,spark根据lineage构建DAG生成task。如上图所示,以RDD的partition为最小粒度,划分stage时,从一个RDD开始,尽可能多的包含流水线操作(窄依赖),将其划分为一个stage(如上图stage2),每个stage的边界是shuffle操作(宽依赖)。
对于宽依赖,会保存中间结果便于遇到错误后数据恢复,和mapreduce的map端相似。

5.2 任务执行

stage划分完毕后,根据stage生成一系列task,使用delay scheduling算法,根据计算跟数据走的原则分发task到各节点执行,计算后的结果返给driver节点的action操作。

5.3 内存管理

spark提供三种RDD持久化方案

  1. 内存存储序列化的java对象
  2. 内存存储其他格式的序列化数据
  3. 磁盘存储
    第一种性能最好,第二种选择合适的序列化和压缩工具能更有效利用内存,第三种适用特别大的数据量。
    为了更有效的使用内存,使用LRU算法,然而,内存不足时,清除的却是最近被使用的RDD的partition,保存old partition在内存,目的是为了防止partition频繁的in and out。因为我们通常操作整个RDD,那么早已在内存中的partition将来被用到的可能性大。目前spark集群的每个实例单独管理自己的 memory,未来调研统一内存管理的可行性(unified memory manager)。

checkpoint

RDD支持保存点(checkpoint):
虽然RDD可以通过lineage实现fault recovery,但是这个恢复可能是很耗时的,因此提供保存点很有必要,通常保存点在有宽依赖时(shuffle耗时)很有用,相反,窄依赖时则不值得使用。spark提供了API ,但何时使用由用户决定,也在调研自动保存点的可行性,RDD的只读属性也使其实现保存点功能比传统的shared state更容易。

6. RDD运行过程

通过上述对RDD概念、依赖关系和Stage划分的介绍,结合之前介绍的Spark运行基本流程,再总结一下RDD在Spark架构中的运行过程:
(1)创建RDD对象;
(2)SparkContext负责计算RDD之间的依赖关系,构建DAG;
(3)DAGScheduler负责把DAG图分解成多个Stage,每个Stage中包含了多个Task,每个Task会被TaskScheduler分发给各个WorkerNode上的Executor去执行。
图 RDD在Spark中的运行过程

图 RDD在Spark中的运行过程

7. RDD编程相关

7.1 RDD相关操作

在这里插入图片描述
spark采用scala语言实现,简洁,效率不错

7.2 RDD组成

在这里插入图片描述

7.3 部分RDD实现

  1. hadoopRDD:
    一个block对应一个partition, preferredLocations方法返回block, 然后iterator reads the block。
  2. mapRDD:
    map方法返回mapRDD, 保持和父RDD相同的partition和preferredLocations,只是将map方法应用于父RDD的每一条记录。
  3. unionRDD:
    union方法返回,每一个child RDD的partition通过窄依赖计算得来。
  4. sampleRDD:
    和map相似,多了一个随机数生成器,用来获取样本RDD中记录
  5. joinRDD:
    可能导致两个宽依赖,两个窄依赖,或者各有一个,result RDD拥有的partition schema,可以是默认的hash partitioner,或者从父RDD继承的
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值