文章目录
官网: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,