spark的性能优化

倾然丶 夕夏残阳落幕 提交于 2020-03-09 13:13:05

spark性能优化点

一、分配更多的资源

 它是性能优化调优的王道,就是增加和分配更多的资源,这对于性能和速度上的提升是显而易见的,      基本上,在一定范围之内,增加资源与性能的提升,是成正比的;写完了一个复杂的spark作业之后,进行性能调 优的时候,首先第一步,就是要来调节优的资源配置;在这个基础之上,如果说你的spark作业,能够分配的资源达到 了你的能力范围的顶端之后,无法再分配更多的资源了,公司资源有限;那么才是考虑去做后面的这些性能调优的点。

1、分配哪些资源

executor-memory、executor-cores、driver-memory

2、在哪里设置

在实际的生产环境中,提交spark任务时,使用spark‐submit shell脚本,在里面调整对应的参数。     

提交任务的脚本:  spark‐submit \

‐‐master spark://node1:7077 \

‐‐class cn.itcast.WordCount \

‐‐num‐executors 3 \    配置executor的数量

‐‐driver‐memory 1g \   配置driver的内存(影响不大)

‐‐executor‐memory 1g \ 配置每一个executor的内存大小

‐‐executor‐cores 3 \   配置每一个executor的cpu个数

/export/servers/wordcount.jar 

3、参数调节到多大,算是最大

 第一种情况:standalone模式

   先计算出公司spark集群上的所有资源 每台节点的内存大小和cpu核数,       比如:一共有20台worker节点,每台节点8g内存,10个cpu。       实际任务在给定资源的时候,可以给20个executor、每个executor的内存8g、每个executor的使用的cpu个数 10。

第二种情况:Yarn

   先计算出yarn集群的所有大小,比如一共500g内存,100个cpu;       这个时候可以分配的大资源,比如给定50个executor、每个executor的内存大小10g,每个executor使用的cpu 个数为2。              使用原则:你能使用的资源有多大,就尽量去调节到大的大小(executor的数量:几十个到上百个不等;executor的 内存;exector的cpu个数)

 

二、提高并行度

1、spark的并行度指的是什么?

spark作业中,各个stage的task的数量,也就代表了spark作业在各个阶段stage的并行度!   

 当分配完所能分配的大资源了,然后对应资源去调节程序的并行度,如果并行度没有与资源相匹配,那么导致你 分配下去的资源都浪费掉了。同时并行运行,还可以让每个task要处理的数量变少(很简单的原理。合理设置并行度, 可以充分利用集群资源,减少每个task处理数据量,而增加性能加快运行速度。

2、提高并行度的三种方式

(1)、设置task的数量

至少设置成与spark Application 的总cpu core 数量相同(理想情况,150个core,分配150task,一起运 行,差不多同一时间运行完毕)官方推荐,task数量,设置成spark Application 总cpu core数量的2~3倍 。      比如150个cpu core ,基本设置task数量为300~500. 与理想情况不同的,有些task会运行快一点,比如50s就完 了,有些task 可能会慢一点,要一分半才运行完,所以如果你的task数量,刚好设置的跟cpu core 数量相同,可能会 导致资源的浪费。      因为比如150个task中10个先运行完了,剩余140个还在运行,但是这个时候,就有10个cpu core空闲出来了,导 致浪费。如果设置2~3倍,那么一个task运行完以后,另外一个task马上补上来,尽量让cpu core不要空闲。同时尽量 提升spark运行效率和速度。提升性能

 (2)、给RDD重新设置partition的数量

使用rdd.repartition 来重新分区,该方法会生成一个新的rdd,使其分区数变大。 此时由于一个partition对应一个task,那么对应的task个数越多,通过这种方式也可以提高并行度

(3)、提高sparksql运行的task数量

通过设置参数 spark.sql.shuffle.partitions=500  默认为200; 可以适当增大,来提高并行度。 比如设置为 spark.sql.shuffle.partitions=500

三、RDD的重用和持久化

1、实际开发遇到的情况说明

 

如上图所示的计算逻辑:

(1)当第一次使用rdd2做相应的算子操作得到rdd3的时候,就会从rdd1开始计算,先读取HDFS上的文件,然后对rdd1 做对应的算子操作得到rdd2,再由rdd2计算之后得到rdd3。同样为了计算得到rdd4,前面的逻辑会被重新计算。

(2)默认情况下多次对一个rdd执行算子操作,去获取不同的rdd,都会对这个rdd及之前的父rdd全部重新计算一次。 这种情况在实际开发代码的时候会经常遇到,但是我们一定要避免一个rdd重复计算多次,否则会导致性能急剧降低。   

总结:可以把多次使用到的rdd,也就是公共rdd进行持久化,避免后续需要,再次重新计算,提升效率。

2、如何对rdd进行持久化

可以调用rdd的cache或者persist方法。

(1)cache方法默认是把数据持久化到内存中 ,例如:rdd.cache ,其本质还是调用了persist方法

(2)persist方法中有丰富的缓存级别,这些缓存级别都定义在StorageLevel这个object中,可以结合实际的应用场 景合理的设置缓存级别。例如: rdd.persist(StorageLevel.MEMORY_ONLY),这是cache方法的实现。

3、rdd持久化时可以采用序列化

(1)如果正常将数据持久化在内存中,那么可能会导致内存的占用过大,这样的话,也许会导致OOM内存溢出。

(2)当纯内存无法支撑公共RDD数据完全存放的时候,就优先考虑使用序列化的方式在纯内存中存储。将RDD的每个 partition的数据,序列化成一个字节数组;序列化后,大大减少内存的空间占用。

(3)序列化的方式,唯一的缺点就是,在获取数据的时候,需要反序列化。但是可以减少占用的空间和便于网络传输

(4)如果序列化纯内存方式,还是导致OOM,内存溢出;就只能考虑磁盘的方式,内存+磁盘的普通方式(无序列化)。

(5)为了数据的高可靠性,而且内存充足,可以使用双副本机制,进行持久化

  持久化的双副本机制,持久化后的一个副本,因为机器宕机了,副本丢了,就还是得重新计算一次;     

  持久化的每个数据单元,存储一份副本,放在其他节点上面,从而进行容错;     

  一个副本丢了,不用重新计算,还可以使用另外一份副本。这种方式,仅仅针对你的内存资源极度充足。     

   比如: StorageLevel.MEMORY_ONLY_2

 4、广播变量的使用

场景描述

 

 

广播变量介入

Spark中分布式执行的代码需要传递到各个executor的task上运行。对于一些只读、固定的数据,每次都需要Driver 广播到各个Task上,这样效率低下。广播变量允许将变量只广播(提前广播)给各个executor。该executor上的各 个task再从所在节点的BlockManager(负责管理某个executor对应的内存和磁盘上的数据)获取变量,而不是从 Driver获取变量,从而提升了效率

 

广播变量,初始的时候,就在Drvier上有一份副本。通过在Driver把共享数据转换成广播变量。   task在运行的时候,想要使用广播变量中的数据,此时首先会在自己本地的Executor对应的BlockManager中,尝 试获取变量副本;如果本地没有,那么就从Driver远程拉取广播变量副本,并保存在本地的BlockManager中;           此后这个executor上的task,都会直接使用本地的BlockManager中的副本。那么这个时候所有该executor中的 task都会使用这个广播变量的副本。也就是说一个executor只需要在第一个task启动时,获得一份广播变量数据,之后 的task都从本节点的BlockManager中获取相关数据。        executor的BlockManager除了从driver上拉取,也可能从其他节点的BlockManager上拉取变量副本,网络距离 越近越好

使用广播变量的后的性能分析

比如一个任务需要50个executor,1000个task,共享数据为100M。

(1)在不使用广播变量的情况下,1000个task,就需要该共享数据的1000个副本,也就是说有1000份数需要大量的网络 传输和内存开销存储。耗费的内存大小1000*100=100G.   

(2)使用了广播变量后,50个executor就只需要50个副本数据,而且不一定都是从Driver传输到每个节点,还可能是就 近从近的节点的executor的blockmanager上拉取广播变量副本,网络传输速度大大增加;内存开销 50*100M=5G   

总结: 不使用广播变量的内存开销为100G,使用后的内存开销5G,这里就相差了20倍左右的网络传输性能损耗和内存开 销,使用广播变量后对于性能的提升和影响,还是很可观的。广播变量的使用不一定会对性能产生决定性的作用。比如运行30分钟的spark作业,可能做了广播变量以后,速度 快了2分钟,或者5分钟。但是一点一滴的调优,积少成多。后还是会有效果的。

如何使用广播变量

例如: (1) 通过sparkContext的broadcast方法把数据转换成广播变量,类型为Broadcast,

            val broadcastArray: Broadcast[Array[Int]] = sc.broadcast(Array(1,2,3,4,5,6))         

    (2) 然后executor上的BlockManager就可以拉取该广播变量的副本获取具体的数据。

            获取广播变量中的值可以通过调用其value方法           

            val array: Array[Int] = broadcastArray.value

四、使用kryo序列化 

1、kryo序列化介绍

Spark在进行任务计算的时候,会涉及到数据跨进程的网络传输、数据的持久化,这个时候就需要对数据进行序列 化。Spark默认采用Java的序列化器。默认java序列化的优缺点如下:     

其好处: 处理起来方便,不需要我们手动做其他操作,只是在使用一个对象和变量的时候,需要实现Serializble接口。     

其缺点: 默认的序列化机制的效率不高,序列化的速度比较慢;序列化以后的数据,占用的内存空间相对还是比较大。       

Spark支持使用Kryo序列化机制。Kryo序列化机制,比默认的Java序列化机制,速度要快,序列化后的数据要更小,大 概是Java序列化机制的1/10。所以Kryo序列化优化以后,可以让网络传输的数据变少;在集群中耗费的内存资源大大减 少

2、Kryo序列化启用后生效的地方

Kryo序列化机制,一旦启用以后,会生效的几个地方:

(1)算子函数中使用到的外部变量 算子中的外部变量可能来着与driver需要涉及到网络传输,就需要用到序列化。         

终可以优化网络传输的性能,优化集群中内存的占用和消耗               

(2)持久化RDD时进行序列化,StorageLevel.MEMORY_ONLY_SER 将rdd持久化时,对应的存储级别里,需要用到序列化。         

终可以优化内存的占用和消耗;持久化RDD占用的内存越少,task执行的时候,创建的对象,就不至于频繁 的占满内存,频繁发生GC。               

(3) 产生shuffle的地方,也就是宽依赖   下游的stage中的task,拉取上游stage中的task产生的结果数据,跨网络传输,需要用到序列化。         

终可以优化网络传输的性能

3、 如何开启Kryo序列化机制

(1)在构建sparkConf的时候设置相关参数 new SparkConf().set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")         

 Kryo之所以没有被作为默认的序列化类库的原因,主要是因为Kryo要求如果要达到它的佳性能的话,那么就一定 要注册你自定义的类(比如,你的算子函数中使用到了外部自定义类型的对象变量,这时,就要求必须注册你的类,否 则Kryo达不到佳性能)。

Kryo也不支持所有实现了 java.io.Serializable 接口的类型,它需要你在程序中 register 需要序列化的类 型,以得到佳性能。         

(2)注册需要通过Kryo序列化的一些自定义类 new SparkConf().registerKryoClasses(Array(classOf[Student]))     

该方法需要一个Class类型的数组,表示可以一下子注册多个需要实现Kryo序列化的类

五、使用fastutil优化数据格式

1、fastutil介绍

fastutil是扩展了Java标准集合框架(Map、List、Set;HashMap、ArrayList、HashSet)的类库,提供了特殊类型 的map、set、list和queue;   fastutil能够提供更小的内存占用,更快的存取速度;我们使用fastutil提供的集合类,来替代自己平时使用的JDK的 原生的Map、List、Set,

2、好处

fastutil集合类,可以减小内存的占用,并且在进行集合的遍历、根据索引(或者key)获取元素的值和设置元素的值 的时候,提供更快的存取速度;   fastutil也提供了64位的array、set和list,以及高性能快速的,以及实用的IO类,来处理二进制和文本类型的文 件;  fastutil新版本要求Java 7以及以上版本;  fastutil的每一种集合类型,都实现了对应的Java中的标准接口(比如fastutil的map,实现了Java的Map接口),因 此可以直接放入已有系统的任何代码中。  fastutil还提供了一些JDK标准类库中没有的额外功能(比如双向迭代器)。  fastutil除了对象和原始类型为元素的集合,fastutil也提供引用类型的支持,但是对引用类型是使用等于号(=)进 行比较的,而不是equals()方法。  fastutil尽量提供了在任何场景下都是速度快的集合类库

3、关于fastutil调优的说明

fastutil其实没有你想象中的那么强大,也不会跟官网上说的效果那么一鸣惊人。广播变量、Kryo序列化类库、 fastutil,都是之前所说的,对于性能来说,类似于一种调味品,烤鸡,本来就很好吃了,然后加了一点特质的孜然麻辣 粉调料,就更加好吃了一点。   分配资源、并行度、RDD架构与持久化,这三个就是烤鸡;  broadcast、kryo、fastutil,类似于调料。   比如说,你的spark作业,经过之前一些调优以后,大概30分钟运行完, 现在加上broadcast、kryo、fastutil,也许就是优化到29分钟运行完、或者更好一点,也许就是28分钟、25分钟。   真正有意义的就是后面要学习的shuffle调优,可能优化之后只需要15分钟; 还有把groupByKey用reduceByKey改写,执行本地聚合,也许10分钟;   甚至可以向公司申请更多的资源,扩大整个集群的计算能力,后可能到达5分钟就完成任务了

 

此外还可以调节数据本地化等待时长、降低cache操作的内存占比等

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!