스칼라 성능 최적화

스파크 성능 최적화 점

첫째, 더 많은 자원을 할당

  이 증가하고 더 많은 자원을 할당하는 성능 최적화 튜닝 왕이다, 그것은 성능과 속도를 업그레이드 명백하다,

  실질적으로 일정 범위 내에서 자원 성능을 증가시키고 개선는 비례, 성능 조정이 첫 번째 단계는 자원의 최적 할당을 조정할 때, 스파크 복잡한 작업 완료 후 퇴근 후 당신의 불꽃이, 자원 능력 범위의 상단에 도달 할당 할 수 있으며, 더 많은 자원, 제한된 회사의 자원을 할당 할 수없는 경우이 기준으로,; 이렇게이 뒤에 간주됩니다 성능 튜닝 포인트.

 얼마나 큰 1.1 매개 변수 조정은 가장 큰입니까?

   첫 번째 경우 : 독립 실행 형 모드

    먼저 클러스터 회사와 스파크 CPU 감사에 각 노드의 모든 자원의 메모리 크기를 계산,

    예를 들어 노드 (20) 작업자, 각 노드 8g 메모리, CPU (10)의 총.

    주어진 자원의 실제 태스크는, 각각의 실행 프로그램 (10)에 의해 사용되는 실행 프로그램 (20), CPU의 수를 각각 실행 프로그램 메모리 8g을 줄 수있다.

  두 번째 경우 : 얀 최초 총 메모리 500g, CPU (100) 등 모든 사 클러스터의 크기를 계산하게하고;

    큰 자원이 그러한 소정 실행 프로그램 (50), 각각의 실행 프로그램 10g의 메모리 크기, 사용되는 각각의 실행 프로그램이 2 CPU의 수로서,이 때 할당 될 수있다.

    사용의 원칙 : 자원은 많이 사용할 수있는, 그들은 큰 크기로 조정하려고 (집행 번호 : 수십에서 수백에 이르는, 메모리의 집행을, CPU의 exector의 수)

 1.2 후 왜 성능을 향상시키기 위해 많은 자원을 전송할 수 있습니다

 

 병렬 둘째, 정도

 2.1 무엇을 의미하는지의 병렬 불꽃

  스파크 작업, 각 단계의 작업의 수, 병렬 처리 단계의 모든 단계에서 스파크 작업을 나타냅니다!

  완성 된 할당하고 프로그램에 대응하는 자원의 병렬 처리를 조정할 수 있습니다 많은 자원을 할당 할 때 병렬 처리 수준이 자원과 일치하지 않을 경우, 다음 낭비 가야 자원의 할당로 이어집니다. 한편 병렬로 실행뿐만 아니라, 각 작업의 수가 적은 처리 할 수 ​​있습니다 (아주 간단한 원리를. 병렬 설정 합리적인 정도, 클러스터 자원을 최대한 활용, 각 작업에 대한 처리하는 데이터의 양을 줄이고, 증가 성능을 빠르게 실행할 수 있습니다.)

  예를 들면 :

   1, 우리와 같은 50 집행자를 스파크 작업 할당 충분한 자원을 제공 할 수있는 스크립트를 스파크 제출 지금에, 경우 각 집행은 각 집행자는 세 가지 CPU 코어를 가지고, 메모리 10G가 있습니다. 그것은 기본적인 스파크 클러스터 또는 클러스터 실 천장에 도달했습니다. 작업 설정, 또는 100 일, 50 게르마늄 집행을 설정하는 등, 거의 설정되지되지 않으며, 각 집행자는 언제든지에게 응용 프로그램을 무대 실행 말을하는 것입니다 세 가지 핵심을 가지고, 150 CPU 코어의 총 수는있다, 당신은 할 수있다 병렬로 실행합니다.

    그러나 지금 균등이 작업에 할당 된 각 집행에 분산 된 100 개의 작업은 다음 작업이 동시에 100 개의 작업을 실행하고, 각 집행자는 단지 두 개의 병렬 작업을 실행합니다. 하나 개의 CPU 코어를 나머지 각 집행자가 손실 될 것입니다! 귀하의 자원,하지만 충분 할당하지만, 문제는 병렬 처리 수준은 모든 낭비 갈 자원 할당의 결과로, 자원과 일치되지 않는 것입니다. 병렬 설정 합리적인 정도가 클러스터 리소스를 완전히 합리적인 사용에 충분한, 충분히 큰 설정해야 그러한 위의 예와 같이 클러스터는 150 CPU 코어의 객실을 선택할 수 있습니다, 당신은 150 동시 작업을 실행할 수 있습니다. 그럼 당신은 또한 병렬로 실행할 수 있습니다 (150)로 증가 이후 150 병렬 작업 실행, 작업 및 귀하의 클러스터 자원을 충분히 효과적으로 사용, 수, 적어도 150, 병렬 응용 프로그램을 이동해야합니다 각 작업 수가 작아지게되도록 처리 될 수 있으며, 데이터가 전체 150G에서 처리되는 것과 같은 태스크 (100)는, 데이터가 1.5G의 각 작업에 대하여 계산 될 수있는 경우. 150 개의 태스크, 데이터 처리 1G만큼 각 태스크 증가.

 2.2 병렬 처리 수준을 향상하는 방법

  1) 당신은 작업의 수를 설정할 수 있습니다

   마찬가지 스파크 출원 전체의 CPU 코어 수에 배치 된 총 CPU 코어 수 (이상적인 150 코어 동시에 주위 함께 실행 배신 150task가 완성 주행 있음) 공식 권고 번호 태스크의 스파크 애플리케이션에 적어도 제공되는 2~3 배. 이러한 150 CPU 코어, 이상적으로 달리 500 일 (300)의 기본 설정 번호와 같은 일부 작업은 그렇다면, 50 년대가 완료 될 것 같은, 어떤 작업이 하나에 조금 느린, 일 반 단지 완성 된 실행 할 수있다, 좀 더 빠르게 실행됩니다 전화 번호 작업이 바로 CPU 코어의 수와 동일하게 설정, 그것은 자원의 낭비가 발생할 수 있습니다.

   이러한 작업 (150 개) (10)가 처음으로 완성에서 실행, 나머지 140은 여전히하지만,이 시간을 실행하기 때문에, 코어는 폐기물의 결과, 유휴 상태의 CPU에서 10있다. 두 개 또는 세 번하면 작업의 완료 즉시 채우기 위해 다른 작업을 실행 한 후 후, CPU 코어가 유휴 만들려고하지 않습니다. 운영 효율성과 속도를 향상하는 동안 스파크. 성능을 향상시킬 수 있습니다.

  2) 어떻게 개선 작업을 병렬의 수를 설정합니다

   설정 매개 변수 spark.defalut.parallelism

    기본값은 10의 값을 설정하면, 그것은 셔플 과정까지 적용되지 않습니다 값입니다.

    예를 들어 브로 rdd2 = rdd1.reduceByKey (_ _ +)

    이때, 격벽 rdd2 수가 파티션 (10)의 수이며,이 효과 rdd1 파라미터 아니다.

    예를 들어, 객체를 설정 :. 새로운 SparkConf () 세트 ( "spark.defalut.parallelism", "500")에 의해 SparkConf를 구성 할 때

  3)에 RDD의 파티션의 번호를 다시 설정할

   재분할 사용 rdd.repartition, 상기 방법은 파티션의 개수를 늘려, 새로운 RDD를 생성한다.

   이 때, 파티션 작업에 해당하기 때문에, 다음, 더는이 방법으로 해당 작업의 수는 병렬 처리의 정도를 증가시킬 수있다.

  4) 작업 스파크 SQL 실행의 수를 증가

   기본 매개 변수 spark.sql.shuffle.partitions = 500 (200)를 설정함으로써,

   적절하게 병렬 처리 수준을 향상시키기 위해 증가 될 수있다. 예를 설정 spark.sql.shuffle.partitions = 500 대

 

세, RDD 재사용 지속성

 실제 개발 경험의 3.1 설명

      

  계산 논리는 위의 그림 :

   RDD2가 rdd3 연산자에서 수득 할 수있는 시간에 대응하는 처음의 계산 rdd1에서 시작할 때 (1)는 HDFS의 파일을 판독하고, 다음 RDD2 의해 다음, RDD2 얻어진 해당 운전자 rdd1 할 rdd3을 계산 후의. 또한 계산 rdd4 위해, 상기 논리 계산된다.   

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

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

       

 3.2  如何对rdd进行持久化

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

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

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

 3.3  rdd持久化时可以采用序列化

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

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

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

  (4)如果序列化纯内存方式,还是导致OOM,内存溢出;就只能考虑磁盘的方式,内存+磁盘的普通方式(无序列化)。   (5)为了数据的高可靠性,而且内存充足,可以使用双副本机制,进行持久化。

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

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

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

 3.4  广播变量

  1)  场景描述

   在实际工作中可能会遇到这样的情况,由于要处理的数据量非常大,这个时候可能会在一个stage中出现大量的 task,比如有1000个task,这些task都需要一份相同的数据来处理业务,这份数据的大小为100M,该数据会拷贝 1000份副本,通过网络传输到各个task中去,给task使用。这里会涉及大量的网络传输开销,同时至少需要的内存 为1000*100M=100G,这个内存开销是非常大的。不必要的内存的消耗和占用,就导致了,你在进行RDD持久化 到内存,也许就没法完全在内存中放下;就只能写入磁盘,后导致后续的操作在磁盘IO上消耗性能;这对于 spark任务处理来说就是一场灾难。

  由于内存开销比较大,task在创建对象的时候,可能会出现堆内存放不下所有对象,就会导致频繁的垃圾回收器的 回收GC。GC的时候一定是会导致工作线程停止,也就是导致Spark暂停工作那么一点时间。频繁GC的话,对 Spark作业的运行的速度会有相当可观的影响。

  2)  广播变量引入

   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上拉取变量副本,网络距离 越近越好。

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

   比如一个任务需要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分钟。但是一点一滴的调优,积少成多。后还是会有效果的。

  4)  如何使用广播变量

    (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序列化

 4.1  spark序列化介绍

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

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

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

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

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

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

   (1)算子函数中使用到的外部变量

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

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

   (2)持久化RDD时进行序列化,StorageLevel.MEMORY_ONLY_SER

    将rdd持久化时,对应的存储级别里,需要用到序列化。

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

   (3) 产生shuffle的地方,也就是宽依赖

    下游的stage中的task,拉取上游stage中的task产生的结果数据,跨网络传输,需要用到序列化。

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

 4.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优化数据格式

 5.1  fastutil 介绍

  fastutil是扩展了Java标准集合框架(Map、List、Set;HashMap、ArrayList、HashSet)的类库,提供了特殊类型 的map、set、list和queue;

  fastutil能够提供更小的内存占用,更快的存取速度;我们使用fastutil提供的集合类,来替代自己平时使用的JDK的 原生的Map、List、Set。

 5.2  fastutil 好处

  fastutil集合类,可以减小内存的占用,并且在进行集合的遍历、根据索引(或者key)获取元素的值和设置元素的值 的时候,提供更快的存取速度;

  fastutil也提供了64位的array、set和list,以及高性能快速的,以及实用的IO类,来处理二进制和文本类型的文件;

  fastutil新版本要求Java 7以及以上版本;

  fastutil的每一种集合类型,都实现了对应的Java中的标准接口(比如fastutil的map,实现了Java的Map接口),因此可以直接放入已有系统的任何代码中。

  fastutil还提供了一些JDK标准类库中没有的额外功能(比如双向迭代器。

  fastutil除了对象和原始类型为元素的集合,fastutil也提供引用类型的支持,但是对引用类型是使用等于号(=)进 行比较的,而不是equals()方法。

  fastutil尽量提供了在任何场景下都是速度快的集合类库。

 5.3  Spark中应用fastutil 的场景

  1)  算子函数使用了外部变量

   ●  你可以使用Broadcast广播变量优化;

   ●  可以使用Kryo序列化类库,提升序列化性能和效率;

   ●  如果外部变量是某种比较大的集合,那么可以考虑使用fastutil改写外部变量;

   首先从源头上就减少内存的占用(fastutil),通过广播变量进一步减少内存占用,再通过Kryo序列化类库进一步减少内存占用。

  2)  算子函数里使用了比较大的集合Map/List

   在你的算子函数里,也就是task要执行的计算逻辑里面,如果有逻辑中,出现,要创建比较大的Map、List等集合, 可能会占用较大的内存空间,而且可能涉及到消耗性能的遍历、存取等集合操作;

   那么此时,可以考虑将这些集合类型使用fastutil类库重写,

   使用了fastutil集合类以后,就可以在一定程度上,减少task创建出来的集合类型的内存占用。

   避免executor内存频繁占满,频繁唤起GC,导致性能下降。

  3)  关于fastutil 的调优说明

   fastutil其实没有你想象中的那么强大,也不会跟官网上说的效果那么一鸣惊人。

   广播变量、Kryo序列化类库、 fastutil 都是之前所说的,对于性能来说,类似于一种调味品,烤鸡,本来就很好吃了,然后加了一点特质的孜然麻辣 粉调料,就更加好吃了一点。

   分配资源、并行度、RDD架构与持久化,这三个就是烤鸡;  broadcast、kryo、fastutil,类似于调料。

   比如说,你的spark作业,经过之前一些调优以后,大概30分钟运行完,现在加上broadcast、kryo、fastutil,也许就是优化到29分钟运行完、或者更好一点,也许就是28分钟、25分钟。

   真正有意义的就是后面要学习的shuffle调优,可能优化之后只需要15分钟;

   还有把groupByKey用reduceByKey改写,执行本地聚合,也许10分钟;

   甚至可以向公司申请更多的资源,扩大整个集群的计算能力,后可能到达5分钟就完成任务了。

  4)  fastutil 的使用

   第一步:在pom.xml中引用fastutil的包

    <dependency>

      <groupId>  fastutil  </groupId>

      <artifactId>  fastutil  </artifactId>

      <version>  5.0.9  </version>

    </dependency>

   第二步:平时使用List (Integer)的替换成IntList即可。

    List<Integer>的list对应的到fastutil就是IntList类型

   使用说明: 基本都是类似于IntList的格式,前缀就是集合的元素类型;

   特殊的就是Map,Int2IntMap,代表了key‐value映射的元素类型。

 

六、调节数据本地化等待时长

 Spark在Driver上对Application的每一个stage的task进行分配之前,都会计算出每个task要计算的是哪个分片数 据,RDD的某个partition;Spark的task分配算法,优先会希望每个task正好分配到它要计算的数据所在的节点, 这样的话就不用在网络间传输数据;

 但是通常来说,有时事与愿违,可能task没有机会分配到它的数据所在的节点,为什么呢,可能那个节点的计算资 源和计算能力都满了;所以这种时候,通常来说,Spark会等待一段时间,默认情况下是3秒(不是绝对的,还有很 多种情况,对不同的本地化级别,都会去等待),到后实在是等待不了了,就会选择一个比较差的本地化级别, 比如说将task分配到距离要计算的数据所在节点比较近的一个节点,然后进行计算。

 6.1  本地化级别

  (1)PROCESS_LOCAL:进程本地化

    代码和数据在同一个进程中,也就是在同一个executor中;计算数据的task由executor执行,数据在executor的 BlockManager中;性能好。

  (2)NODE_LOCAL:节点本地化

    代码和数据在同一个节点中;比如说数据作为一个HDFS block块,就在节点上,而task在节点上某个executor中 运行;或者是数据和task在一个节点上的不同executor中;数据需要在进程间进行传输;性能其次。

  (3)RACK_LOCAL:机架本地化

    数据和task在一个机架的两个节点上;数据需要通过网络在节点之间进行传输; 性能比较差。

  (4) ANY:无限制

    数据和task可能在集群中的任何地方,而且不在一个机架中;性能最差。

 6.2  数据本地化等待时长

  spark.locality.wait,默认是3s

  首先采用佳的方式,等待3s后降级,还是不行,继续降级...,后还是不行,只能够采用差的。

 

七、降低cache操作的内存占比

 7.1  为什么需要JVM调优

   spark的scala代码调用了很多java api。scala也是运行在java虚拟机中的。spark是运行在java虚拟机中的。

   java虚拟机可能会产生什么样的问题:内存不足??!!

   我们的RDD的缓存、task运行定义的算子函数,可能会创建很多对象。都可能会占用大量内存,没搞好的话,可能导致 JVM出问题。

    JVM调优(Java虚拟机):JVM相关的参数,通常情况下,如果你的硬件配置、基础的JVM的配置,都ok的话,JVM通常不 会造成太严重的性能问题;反而更多的是在troubleshooting(故障排除)中,JVM占了很重要的地位;JVM造成线上的 spark作业的运行报错,甚至失败(比如OOM)。

 7.2  JVM堆内存模型图

      

   有通过new创建的对象的内存都在堆中分配,其大小可以通过‐Xmx和‐Xms来控制。

   堆被划分为年轻代和老年代,年轻代又被进一步划分为Eden和Survivor区

   JVM堆空间内存分配,在默认情况下:

    年轻代 :用于存放新产生的对象。我们在spark task执行算子函数操作的时候,可能会创建很多对象,这些对象都 是要放入JVM年轻代中的,它占用堆中三分之一的堆内存空间。

    里面又分为三个区域

      eden区:    8/10 的年轻代空间

      survivor0 : 1/10 的年轻代空间

      survivor1 : 1/10 的年轻代空间

    老年代 : 用于存放被长期引用的对象,它占用堆中三分之一的堆内存空间。

 

 

 7.3  JVM工作阐述

   每一次放对象的时候,都是放入eden区域,和其中一个survivor区域;另外一个survivor区域是空闲的。  当eden区域和一个survivor区域放满了以后(spark运行过程中,产生的对象实在太多了),就会触发minor gc,小型 垃圾回收。把不再使用的对象,从内存中清空,给后面新创建的对象腾出来点儿地方。

  清理掉了不再使用的对象之后,那么也会将存活下来的对象(还要继续使用的),放入之前空闲的那一个 survivor区域中。这里可能会出现一个问题。默认eden、survior0和survivor1的内存占比是8:1:1。问题是,如果存 活下来的对象是1.5,一个survivor区域放不下,将多余的对象,直接放入老年代了。

  如果你的JVM内存不够大的话,可能导致频繁的年轻代内存满溢,频繁的进行minor gc(清理Eden区和  Survivor区)。频繁的minor gc会导致短时间内,有些存活的对象,多次垃圾回收都没有回收掉。会导致这种短生命周 期(其实不一定是要长期使用的)对象,年龄过大,垃圾回收次数太多还没有回收到,跑到老年代。老年代中,可能会因 为内存不足,囤积一大堆,短生命周期的,本来应该在年轻代中的,可能马上就要被回收掉的对象。此时可能导致老年代 频繁满溢。频繁进行full gc(清理整个堆空间—包括年轻代和老年代)。full gc就会去回收老年代中的对象。  full gc / minor gc,无论是快,还是慢,都会导致jvm的工作线程停止工作,stop the world。  简而言之,就是说gc的时候,spark停止工作了。等着垃圾回收结束。

  内存不充足的时候,会出现的问题:

  (1)、频繁minor gc,也会导致频繁spark停止工作

  (2)、老年代囤积大量活跃对象(短生命周期的对象),导致频繁full gc,full gc时间很长,短则数十秒,长则数分 钟,甚至数小时。可能导致spark长时间停止工作。

  (3)、频繁gc会严重影响spark的性能和运行的速度。

 7.4  降低cache操作的内存占比

  spark中,堆内存又被划分成了两块儿,一块儿是专门用来给RDD的cache、persist操作进行RDD数据缓存用的;

  另外一块儿,就是我们刚才所说的,用来给spark算子函数的运行使用的,存放函数中自己创建的对象。

  默认情况下,给RDD cache操作的内存占比是0.6(spark.storage.memoryFraction=0.6),60%的内存都给 了cache操作了。如果某些情况下,cache不是那么的紧张,在task算子函数中创建的对象过多,然后内存又不太大,导 致了频繁的minor gc,甚至频繁full gc,导致spark频繁的停止工作。性能影响会很大。

  针对上述这种情况,大家可以在之前我们讲过的那个spark ui。yarn去运行的话,那么就通过yarn的界面,去 查看你的spark作业的运行统计,很简单大家一层一层点击进去就好。可以看到每个stage的运行情况,包括每个task的 运行时间、gc时间等等。如果发现gc太频繁,时间太长。此时就可以适当调节这个比例。

  降低cache操作的内存占比,大不了用persist操作,选择将一部分缓存的RDD数据写入磁盘,或者序列化方式, 配合Kryo序列化类,减少RDD缓存的内存占用;降低cache操作内存占比;此时对应的算子函数的内存占比就提升了。这个 时候,可能就可以减少minor gc的频率,同时减少full gc的频率。对性能的提升是有一定的帮助的。

  总之一句话,让task执行算子函数时,有更多的内存可以使用。

 7.5  降低cache操作的内存占比代码实现

  cache操作的内存占比为堆内存的0.6 也就是百分之60,可以适当调节,降低该值,

   修改spark.storage.memoryFraction参数

   可以设置为0.5‐‐‐>0.4‐‐‐‐‐‐>0.3   

  例如: 

   new SparkConf().set("spark.storage.memoryFraction","0.4")

   把cache操作的内存占比修改为堆内存的百分之40,让堆内存可以容纳更多的对象,减少gc的频率,提高spark任务运行 的速度和性能。

 

 

 

추천

출처www.cnblogs.com/chen2710/p/12445927.html