la optimización del rendimiento Scala

punto de la optimización del rendimiento de chispa

En primer lugar, asignar más recursos

  Es un rey optimización del rendimiento de sintonización, es aumentar y la asignación de más recursos, es evidente para mejorar el rendimiento y la velocidad,

  Sustancialmente dentro de un cierto rango, aumentando el rendimiento y mejorar los recursos, es proporcional; terminó después de operaciones complejas una chispa, cuando el ajuste del rendimiento, el primer paso es ajustar la asignación óptima de recursos a ; sobre esta base, si su chispa después del trabajo, los recursos pueden ser asignados para llegar a la parte superior de su rango de capacidad y no puede asignar más recursos, los recursos limitados de la empresa, para ello se considera detrás de estas punto de ajuste del rendimiento.

 1.1 Ajuste de parámetros a lo grande que es el más grande?

   El primer caso: el modo independiente

    En primer lugar calcular el tamaño de la memoria de todos los recursos de cada nodo en el racimo y las empresas CPU chispa de auditoría,

    Por ejemplo: Un total de 20 nodos de trabajo, la memoria cada nodo 8 g, 10 cpu.

    La tarea real en un recurso dado, puede dar 20 ejecutor, el número de CPU cada 8g de memoria ejecutor, utilizado por cada ejecutor 10.

  El segundo caso: Hilado primero calcular el tamaño de todos los grupos de hilos, tales como 500g memoria total, 100 cpu;

    recursos grandes pueden ser asignados en este momento, tal como un ejecutor dado 50, el tamaño de memoria de cada 10g ejecutor, el número de CPU cada ejecutor utilizado es 2.

    Principios de utilización: recurso que se puede utilizar tanto, tratan de adaptarse a gran tamaño (número de ejecutor: van desde decenas a cientos; ejecutor de la memoria, el número de exector CPU)

 Después de 1.2 ¿Por qué se puede transferir grandes recursos para mejorar el rendimiento

 

 En segundo lugar, el grado de paralelismo

 2.1 Spark el paralelismo de lo que se entiende por

  empleos de chispa, el número de tarea de cada etapa, también representa el trabajo chispa en cada etapa de la fase de paralelismo!

  Cuando haya terminado asignar grandes recursos que pueden ser asignados, y luego ajustar el paralelismo de los recursos correspondientes al programa, si el grado de paralelismo no se corresponde con los recursos, a continuación, llevar a la asignación de los recursos que tienen que ir en vano. Mientras tanto corren en paralelo, sino que también permite que el número de cada tarea a procesar menos (principio muy simple. Conjunto razonable grado de paralelismo, puede hacer pleno uso de los recursos del clúster, reducir la cantidad de procesamiento de datos para cada tarea, y el aumento de rendimiento correr más rápido.)

  Por ejemplo:

   Si, ahora en la chispa presentar un guión que nos puede dar los recursos suficientes asignados Trabajo de la chispa, como ejecutor 50, cada ejecutor tiene 10G de memoria, cada ejecutor tiene tres núcleos de la CPU. Se ha alcanzado un límite de hilo cúmulo chispa básica o racimos. la tarea no se ha establecido, o conjunto en raras ocasiones, como por ejemplo la creación de un 100 tareas, 50 Ge ejecutor, cada ejecutor tiene tres núcleos, es decir Aplicación cualquier momento una carrera etapa, no es el número total de 150 núcleo de la CPU, que puede ejecutar en paralelo.

    Pero que ahora sólo 100 tarea, distribuido uniformemente en cada ejecutor asignado a dos tareas, la tarea se está ejecutando al mismo tiempo, a sólo 100 tarea, cada ejecutor sólo se ejecutará dos tareas en paralelo. Cada ejecutor restante núcleo de la CPU se perdería! Sus recursos, aunque asignen suficiente, pero el problema es que el grado de paralelismo no se corresponde con los recursos, lo que resulta en la asignación de recursos que vaya todo perdido. Establecer un razonable grado de paralelismo debe ser lo suficientemente grande, lo suficientemente grande como para uso totalmente razonable de los recursos del clúster, tales como el ejemplo anterior, el grupo tiene un total de 150 CPU de la base, puede ejecutar 150 tarea concurrente. A continuación, debe mover su Solicitud de paralelismo, al menos a 150, para ser plenamente eficaz utilización de los recursos del clúster a 150 ejecución de tareas en paralelo, tarea y más tarde aumentó a 150, que puede funcionar en paralelo, también cada tarea se puede procesar de manera que el número se convierte en pequeña; tales como los datos a procesar en total 150G, si la tarea 100, los datos que se calcula para cada tarea de 1,5 G. Ahora aumentado a 150 tarea, cada tarea, siempre y cuando los datos de procesado 1G.

 2.2 ¿Cómo mejorar el grado de paralelismo

  1) se puede establecer el número de tarea

   La misma se proporciona al menos para la aplicación de chispa del número total núcleo de la CPU (idealmente, 150 de núcleo, 150task distribución, correr juntos, alrededor del mismo tiempo tiene en funcionamiento terminado) recomendación oficial, Número de tareas, dispuesto para número de la base de la solicitud de chispa total de CPU 2 ~ 3 veces. Tales como 150 núcleo de la CPU, número de serie básica de trabajo de 300 a 500. A diferencia Lo ideal sería que alguna tarea se ejecuta un poco más rápido, tal como se terminarían los años 50, una tarea puede ser un poco más lento, a uno y correr un medio acaba de terminar, por lo que si su tarea número, acaba de establecer la misma con el número de CPU de la base, que puede conducir a una pérdida de recursos.

   Debido a que tal tarea 150 carreras en 10 primero terminado, y el 140 restante todavía se están ejecutando, pero esta vez, hay 10 de núcleo de la CPU está inactivo, lo que resulta en los residuos. Si dos o tres veces, y luego después de la finalización de una tarea se ejecute otra tarea para llenar de inmediato, no se trata de hacer núcleo de la CPU está inactivo. Spark al tratar de mejorar la eficiencia operativa y la velocidad. Mejorar el rendimiento.

  2) ¿Cómo establecer el número de paralelismo de tareas para mejorar

   parámetros de ajuste spark.defalut.parallelism

    El valor predeterminado es sin valor, si se establece un valor de 10, que no entrará en vigor hasta que el proceso de reproducción aleatoria.

    Por ejemplo: val rdd2 = rdd1.reduceByKey (_ + _)

    En este momento, el número de particiones rdd2 es el número de particiones 10, RDD1 no es este parámetros de efectos.

    Cuando se construye SparkConf estableciendo el objeto, por ejemplo :. Nueva SparkConf set () ( "spark.defalut.parallelism", "500")

  3) para re-establecer el número de partición de RDD

   Uso rdd.repartition de reparto, el método genera un nuevo RDD, aumenta el número de particiones.

   En este momento, ya que una partición correspondiente a una tarea, entonces el mayor es el número de la tarea correspondiente, de esta manera puede también aumentar el grado de paralelismo.

  4) aumentar el número de la tarea de marcha chispa sql

   Mediante el establecimiento de parámetros por defecto spark.sql.shuffle.partitions = 500 200;

   Se puede aumentar apropiadamente para mejorar el grado de paralelismo. Por ejemplo establecido spark.sql.shuffle.partitions = 500

 

Tres, la reutilización y la persistencia RDD

 3.1 Descripción de la experiencia de desarrollo real

      

  lógica de cálculo mostrado arriba

   (1) cuando el primer tiempo que corresponde al tiempo de hacer RDD2 obtenido en el operador rdd3, el cálculo se iniciará desde RDD1, para leer archivos en los HDFS, y luego hacer lo RDD1 operador correspondiente obtenido en RDD2, luego por RDD2 después de calcular rdd3 obtenido. También para rdd4 calculado, la lógica anterior se vuelve a calcular.   

   (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任务运行 的速度和性能。

 

 

 

Supongo que te gusta

Origin www.cnblogs.com/chen2710/p/12445927.html
Recomendado
Clasificación