Spark开发性能调优

Spark开发性能调优

标签(空格分隔): Spark


–Write By Vin

1. 分配资源调优

Spark性能调优的王道就是分配资源,即增加和分配更多的资源对性能速度的提升是显而易见的,基本上,在一定范围之内,增加资源与性能的提升是成正比的,当公司资源有限,能分配的资源达到顶峰之后,那么才去考虑做其他的调优

如何分配及分配哪些资源

在生产环境中,提交spark作业时,使用spark-submit shell脚本,里面调整对应的参数
常用参数

/opt/modules/spark-1.6.1-bin-2.5.0-cdh5.3.6/bin/spark-submit \
--class com.hypers.sparkproject.spark.session.UserVisitSessionAnalyzeSpark \
--num-executors 3 \                           --配置executor的数量
--driver-memory  1024M \                      --配置driver的内存,影响不大
--executor-memory 2G \                        --配置每个executor的内存大小
--executor-cores 3 \ --Spark standalone and YARN only --配置每个executor的cpu核数
/usr/loacl/recommend-1.0-SNAPSHOT.jar \

分配多少

  • 第一种:Spark Standalone即Spark运行在自己的分布式框架时,需要知道每台机器能够使用的内存,CPU核数,假如每台机器能够使用4G内存和2个CPU核数,一共20台机器,那么就可以executor数量设置20,每个executor内存设置4G,每个executor设置2 CPU core
  • 第二种: Yarn 当Spark运行在yarn上时,需要查看资源队列有多少资源,假如资源队列有500G内存,100个CPU core可用,那么就可以设置50个executor,每个executor内存设置10G,每个executor设置2个CPU core

总之,就是尽量的去调节到最大的大小(executor的数量和executor的内存)

资源分配是如何影响性能的

当我们从客户端提交一个Spark应用程序时,SparkContext,DAGScheduler,TaskScheduler会将程序中的算子,切分成大量的task,提交到executor上面运行

  • 增加executor数量和executor的CPU核数 : 增加了并行执行能力,加入原来20个executor,每个executor的CPU核数为2个,那么能够并行执行的task数量就是40个task,当在资源允许的情况下增加这两个指标,执行速度将会成倍增加
  • 增加executor的内存 : 增加内存后,对性能的提升主要有三点:
    1. 如果要对RDD进行cache,那么更多的内存就可以缓存更多的数据,将更少的数据写入磁盘,甚至不写入磁盘,减少了磁盘IO
    2. 对于shuffle操作,在reduce端,会需要内存来存放拉取的数据并进行聚合,如果内存不足,也会写入磁盘,如果给executor分配更多的内存,同样减少磁盘IO
    3. 对于task的执行,可能会创建很多对象,如果内存较小,可能会频繁导致JVM堆内存满了,然后频繁GC,垃圾回收,minor GC和full GC,速度很慢

2. 并行度调优

何为并行度

并行度指Spark作业中,各个stage的task数量
为什么要设置并行度 ?
假设,现在已经在spark-submit脚本里面,给spark作业分配了足够多的资源,比如50个executor,每个executor有10G内存,每个executor有3个CPU核。基本已经达到了集群或者yarn队列的资源上限。但是task没有设置,或者设置的很少,比如就设置了,100个task。50个executor,每个executor有3个cpu核,也就是说,你的Application任何一个stage运行的时候,都有总数在150个cpu核,可以并行运行。但是你现在,只有100个task,平均分配一下,每个executor分配到2个task,那么同时在运行的task,只有100个,每个executor只会并行运行2个task。每个executor剩下的一个cpu核,就浪费掉了。所以你的资源虽然分配足够了,但是问题是,并行度没有与资源相匹配,导致你分配下去的资源都浪费掉了。

合理的设置并行度

理想情况下,task数量设置成Spark Application 的总CPU核数,但是现实中总有一些task运行慢一些task快,导致快的先执行完,空余的cpu 核就浪费掉了,所以官方推荐task数量要设置成Spark Application的总cpu核数的2~3 倍

如何设置并行度

SparkConf conf = new SparkConf()
  .set("spark.default.parallelism", "500")

3. 重构RDD架构及RDD持久化 序列化

  • RDD架构重构与优化
    默认情况下,多次对一个RDD执行算子,去获取不同的RDD,都会对这个RDD以及之前的父RDD全部重新计算一次,在实际项目中,一定要避免出现一个RDD重复计算的情况, 所以,要尽量去复用RDD,差不多的RDD可以抽取为一个共同的RDD,供后面的RDD计算时反复使用
  • 公共RDD实现持久化
    对于要多次计算和使用的公共RDD,一定要进行持久化
    持久化:也就是说,将RDD的数据缓存在内存中/磁盘中,(BlockManager),之后无论对这个RDD做多少次计算,都是直接取这个RDD的持久化的数据
  • 持久化数据序列化
    如果正常将数据持久化在内存中,那么可能会导致内存的占用过大,会导致OOM,当纯内存无法支撑公共RDD数据完全存放的时候,就需要优先考虑使用序列化的方式在纯内存中存储,将RDD的每个partition的数据,序列化成一个大的字节数组,就一个对象,序列化后,大大减少内存的空间占用
    序列化的唯一缺点就是在获取数据的时候需要反序列化, 如果序列化后纯内存的方式还导致OOM,就只能考虑内存+无序列化的普通方式
  • 持久化+双副本机制
    为了数据的高可靠性,而且内存充足,可以使用双副本机制进行持久化
    持久化的双副本机制,持久化后的一个副本,因为机器宕机了,副本丢了,就还是得重新计算一次;持久化的每个数据单元,存储一份副本,放在其他节点上面;从而进行容错;一个副本丢了,不用重新计算,还可以使用另外一份副本。

4. 广播变量

广播变量:Broadcast,将大变量广播出去,而不是直接使用

  • 为什么要用Broadcast
    当进行随机抽取一些操作,或者从某个表里读取一些维度的数据,比如所有商品品类的信息,在某个算子函数中要使用到,加入该数据大小为100M,那么1000个task将会消耗100G的内存, 集群损失不可估量
  • Broadcast的原理
    默认的情况下,每个task执行的算子中,使用到了外部的变量,每个task都会获取一份变量的副本,所以会消耗很多的内存,进而导致RDD持久化内存不够等情况,大大影响执行速度
    广播变量,在driver上会有一份初始的副本,task在运行的时候,如果要使用广播变量中的数据,首先会在自己本地的Executor对应的BlockManager中尝试获取变量副本,并保存在本地的BlockManager中,此后这个Executor上的所有task,都会直接使用本地的BlockManager中的副本,Executor的BlockManager除了从driver上拉取,也可能从其他节点的BlockManager上拉取变量副本,距离越近越好.
    总而言之: 广播变量的好处不是每一个task一份变量副本,而是变成每个节点的executor才一份副本,这样的话就可以变量产生的副本大大减少

5. Kryo序列化的使用

什么是序列化

默认情况下,Spark内部是使用java的序列化机制ObjectInputStream/ObjectOutputStream,即对象输入输出流机制来进行序列化, 这种序列化机制的好处在于,处理起来比较方便,只需在在算子里面使用的变量,必须是实现Serializable接口的,可序列化即可,但是这种默认的序列化机制的效率不高,序列化速度慢,序列化以后的数据占用的内存空间相对很大
Spark支持使用Kryo序列化机制,比默认的java序列化机制速度要快很多,而且序列化后的数据大小大概是java序列化机制的1/10
Kryo序列化机制,一旦启用,会生效的几个地方

  • 算子函数中使用的外部变量
    算子函数中使用的外部变量,在经过kryo序列化之后,会优化网络传输的性能,优化集群中内存的占用和消耗

  • 持久化RDD时进行序列化,StorageLevel.MEMORY_ONLY_SER
    持久化RDD的时候,优化内存的占用和消耗

  • shuffle
    优化网络传输的性能

在Spark程序中如何使用序列化

  • 第一步: 在SparkConf中设置一个属性
SparkConf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
  • 第二步: 注册需要使用Kryo序列化的自定义的类
    如果要达到Kryo的最佳性能的话,那么就一定要注册自定义的类
.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
.registerKryoClasses(new Class[]{CategorySortKey.class})

6.fastutil

fastutil 是扩展了java标准集合框架(Map,List,Set,HashMap,ArrayList,HashSet)的类库,提供了特殊类型的Map,Set,List和queue,fastutil能够提供更小的内存占用,更快的存取速度,fastutil也提供了64位的array、set和list,以及高性能快速的,以及实用的IO类,来处理二进制和文本类型的文件;
fastutil最新版本要求Java 7以及以上版本
Spark中fastutil应用场景:

  • 1.如果算子函数使用了外部变量,那么可以用三步来优化: a.使用Broadcast广播变量优化, b. 使用Kryo序列化类库优化,提升性能和效率,c.如果外部变量是某种比较大的集合,可以使用fastutil改写外部变量
  • 2.在算子函数中,如果要创建比较大的Map.List等集合,可以考虑将这些集合类型使用fastutil类库重写

fastutil的使用:

<dependency>
    <groupId>fastutil</groupId>
    <artifactId>fastutil</artifactId>
    <version>5.0.9</version>
</dependency>

7.数据本地化等待时长

什么是本地化等待时长

Spark在Driver上,对Application的每一个stage的task,在进行分配之前,都会计算出每个task要计算的是哪个分片的数据也即是RDD的某个partition.Spark的task分配算法优先会希望每个task正好分配到它要计算的数据所在的节点,这样就避免了网络间传输数据
但是,task可能没有机会分配到它的数据所在的节点,因为可能计算资源和计算能力都满了,这种情况下,Spark会等待一段时间,过了这个时间,才会选择一个比较差的本地化级别,比如将这个task分配到相邻的一个节点上,这个时候肯定发生网络传输,会通过一个getRemote()方法,通过TransferService(网络数据传输组件)从数据所在节点的BlockManager中获取数据,上述中的一段时间即为本地化等待时长

如何调节本地化等待时长

PROCESS_LOCAL:进程本地化,代码和数据在同一个进程中,也就是在同一个executor中;计算数据的task由executor执行,数据在executor的BlockManager中;性能最好
NODE_LOCAL:节点本地化,代码和数据在同一个节点中;比如说,数据作为一个HDFSblock块,就在节点上,而task在节点上某个executor中运行;或者是,数据和task在一个节点上的不同executor中;数据需要在进程间进行传输
NO_PREF:对于task来说,数据从哪里获取都一样,没有好坏之分
RACK_LOCAL:机架本地化,数据和task在一个机架的两个节点上;数据需要通过网络在节点之间进行传输
ANY:数据和task可能在集群中的任何地方,而且不在一个机架中,性能最差

spark.locality.wait,默认是3s

在使用client模式测试时,在本地就可以看到比较全的日志,日志里面会显示:
image_1bh8t33kl1k5r1veui9u1cjr1bbd9.png-80.2kB
如果大多都是PROCESS_LOCAL,那就不用调节了
如果是发现,好多的级别都是NODE_LOCAL、ANY,那么最好就去调节一下数据本地化的等待时长
调节完,应该是要反复调节,每次调节完以后,再来运行,观察日志
看看大部分的task的本地化级别有没有提升;看看,整个spark作业的运行时间有没有缩短
调节方法:

spark.locality.wait,默认是3s;可以调节为6s,10s
默认情况下,下面3个的等待时长,都是跟上面那个是一样的,都是3s
spark.locality.wait.process
spark.locality.wait.node
spark.locality.wait.rack

new SparkConf()
  .set("spark.locality.wait", "10")

8.JVM调优

JVM体系结构

image_1bh909ge5fl315j1d7916rt3241g.png-240.3kB

  • CLASS FILES : scala或者java类程序
  • CLASS LOADER : 类加载子系统
  • RUNTIME DATA AREA : 运行数据域组件

    • Java Stack(栈)
    • HEAP (堆)
    • Method Area(方法区)
    • PC Register(程序计数器)
    • Native Method Stack (本地方法栈)
    • <
  • 18
    点赞
  • 140
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值