=======================================================================================
使用SparkConf配置Spark
对 Spark 进行性能调优,通常就是修改 Spark 应用的运行时配置选项。
Spark 中最主要的配置机制是通过 SparkConf 类对 Spark 进行配置
当创建出一个 SparkContext 时,就需要创建出一个 SparkConf 的实例
SparkConf 实例包含用户要重载的配置选项的键值对
除了 set() 之外,SparkConf 类也包含了一小部分工具方法,可以很方便地设置部分常用参数
Spark 允许通过 spark-submit 工具动态设置配置项
spark-submit 工具为常用的 Spark 配置项参数提供了专用的标记,
还有一个通用标记--conf 来接收任意 Spark 配置项的值,
spark-submit 也支持从文件中读取配置项的值
方便复用这些配置
默认情况下,spark-submit 脚本会在 Spark 安装目录中找到 conf/spark-defaults.conf 文件,尝试读取该文件中以空格隔开的键值对数据
也可以通过 spark-submit 的 --properties-File 标记,自定义该文件的路径
几乎所有的 Spark 配置都发生在 SparkConf 的创建过程中,但有一个重要的选项是个例外。
你需要在 conf/spark-env.sh 中将环境变量
SPARK_LOCAL_DIRS 设置为用逗号隔开的存储位置列表,来指定
Spark 用来混洗数据的本地存储路径。
这需要在集群模式下设置。
这个配置项之所以和其他的 Spark 配置项不一样,是因为它的值在不同的物理主机上可能会有区别
==============================================================================================
Spark执行的组成部分:作业、步骤 、任务
每个RDD 维护了其与父节点之间关系的信息
比如,当你在 RDD 上调用 val b = a.map() 时,b 这个 RDD 就存下了对其父节点 a 的一个引用。
这些引用使得 RDD 可以追踪到其所有的祖先节点
Spark 提供了 toDebugString() 方法来查看 RDD 的谱系
toDugString输出的谱系图使用不同缩进等级来展示 RDD 是否会在物理步骤中进行流水线执行
例如,当计算 counts 时,尽管有很多级父 RDD,但从缩进来看总共只有两级。这表明物理执行只需要两个步骤
归纳一下,Spark 执行时有下面所列的这些流程
- 用户代码定义RDD的有向无环图
- 行动操作把有向无环图强制转译为物理执行计划
- Spark 调度器提交一个作业来计算所有必要的 RDD。
- 这个作业会包含一个或多个步骤,
- 一个步骤对应有向无环图中的一个或多个 RDD,一个步骤对应多个 RDD 是因为发生了流水线执行
- 每个步骤其实也就是一波并行执行的计算任务。
- 每个任务都是在不同的数据分区上做同样的事情
- 任务于集群中调度并执行
自动流水线执行优化
RDD 不需要混洗(Shuffle)数据就可以从父节点计算出来时,调度器就会自动进行流水线执行
访问应用的4040网页用户界面,查看程序运行期间执行了哪些步骤
RDD谱系图截短的三种情况:
- 流水线执行的优化,
- 当一个 RDD 已经缓存在集群内存或磁盘上时,Spark 的内部调度器也会自动截短 RDD 谱系图
- 当 RDD 已经在之前的数据混洗中作为副产品物化出来时
- 这种内部优化是基于 Spark 数据混洗操作的输出均被写入磁盘的特性,
================================================================================================
查找信息
查找信息的两个地方:
Spark 的网页用户界面
以及驱动器进程和执行器进程生成的日志文件中。
不过对于 YARN 集群模式来说,
应该通过 YARN 的资源管理器来访问用户界面。
用户界面主要由四个不同的页面组成
- 作业页面:步骤与任务的进度和指标
- 本页面经常用来评估一个作业的性能表现。
- 我们可以着眼于组成作业的所有步骤,
- 看看是不是有一些步骤特别慢,或是在多次运行同一个作业时响应时间差距很大
- 存储页面:已缓存的RDD的信息
- 这个页面可以告诉我们到底各个 RDD 的哪些部分被缓存了,
- 以及在各种不同的存储媒介(磁盘、内存等)中所缓存的数据量。
- 执行器页面:应用中的执行器进程列表
- 本页面列出了应用中申请到的执行器实例
- 本页面的用处之一在于确认应用可以使用你所预期使用的全部资源量
- 调试问题时也最好先浏览这个页面,因为错误的配置可能会导致启动的执行器进程数量少于我们所预期的,显然也就会影响实际性能。
- 执行器页面的另一个功能是使用线程转存(Thread Dump)按钮收集执行器进程的栈跟踪信息(该功能在 Spark 1.2 中引入)。
- 在短时间内使用该功能对一个执行器进程进行多次采样,你就可以发现“热点”,也就是用户代码中消耗代价比较大的代码段
- 环境页面:用来调试Spark配置项
- 本页面枚举了你的 Spark 应用所运行的环境中实际生效的配置项集合
数据倾斜是导致性能问题的常见原因之一。
当看到少量的任务相对于其他任务需要花费大量时间的时候,一般就是发生了数据倾斜(即该分区内数据量过大?)
============
日志会更详细地记录各种异常事件
这些数据对于寻找错误原因很有用
Spark 日志文件的默认具体位置取决于部署模式
Spark 的日志系统是基于广泛使用的 Java 日志库 log4j 实现的,使用 log4j 的配置方式进行配置
======================================================================
关键性能考量
=================
并行度
在物理执行期间,RDD 会被分为一系列的分区,每个分区都是整个数据的子集。
当 Spark 调度并运行任务时,Spark 会为每个分区中的数据创建出一个任务
输入 RDD 一般会根据其底层的存储系统选择并行度。
例如,从HDFS 上读数据的输入 RDD 会为数据在 HDFS 上的每个文件区块创建一个分区
从数据混洗后的 RDD 派生下来的 RDD 则会采用与其父 RDD 相同的并行度
评判并行度是否过高的标准
包括任务是否是几乎在瞬间(毫秒级)完成的(分区内数据很少)
或者是否观察到任务没有读写任何数据(分区是空的)
Spark 提供了两种方法来对操作的并行度进行调优
- 在数据混洗操作时,使用参数的方式为混洗后的 RDD 指定并行度
- 对于任何已有的 RDD,可以进行重新分区来获取更多或者更少的分区数
- 重新分区操作通过 repartition() 实现,该操作会把 RDD 随机打乱并分成设定的分区数目。
- 如果你确定要减少 RDD 分区,可以使用coalesce() 操作。由于没有打乱数据,该操作比 repartition() 更为高效
- 例如行 filter() 操作筛选掉数据集中的绝大部分数据时,可以通过合并得到分区更少的 RDD 来提高应用性能
==========
序列化格式
序列化会在数据进行混洗操作时发生(IO)
默认情况下,Spark 会使用 Java 内建的序列化库
使用第三方序列化库 Kryo可以获得了更好的性能(更短的序列化时间、更高的压缩比)
很多 JVM 都支持通过一个特别的选项来帮助调试NotSerializableException情况:"-Dsun.io.serialization.extended DebugInfo=true”。
你可 以 通 过 设 置 spark-submit 的 --driver-java-options 和 --executor-java-options 标 记来打开这个选项。
==========
内存管理
在各个执行器进程中,内存有以下所列几种用途
- RDD存储
- 当调用 RDD 的 persist() 或 cache() 方法时
- Spark 会根据 spark.storage.memoryFraction 限制用来缓存的内存占整个 JVM 堆空间的比例大小。
- 如果超出限制,使用LRU策略将旧的分区数据会被移出内存
- 数据混洗与聚合的缓存区
- Spark 会尝试根据 spark.shuffle.memoryFraction 限定这种缓存区内存占总内存的比例
- 用户代码
- Spark 可以执行任意的用户代码,所以用户的函数可以自行申请大量内存。
- 用户代码可以访问 JVM 堆空间中除分配给 RDD 存储和数据混洗存储以外的全部剩余空间
在默认情况下,Spark 会使用 60%的空间来存储 RDD,20% 存储数据混洗操作产生的数据,剩下的 20% 留给用户程序
用户可以自行调节这些选项来追求更好的性能表现
除了调整内存各区域比例,我们还可以为一些工作负载改进缓存行为的某些要素
例如,有时以 MEMORY_AND_DISK 的存储等级调用 persist() 方法,当RDD 分区的重算代价很大(比如从数据库中读取数据)时,会获得更好的效果。。persist默认是MEMORY_ONLY
对于默认缓存策略的另一个改进是
缓存序列化后的对象而非直接缓存
这可以显著减少 JVM 的垃圾回收时间,因为这种缓存方式会把大量对象序列化为一个巨大的缓存区对象
垃圾回收的代价与堆里的对象数目相关,而不是和数据的字节数相关
我们可以通过MEMORY_ONLY_SER 或者 MEMORY_AND_DISK_SER 的存储等级来实现这一点。
如果你需要以对象的形式缓存大量数据(比如数 GB 的数据),或者是注意到了长时间的垃圾回收暂停,可以考虑配置这个选项
============
硬件供给
影响集群规模的主要参数包括
执行器节点总数,
(典型的CPU、内存、硬盘三大件的性能和容量)分配给每个执行器节点的内存大小、每个执行器节点占用的核心数、以及用来存储临时数据的本地磁盘数量
执行器节点的内存
- 都可以通过 spark.executor.memory 配置项或者spark-submit 的 --executor-memory 标记来设置。
执行器节点的数目以及每个执行器进程的核心数的配置选项则取决于各种部署模式
- 在 YARN 模式下,你可以通过 spark.executor.cores 或 --executor-cores 标 记 来 设 置 执 行 器 节 点 的 核 心 数, 通 过 --numexecutors 设置执行器节点的总数。
在 YARN 模式下,由于 YARN 提供了自己的指定临时数据存储目录的机制,Spark 的本地磁盘配置项会直接从 YARN 的配置中读取。
双倍的资源通常能使应用的运行时间减半。
切记,“越多越好”的原则在设置执行器节点内存时并不一定适用。
- 使用巨大的堆空间可能会导致垃圾回收的长时间暂停,从而严重影响 Spark 作业的吞吐量
- YARN 本身就已经支持在同一个物理主机上运行多个较小的执行器实例
- 还可以用序列化的格式存储数据(参见 8.4.3 节)来减轻垃圾回收带来的影响。