您当前的位置: 首页 >  ar

少林码僧

暂无认证

  • 2浏览

    0关注

    317博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

spark调优指南

少林码僧 发布时间:2019-09-29 18:35:08 ,浏览量:2

文章目录
    • Spark 内核原理快速入门
      • 1. Spark 内核原理
        • 1.1 Spark 是什么
        • 1.2 RDD 及其存储
        • 1.3 RDD 五大特性
          • 1.3.1 分区列表
          • 1.3.2 Compute 计算函数
          • 1.3.3 依赖关系 Dependency
          • 1.3.4 分区器 Partitioner
          • 1.3.5 Partition 位置列表
        • 1.4 RDD“弹性”的 7 个体现
      • 2. Spark 宽窄依赖
        • 2.1 窄依赖
        • 2.2 宽依赖
      • 3. Spark 性能调优
        • 3.1 熟练使用 Web 诊断工具
        • 3.2 牢记 Spark 程序编写准则
        • 3.3 并行度优化
        • 3.4 资源参数调优
        • 3.5 序列化与压缩
        • 3.6 广播大变量
        • 3.7 缓存与 Checkpoint
        • 3.8 数据本地性
        • 3.9 垃圾回收调优
        • 3.10 Shuffle 调优
        • 3.11 内存调优

Spark 内核原理快速入门
1. Spark 内核原理
	1.1 Spark 是什么
    1.2 RDD 及其存储
    1.3 RDD 五大特性
	    1.3.1 分区列表]
	    1.3.2 Compute 计算函数
	    1.3.3 依赖关系 Dependency
	    1.3.4 分区器 Partitioner
	    1.3.5 Partition 位置列表
	1.4 RDD“弹性”的 7 个体现
2. Spark 宽窄依赖
	2.1 窄依赖
	2.2 宽依赖
3. Spark 性能调优
	3.1 熟练使用 Web 诊断工具
	3.2 牢记 Spark 程序编写准则
	3.3 并行度优化
	3.4 资源参数调优
	3.5 序列化与压缩
	3.6 广播大变量
	3.7 缓存与 Checkpoint
	3.8 数据本地性
	3.9 垃圾回收调优
	3.10 Shuffle 调优
	3.11 内存调优
1. Spark 内核原理 1.1 Spark 是什么

Spark 是一款大数据处理框架,其建立在抽象的分布式弹性数据集 RDD 之上,这使得它可以用一致的方式处理大数据不同的应用场景,把需要处理的数据转化成为 RDD,然后对 RDD 进行一系列的算子运算从而得到结果。

RDD(Resilient Distributed Datasets 弹性分布式数据集),是一个容错的、并行的数据结构,可以将数据存储到内存和磁盘中,并能控制数据分区,且提供了丰富的 API 来操作数据,特别适合于迭代计算,例如机器学习和图计算;同时 Spark 提供了对 Scala 和 Python 交互式 Shell 的支持,极大地方便了通过交互式接口来快速验证解决思路和想法,这对于原型开发至关重要,对数据分析人员有着无法拒绝的吸引力。

1.2 RDD 及其存储

RDD(Resilient Distributed Datasets,弹性分布式数据集),是分布式内存的一个抽象概念,是一种高度受限的共享内存模型,它是只读的记录分区的集合,能横跨集群所有节点进行并行计算,是一种基于工作集的应用抽象。

RDD 底层存储原理

RDD数据分布存储于多台机器上,每个 RDD 的数据都以 Block 的形式存储在多台机器上,其中的数据只有在 Action 算子触发之后才会真正地加载到内存中,因此具有 Lazy 的特性,每个 Executor 会启动一个 BlockManagerSlave,并管理一部分 Block,而 Block 的元数据由 Driver 节点上的 BlockManagerMaster 保存,BlockManagerSlave 生成 Block 后向 BlockManagerMaster 注册该 Block,BlockManagerMaster 管理 RDD Partition 与 Block 的关系,当 RDD 不再需要存储的时候,将向 BlockManagerSlave 发送指令删除相应的 Block,其架构图如图 1.1 所示。

![876cb160-dcf0-11e9-a7d6-b7d6ec70d28e.png][0f063f31c254ddb828d5493845fa8450]

图 1.1 Spark 存储架构图

BlockManager 管理 RDD 的物理分区,每个 Block 就是节点上对应的一个数据块,可以存储在内存或者磁盘上。而 RDD 中的 partition 是一个逻辑数据块,对应相应的物理块 Block。本质上一个 RDD 在代码中相当于是数据的一个元数据结构,存储着数据分区及其逻辑结构映射关系,存储着 RDD 之前的依赖转换关系。

BlockManagerMaster 会持有整个 Application 的 Block 的位置、Block 所占用的存储空间等元数据信息,在 Spark 的 Driver 的 DAGScheduler 中就是通过这些信息来确认数据运行的本地性的。Spark 支持重分区,数据通过 Spark 默认的或者用户自定义的分区器决定数据块分布在那些节点。

RDD 的物理分区是由 BlockManager 管理的,每个 Block 就是节点上对应的一个数据块,可以存储在内存或者磁盘。而 RDD 中的 partition 是一个逻辑数据块,对应相应的物理块 Block。本质上一个 RDD 在代码中相当于是数据的一个元数据结构(一个 RDD 就是一组分区),存储着数据分区及 Block、Node 等的映射关系,以及其他元数据信息,存储着 RDD 之前的依赖转换关系。分区是一个逻辑概念,transformation 前后的新旧分区在物理上可能是同一块内存存储。

1.3 RDD 五大特性

五大特性包括:

  • 分区列表
  • Compute 计算函数
  • 依赖列表 Dependency
  • 分区器 Partitioner
  • 分区位置列表

在 RDD 抽象类中,可以看到 RDD 中的五大特性:

  1. 返回一个 RDD 分区列表,这个方法仅仅只被调用一次。
protected def getPartitions: Array[Partition]
  1. 通过子类来实现对给定分区的计算
@DeveloperApi
def compute(split: Partition, context: TaskContext): Iterator[T]
  1. 返回父 RDD 的依赖列表,这个方法仅仅只被调用一次。
protected def getDependencies: Seq[Dependency[\_]] = deps
  1. 可选的分区的方法,用于指定如何分区,默认分区器为 HashPartitioner。
@transient val partitioner: Option[Partitioner] = None
  1. 得到 Parition 逻辑存储对应的物理 block 位置节点,输入参数是 split 分片,输出结果是一组优先的节点位置。
protected def getPreferredLocations(split: Partition): Seq[String] = Nil
1.3.1 分区列表

Spark RDD 是被分区的,getPartitions 方法获取分区列表,每一个分区都会被一个计算任务(Task)处理,分区数决定并行计算数量,RDD 的并行度默认从父 RDD 传给子 RDD。

默认情况下,一个 HDFS 上的的数据分片就是一个 partiton,RDD 分片数决定了并行计算的力度,可以在创建 RDD 时指定 RDD 分片个数,如果不指定分区数量,当 RDD 从集合创建的时候,默认分区数量为该程序所分配到的资源的 CPU 核数(每个 Core 可以承载 2~4 partition),如果是从 HDFS 文件创建,默认为文件的 Block 数。

1.3.2 Compute 计算函数

每一个分区都有一个计算函数 Compute,RDD 的计算函数是以分片为基本单位的,每个 RDD 都会实现 Compute 函数。对具体的分片进行计算,RDD 中的分片是并行的,所以是分布式并行计算。

有一点非常重要,就是由于 RDD 有前后依赖关系,遇到宽依赖关系,例如 reduceByKey 等这些 Shuffle 操作的时候划分 Stage,每个 Stage 内部的操作都是通过 Pipeline,在具体处理数据的时候它会通过 BlockManager 来获取相关的数据,因为具体的 split 它要从外界读数据,也要把具体的计算结果写入外部,所以用了一个管理器。

具体的 Partition 都会映射成 BlockManager 的 Block,而该 Partition 的处理正是调用 Cumpute 方法来运行,它会运行闭包形式传入的各种算子。

@DeveloperApi

def compute(split: Partition, context: TaskContext): Iterator[T]
1.3.3 依赖关系 Dependency

由于 RDD 每次转换都会生成新的 RDD,所以 RDD 会形成类似流水线一样的前后依赖关系,这些依赖关系分为宽依赖和窄依赖,宽依赖就不类似于流水线了,宽依赖后面的 RDD 具体的数据分片会依赖前面 Stage 的数据分片,这个时候就不能进行 Pipeline 流水线操作了,一般是跨机器的,因此宽依赖会产生跨机器的 Shuffle 开销。

依赖关系的维护还有一个好处,当有分区的数据丢失时,通过依赖关系重新计算丢失的数据分区,而不是对 RDD 所有的分区进行重新计算。

RDD 有窄依赖(Narrow Dependency)和宽依赖(Wide Dependency)两种不同类型的依赖,其中的 Narrow Dependency 指的是每一个 Parent RDD 的 Partition 最多被 Child RDD 的一个 Partition 所使用,而 Wide Dependency 指的是多个 Child RDD 的 Partition 会依赖于同一个 Parent RDD 的 Partition。

可以从两个方面来理解 RDD 之间的依赖关系,一方面是 RDD 的 parent RDD 是什么,另一方面是依赖于 Parent RDD 哪些 Partions;产生宽依赖典型的操作有 groupByKey、sortByKey 等带“ByKey”的操作,宽依赖意味着 Shuffle 操作,而 Shuffle 会产生大量的网络 IO,这往往成为 Spark 程序优化的要点,也是面试的必考点。

Spark 内部,宽依赖也是划分 Stage 的边界的依据。一旦产生 Shuffle 操作,就需要借助相应的管理器来管理,Spark 内部实现了两种 Shuffle Manager,它们是 HashShuffleManager 和 SortShuffleManager,前者是基于 Hash 的 Shuffle 机制,后者是基于排序的 Shuffle 机制。

![ec616400-dcf3-11e9-a8a0-8352c69cd96b.png][eafa39692ef8da507801717adabe8cab]

图 1.2 左图为“窄依赖”右图为“宽依赖”(图片来自 Spark 官网)

1.3.4 分区器 Partitioner

分区器用于控制分区策略,每个 key-value 形式的 RDD 都有 Partitoner 属性,它决定了 RDD 如何分区。当然 Partiton 的个数还决定每个 Stage 的 Task 个数,间接决定了整个任务的并行度,而并行度往往也是程序优化的要点,也是面试的考点。

常见的分区器有&#

关注
打赏
1661398670
查看更多评论
立即登录/注册

微信扫码登录

0.0390s