spark调优

目录

1.官网硬件配置建议

2.官网优化建议

1.数据序列化优化

2.确定内存消耗大小的方法

3.结构优化

4.gc优化

1.度量GC的影响

2.GC优化

5.并行度设置

6.reduce任务的内存使用

7.广播变量

8.数据局部性

3.spark参数

1.num-executors

2.executor-memory

3.executor-cores

4.driver-memory

5.spark.default.parallelism

6.参数参考示例


1.官网硬件配置建议

官网建议

Hardware Provisioning - Spark 3.3.0 Documentation

  1. spark和hdsf最好在同一个节点上,如果不在同一个节点上也最好在同一个局域网
  2. 虽然spark使用内存计算,但仍会使用本地磁盘来存储RAM中无法容纳的数据,并在各个阶段之间保留中间输出,所以建议每个节点使用4-8个磁盘,不配置RAID。
  3. 每个节点内存建议8G及以上,推荐分配给spark的内存不超过75%,,留一些内存给系统和buffer缓存用。
  4. 很多Spark应用程序都是网络绑定的。使用10gb或更高的网络是使这些应用程序更快的最佳方法。 对于“分布式reduce”应用程序(如group-by、reduce-by和SQL连接)来说尤其如此。可以从应用程序的监控UI (http://:4040)中看到有多少数据通过Spark在网络上传输。
  5. 每个节点至少提供8-16个cores

2.官网优化建议

官网建议

Tuning - Spark 3.3.0 Documentation

1.数据序列化优化

序列化在分布式应用的性能中扮演重要角色,序列化对象慢或占用大量字节的格式将会严重影响计算速度。spark提供了2个序列化库

Java serialization:spark默认使用Java serialization,

Kryo serialization:更快,更紧凑,这个性能是Java serialization10倍,但不支持所有的Serializable类型。

2.确定内存消耗大小的方法

  1. 查看web ui的storage
  2. 使用SizeEstimator的estimate方法,这个方法对于试验不同的数据布局以减少内存使用,以及确定广播变量将在每个执行器堆上占用的空间量非常有用

3.结构优化

  1. 避免使用嵌套结构,尽量使用基本类型
  2. 考虑使用数值id而不是字符串作为键

4.gc优化

1.度量GC的影响

度量GC的影响GC调优的第一步是收集关于垃圾收集发生的频率和花在GC上的时间的统计信息。 这可以通过添加-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps到Java选项来完成,下次运行spark作业时,日志会输出到worker节点的stdout而不是驱动程序那里。

2.GC优化

1.JVM内存管理

java堆空间分为2个区域,年轻和年老,年轻用于保存寿命短的对象,年老保存寿命长的对象。

年轻区域分为Eden, Survivor1, Survivor2。

垃圾回收过程可以简单描述为:当Eden被填满时,在Eden上运行一个小型GC,并且从Eden和Survivor1中存活的对象被复制到Survivor2中。幸存者区域被交换。如果一个对象足够老或Survivor2已满,它将被移动到old。最后,当Old接近饱和时,将调用一个完整的GC

2.GC调优的目标

spark GC的调优的目标是确保只有长生命周期的存储在老的区域,新的区域有足够的空间存储短生命周期的对象

3.GC调优的方法

  1. 收集GC数据检查是否有太多的GC次数,如果任务完成之前有多次GC,说明没有足够的内存用于执行任务。
  2. 在打印的GC统计中,如果OldGen接近满,通过降低spark.memory.fraction来减少用于缓存的内存数量;缓存更少的对象比降低任务执行速度要好
  3. 尝试使用-XX:+UseG1GC的G1GC垃圾收集器。在垃圾收集成为瓶颈的某些情况下,它可以提高性能。请注意,对于较大的执行程序堆大小,使用-XX:G1HeapRegionSize增加G1区域大小可能很重要
  4. 执行器的GC调优标志可以通过在作业配置中设置spark.executor.defaultJavaOptions或spark.executor.extraJavaOptions来指定。

5.并行度设置

除非你为每一个操作设置了最高并行度,否则不会利用集群中所有的节点。spark会根据每个文件的大小自动设置每个文件上运行的map任务的数量,或者设置属性spark.default.parallelism来更改默认值,通常建议每个cpu core设置2-3个tasks

有时,当作业输入有大量目录时,可能还需要增加目录列表并行度

6.reduce任务的内存使用

spark在执行reduce的时候遇到OutOfMemoryError,这并非因为RDDs大小超过了可用内存,而是因为你的任务集合中的某一个任务,比如groupByKey这样的Reduce操作,他太大了。

Spark的混洗操作(sortByKey,groupByKey,reduceByKey等)会为每一个任务创建一个哈希表来执行分组,这个哈希表通常会很大。最简单的解决此类错误的方式就是提高并行度,提高了并行度每一个该操作的任务的输入集就会变小。

Spark可以有效的支持任务在200ms结束,因为他在很多的任务之间重复使用了执行器的JVM,并且有着较低的任务启动花费,所以你能够安全的提升并行度的值,使其大于整个集群的内核数

7.广播变量

使用SparkContext中可用的广播功能可以大大减少每个序列化任务的大小,以及在集群中启动一个任务的成本。

如果你的任务使用了它们内部驱动程序中的任何大对象(例如静态查找表),考虑将其转换为广播变量。 Spark在主服务器上打印每个任务的序列化大小,因此您可以通过查看来决定您的任务是否太大; 一般来说,大于20kib的任务可能值得优化。

8.数据局部性

数据局部性对Spark作业的性能有着巨大的影响。如果数据和代码在同一个结点上那么计算就会变的很快。但是如果代码和数据是分开的,那么其中一个要移动到另一个所在的结点。

通常,移动序列化后的代码比移动数据块要快的多,这是因为代码的大小远远小于数据块的大小。Spark在调度作业的时候遵守数据局部性的一般原则。

数据局部性表示数据与处理他的程序之间的距离。这里有几种基于数据当前位置的局部化的级别。顺序是由近至远:

  • PROCESS_LOCAL数据和处理程序在同一个JVM中,这是最好的情况
  • NODE_LOCAL数据和处理程序在同一个结点上。比如数据在HDFS的一个结点上,同时一个执行器也在同一个结点上。这比PROCESS_LOCAL要慢一些,因为数据要在进程之间转移
  • NO_PREF在任何地方访问数据都是一样的,没有任何局部性的优先权 RACK_LOCAL数据和程序位于同一个机架上的不同机器上,因此数据需要通过网络进行传输,典型地通过一个单开关
  • ANY数据可以在网络中的任何位置,但与处理程序不在同一个机架上。

Spark优先将所有任务安排到最好的局部性级别上,但是这并不总是可能的。在某些情况下,一些空闲的执行器上没有未处理的数据了,Spark会切换较低的局部性等级。这时候有两个选项:

  1. 等待非空闲并且有未处理数据的执行器上的繁忙的CPU空闲的时候,在数据所在的服务器上启动一个新的任务
  2. 立刻在空闲的执行上启动1个新的任务然后请求移动数据。

Spark会选择哪一个呢?Spark先选择1选项,等待繁忙的CPU一段期望的时间,如果这段时间后,CPU仍然繁忙,那么Spark就会选择2选项

3.spark参数

1.num-executors

参数说明:该参数用于设置Spark作业总共要用多少个Executor进程来执行。Driver在向YARN集群管理器申请资源时,YARN集群管理器会尽可能按照你的设置来在集群的各个工作节点上,启动相应数量的Executor进程。这个参数非常之重要,如果不设置的话,默认只会给你启动少量的Executor进程,此时你的Spark作业的运行速度是非常慢的。

参数调优建议:每个Spark作业的运行一般设置50~100个左右的Executor进程比较合适,设置太少或太多的Executor进程都不好。设置的太少,无法充分利用集群资源;设置的太多的话,大部分队列可能无法给予充分的资源。

2.executor-memory

参数说明:该参数用于设置每个Executor进程的内存。Executor内存的大小,很多时候直接决定了Spark作业的性能,而且跟常见的JVM OOM异常,也有直接的关联。

参数调优建议:每个Executor进程的内存设置4G/8G较为合适。

但是这只是一个参考值,具体的设置还是得根据不同部门的资源队列来定。可以看看自己团队的资源队列的最大内存限制是多少,num-executors乘以executor-memory,是不能超过队列的最大内存量的。此外,如果你是跟团队里其他人共享这个资源队列,那么申请的内存量最好不要超过资源队列最大总内存的1/3或者1/2,避免你自己的Spark作业占用了队列所有的资源,导致别的同学的作业无法运行。

注意事项:yarn分配给excutor容器的内存比executor-memory大,因为容器的内存=executor-memory + spark.executor.memoryOverhead

spark.executor.memoryOverhead:默认值executor-memor*0.1,最小为384m

spark.driver.memoryOverhead:默认值spark.driver.memory*0.1,最小为384m

yarn分配container的大小为yarn.scheduler.minimum-allocation-mb的倍数

spark在yarn上的allocated memory计算方式

total memory = (executor-memory + spark.executor.memoryOverhead) * num-executors + (spark.driver.memory + spark.driver.memoryOverhead)

举个例子

executor-memory为8g,spark.driver.memory为4g,yarn.scheduler.minimum-allocation-mb=6g

那么excutor中的container内存为8+8X0.1<2X6,所以最小申请12g

driver内存计算为:4g+4X0.1<6g,所以最小申请6g

所有如果启动15个excutor的总内存消耗 12*15+6=186g

分配给excutor的container内存是12g,任务不一定会把这12g占用完,在executor生成前后使用free -g我们可以发现可能任务使用的内存很少,所以我们分配内存的时候executor不要分配的过大,yarn分配的executor时候是按每个节点能执行的最多的container分配的,这样就造成了很大的浪费。

另外还存在一种浪费就是executor-memory + spark.executor.memoryOverhead小于yarn.scheduler.minimum-allocation-mb的倍数。像上面我们就浪费了12-(8+8X0.1),,所以executor-memory + spark.executor.memoryOverhead要尽量的贴近yarn.scheduler.minimum-allocation-mb的倍数大小,不然就存在了内存资源的浪费。

我们可以通过查看节点上的executor进程信息看到内存的参数配置,ps -ef|grep appID

查看 -Xmx显示多少就是最大能使用多少内存,如果executor-memory没有写会使用默认的配置1g,即使yarn给每个容器分配6g,在服务器执行的进程上面的-Xmx显示也只有1g

3.executor-cores

参数说明:该参数用于设置每个Executor进程的CPU core数量。这个参数决定了每个Executor进程并行执行task线程的能力。因为每个CPU core同一时间只能执行一个task线程,因此每个Executor进程的CPU core数量越多,越能够快速地执行完分配给自己的所有task线程。

参数调优建议:Executor的CPU core数量设置为24个较为合适。同样得根据不同部门的资源队列来定,可以看看自己的资源队列的最大CPU core限制是多少,再依据设置的Executor数量,来决定每个Executor进程可以分配到几个CPU core。同样建议,如果是跟他人共享这个队列,那么num-executors * executor-cores不要超过队列总CPU core的1/31/2左右比较合适,也是避免影响其他同学的作业运行。

查看任务用了多少核 进入history web点击excutors,看cores显示多少就是用了多少,在yarn上看到的cpus可能不准 查看每个excutors使用了几个核

我们可以看excutor并发执行的task数,进入history web点击stages,随便点击一个stage再点击event timeline按钮就可以看到并发执行的task数了 或者进到执行任务的节点ps -ef|grep appID查看进程信息上面会有个 --cores就可以看到使用了几个core

4.driver-memory

参数说明:该参数用于设置Driver进程的内存。

参数调优建议:Driver的内存通常来说不设置,或者设置1G左右应该就够了。唯一需要注意的一点是,如果需要使用collect算子将RDD的数据全部拉取到Driver上进行处理,那么必须确保Driver的内存足够大,否则会出现OOM内存溢出的问题。

5.spark.default.parallelism

参数说明:该参数用于设置每个stage的默认task数量。这个参数极为重要,如果不设置可能会直接影响你的Spark作业性能。spark.default.parallelism只有在处理RDD时有效,spark.sql.shuffle.partitions则是只对SparkSQL有效。

参数调优建议:官网给的建议是设置为当前spark job的总core数量的2~3倍.。

假设我们给当前Spark job设置总Core数为 100, 那么依据1 core 1 task, 当前spark集群中最多并行运行100task任务, 那么通过设置上述两个参数为100, 使得我们结果RDD的分区数为100, 一个分区 1task 1core, 完美! 但是实际生产中会有这样的情况, 100个task中有些task的处理速度快, 有些处理慢, 假设有20个task很快就处理完毕了, 此时就会出现 我们集群中有20个core处理闲置状态, 不符合spark官网所说的最大化压榨集群能力.  而如果我们设置上述参数值为199, 此时的现象: 虽然集群能并行处理199个task, 奈何总core只有100, 所以会出现有99个task处于等待处理的情况. 处理较快的那20task闲置下来的20个core就可以接着运行99个中的20个task, 这样就最大化spark集群的计算能力

6.参数参考示例

spark-sql -f xxx.spark --master yarn --queue queueName --num-executors 50 --executor-memory 4g --executor-cores 4 --driver-memory 4g --driver-cores 2 --total-executor-cores 200 --conf spark.port.maxRetries=128 --conf spark.sql.shuffle.partitions=500 --spark.yarn.priority=10

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值