SparkCore:数据序列化,内存调优


官网:Tuning Spark
http://spark.apache.org/docs/2.4.2/tuning.html

1、Data Serialization

1.1 Java和Kryo序列化

序列化在任何分布式应用程序的性能中都扮演着重要的角色。将对象序列化得很慢或消耗大量字节的格式将极大地降低计算速度。通常,这将是优化Spark应用程序的第一件事。Spark的目标是在便利性和性能之间取得平衡。它提供了两个序列化库:

  • Java serialization:默认情况下,Spark使用Java的ObjectOutputStream框架序列化对象,并且可以与您创建的任何实现Java .io. serializable的类一起工作。还可以通过扩展java.io.Externalizable更紧密地控制序列化的性能。Java序列化是灵活的,缺点是非常慢,并且会导致文件非常大。
  • Kryo serialization:Spark还可以使用Kryo库(版本4)更快地序列化对象。Kryo比Java序列化(通常高达10x)要快得多,也更紧凑(文件小),但是它不支持所有Serializable的类型,并且要求您预先注册将在程序中使用的类,以获得最佳性能。

1.2 Kryo序列化注册

在SparkConf初始化并调用conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")使用Kryo。此设置配置序列化器,该序列化器不仅用于在工作节点之间移动数据,而且用于将RDDs序列化到磁盘。Kryo不是默认值的唯一原因是定制注册要求,但是我们建议在任何网络密集型应用程序中尝试它。从Spark 2.0.0开始,当使用简单类型、简单类型数组或字符串类型打乱RDDs时,我们在内部使用Kryo serializer。Spark自动包含Kryo序列化器。

  • 向Kryo注册您自己的自定义类,请使用registerKryoClasses方法。
val conf = new SparkConf().setMaster(...).setAppName(...)
conf.registerKryoClasses(Array(classOf[MyClass1], classOf[MyClass2])) //可以注册多个类
val sc = new SparkContext(conf)

也可以在spark-defaults.conf配置文件中开启

[hadoop@vm01 conf]$ vi spark-defaults.conf 
spark.serializer                 org.apache.spark.serializer.KryoSerializer

1.3 Storage-Level和Kryo序列综合使用对比

现在本地准备一份日志文件,大小大概92.4M
然后做以下测试,看看4个的区别:
无序列化,Storage-Level=memory_only
java序列化,Storage-Level=memory_only_ser(默认就是java序列化)
kyro+不注册,Storage-Level=memory_only_ser
kyro+register,torage-Level=memory_only_ser
根据下面的代码,按照上面4种方式,注释掉相应的地方,一一测试结果。

package com.ruozedata.spark
import org.apache.spark.storage.StorageLevel
import org.apache.spark.{SparkConf, SparkContext}

object KryoApp {
  def main(args: Array[String]): Unit = {
    val conf=new SparkConf().setAppName("KryoApp").setMaster("local[2]")
    	//开kyro序列化
        conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
        //注册
        conf.registerKryoClasses(Array(classOf[Logger]))
    val sc= new SparkContext(conf)

    val logRDD = sc.textFile("D:\\Develop\\hadoopsource\\ser.txt")
      .map(x => {
        val logFields = x.split(",")
        Logger(logFields(0), logFields(1), logFields(2), logFields(3),
          logFields(4), logFields(5), logFields(6), logFields(7))
      })
	
	//StorageLevel
    //val persistRDD=logRDD.persist(StorageLevel.MEMORY_ONLY)
    val persistRDD=logRDD.persist(StorageLevel.MEMORY_ONLY_SER)

    println("总条数" + persistRDD.count())

    Thread.sleep(200000)
    sc.stop()
  }
  case class Logger(filed1: String, filed2: String, filed3: String,
                    filed4: String, filed5: String, filed6: String,
                    filed7: String, filed8: String)
}

Storage-Level=memory_only:172.9M
在这里插入图片描述
java序列化,Storage-Level=memory_only_ser(默认就是java序列化):48M
在这里插入图片描述

kyro+不注册,Storage-Level=memory_only_ser :51M
在这里插入图片描述
kyro+register,torage-Level=memory_only_ser :43M
在这里插入图片描述

1.4 SizeEstimator评估

scala> val a = sc.textFile("file:///home/hadoop/data/test.txt")
scala> import org.apache.spark.util.SizeEstimator
import org.apache.spark.util.SizeEstimator

#使用estimate方法,可以评估出文件占用大小
scala> SizeEstimator.estimate(a)
res1: Long = 1627656

2、Memory Tuning

2.1 Memory Tuning方面

调优内存使用时需要考虑三个因素:对象使用的内存数量、访问这些对象的成本以及垃圾收集的开销
默认情况下,Java对象访问速度很快,但是很容易比字段中的“原始”数据多消耗2-5倍的空间。这是由于几个原因:

  • 每个不同的Java对象都有一个“object header”,它大约是16个字节,包含指向其类的指针等信息。对于一个只有很少数据的对象(比如一个Int字段),它可以比数据更大。
  • Java Strings在原始字符串数据上大约有40个字节的开销(因为它们存储在字符数组中,并保存额外的数据,比如长度),由于字符串内部使用UTF-16编码,所以每个字符都存储为两个字节。因此,一个10个字符的字符串可以轻松地消耗60个字节。
  • 公共集合类,如HashMap和LinkedList,使用链接数据结构,其中每个条目都有一个“wrapper/包装器”对象(例如Map.Entry)。这个对象不仅有一个头,而且还有指向列表中下一个对象的指针(通常每个指针8个字节)。
  • 基本类型的集合通常将它们存储为“boxed/装箱的”对象,如java.lang.Integer。

2.2 Memory Management

Spark中的内存使用主要分为两类:execution 和 storage。执行内存指的是在改组、连接、排序和聚合中用于计算的内存,而存储内存指的是在集群中缓存和传播内部数据的内存。在Spark中,执行和存储共享一个统一的区域(M),当没有执行内存时,存储可以获得所有可用的内存,反之亦然。如果有必要,执行可能会驱逐存储,但仅在总存储内存使用量低于某个阈值®时才会这样做。由于实现的复杂性,存储可能不会驱逐执行。

这种设计确保了几个理想的性能。首先,不使用缓存的应用程序可以使用整个空间执行,从而避免不必要的磁盘溢出。其次,确实使用缓存的应用程序可以保留一个最小的存储空间®,在这里它们的数据块不会被驱逐。最后,这种方法为各种工作负载提供了合理的开箱即用性能,而不需要用户了解如何在内部划分内存。
这两种相关配置,默认值适用于大多数工作负载,一般用户不需要调整它们:

  • spark.memory.fraction表示M的大小是(JVM堆空间- 300MB)(默认0.6)的一部分。其余的空间(40%)预留给用户数据结构、Spark中的内部元数据,以及在稀疏且异常大的记录情况下防止OOM错误。
  • spark.memory.storageFraction表示R的大小为M的一部分(默认值为0.5)。R是M中的存储空间,其中缓存的块不会被执行驱逐。

SparkEnv.scala源码:memoryManager:MemoryManager这段

val useLegacyMemoryManager = conf.getBoolean("spark.memory.useLegacyMode", false)
val memoryManager: MemoryManager =
    if (useLegacyMemoryManager) { // true
        // 老的内存管理器,1.5之前的
        new StaticMemoryManager(conf, numUsableCores)
    } else {  //false
        // 新的内存管理器 1.6才有的
        UnifiedMemoryManager(conf, numUsableCores)  <==我们现在走的肯定是这一段
    }
}

StaticMemoryManager.getMaxExecutionMemory(conf) {
      private def getMaxExecutionMemory(conf: SparkConf): Long = {
        val systemMaxMemory = conf.getLong("spark.testing.memory", Runtime.getRuntime.maxMemory)

        if (systemMaxMemory < MIN_MEMORY_BYTES) {
          throw new IllegalArgumentException(s"System memory $systemMaxMemory must " +
            s"be at least $MIN_MEMORY_BYTES. Please increase heap size using the --driver-memory " +
            s"option or spark.driver.memory in Spark configuration.")
        }
        if (conf.contains("spark.executor.memory")) {
          val executorMemory = conf.getSizeAsBytes("spark.executor.memory")
          if (executorMemory < MIN_MEMORY_BYTES) {
            throw new IllegalArgumentException(s"Executor memory $executorMemory must be at least " +
              s"$MIN_MEMORY_BYTES. Please increase executor memory using the " +
              s"--executor-memory option or spark.executor.memory in Spark configuration.")
          }
        }
        val memoryFraction = conf.getDouble("spark.shuffle.memoryFraction", 0.2)
        val safetyFraction = conf.getDouble("spark.shuffle.safetyFraction", 0.8)

        // 10G * 0.2 * 0.8 = 1.6G
        (systemMaxMemory * memoryFraction * safetyFraction).toLong
      }

}
StaticMemoryManager.getMaxStorageMemory(conf) {
    val systemMaxMemory = conf.getLong("spark.testing.memory", Runtime.getRuntime.maxMemory)
    val memoryFraction = conf.getDouble("spark.storage.memoryFraction", 0.6)
    val safetyFraction = conf.getDouble("spark.storage.safetyFraction", 0.9)
    // 10G * 0.6 * 0.9 = 5.4G
    (systemMaxMemory * memoryFraction * safetyFraction).toLong
}

UnifiedMemoryManager
private def getMaxMemory(conf: SparkConf): Long = {
    val systemMemory = conf.getLong("spark.testing.memory", Runtime.getRuntime.maxMemory)
    val reservedMemory = conf.getLong("spark.testing.reservedMemory",
      if (conf.contains("spark.testing")) 0 else RESERVED_SYSTEM_MEMORY_BYTES)
    val minSystemMemory = (reservedMemory * 1.5).ceil.toLong
    if (systemMemory < minSystemMemory) {
      throw new IllegalArgumentException(s"System memory $systemMemory must " +
        s"be at least $minSystemMemory. Please increase heap size using the --driver-memory " +
        s"option or spark.driver.memory in Spark configuration.")
    }
    // SPARK-12759 Check executor memory to fail fast if memory is insufficient
    if (conf.contains("spark.executor.memory")) {
      val executorMemory = conf.getSizeAsBytes("spark.executor.memory")
      if (executorMemory < minSystemMemory) {
        throw new IllegalArgumentException(s"Executor memory $executorMemory must be at least " +
          s"$minSystemMemory. Please increase executor memory using the " +
          s"--executor-memory option or spark.executor.memory in Spark configuration.")
      }
    }
    // 10G - 300M
    val usableMemory = systemMemory - reservedMemory
    val memoryFraction = conf.getDouble("spark.memory.fraction", 0.6)

    // (10G - 300M) * 60%
    (usableMemory * memoryFraction).toLong
  }

// (10G - 300M) * 60% * 50%
onHeapStorageRegionSize =
    (maxMemory * conf.getDouble("spark.memory.storageFraction", 0.5)).toLong,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值