文章目录
1.sc.textFile()源码讲解
textFile读取的是hdfs上的数据,调的底层就是hdfs读取数据API
2.shell脚本启动流程
Spark相关shell脚本是工业级脚本,在以后生产上若遇到写此类的服务脚本完全,可以借它们进行代码搬运
3.(Tuning Guidance)
spark生产中,调优是非常重要的一部分,好的调优可使得程序更加高效稳健,以下从常用的六个方面进行调优。
3.1数据存储级别改为MEMO_ONLY_SER
spark core中默认的数据存储级别即mem_only,可以设置为,mem_only_sec,这样大大减少内存的开销。
3.2.使用Kryo进行ser
java序列化速度慢且占用也大,而kryo更快且小。
- 第一步:配置 , conf.set(“spark.serializer”,“org.apache.spark.serializer.KryoSerializer”),该配置可在spark-default中进行默认配置
- 第二步:Regist,将需要序列化的对象注册到Kryo,虽然不注册也能用,但是相较于注册速度慢且大点儿
如下是测试代码,实验数据100万条
package com.wsk.spark.tuning
import org.apache.spark.storage.StorageLevel
import org.apache.spark.{SparkConf, SparkContext}
/**
* 使用kryo序列化数据对象,
* 1、生产山spark.serializer这个配置直接写到spark-default配置文件中即可
*/
object KryoApp {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("Kyro App").setMaster("local[2]")
conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
conf.registerKryoClasses(Array(classOf[Logger]
val sc = new SparkContext(conf)
val logRDD = sc.textFile("data/cdnlog/input/CDN_click.log")
.map(x => {
val logFields = x.split(",")
Logger(logFields(0), logFields(1), logFields(2), logFields(3),
logFields(4), logFields(5), logFields(6), logFields(7))
})
// val pesisitRDD = logRDD.persist(StorageLevel.MEMORY_ONLY)
val pesisitRDD = logRDD.persist(StorageLevel.MEMORY_ONLY_SER)
println("总条数" + pesisitRDD.count())
Thread.sleep(2000000)
sc.stop()
}
case class Logger(filed1: String, filed2: String, filed3: String,
filed4: String, filed5: String, filed6: String,
filed7: String, filed8: String)
}
测试结果:
存储方式 | 大小 |
---|---|
磁盘 | 93.5M |
内存 | 560.4 MB |
内存+java序列化 | 116.2M |
内存+kyro+不注册 | 130.7M |
内存+kyro+注册 | 96.4M |
从表中可以轻易看出,使用kryo方式进行序列化,占用的内存最少,非常接近数据在磁盘中的大小。
综上所述:生产中内存不够,应使用kryo方式序列化数据
3.3. leval of Parallelism
将RDD的分区数合理的设置为core的2~3倍
3.4 MemoryManager Tuning(内存管理优化)
这里介绍了内存管理的方式以及从源码解读两种内存的计算方式,生产中必要时可修改相关参数从而达到对内存管理优化。
3.4.1统一内存管理
spark1.5之后默认使用统一内存管理,spark能管理的内存通常被划分为两个部分:
- Executor Memory:计算时占用内存,如在map、reduce、join时数据占据的内存。
- Storage Memory:cache数据占用的内存。
Executor Memory和Storage Memory公用一个内存的region(统一内存管理),双方都可以互相的借用至全部的内存,但是当双方都资源紧张时Executor Memory可以驱逐部分Storage Memory到一定阀值(默认0.5),反之不可以,这种设计是保障计算能够进行下去。
3.4.2 两种MemoryManager占比源码解析
以下是对比静态以及统一两种内存管理,相关的代码片段:
# SparkEnv.scala
val useLegacyMemoryManager = conf.getBoolean("spark.memory.useLegacyMode", false)
val memoryManager: MemoryManager =
if (useLegacyMemoryManager) {
new StaticMemoryManager(conf, numUsableCores)
} else {
UnifiedMemoryManager(conf, numUsableCores)
}
# StaticMemoryManager 构造器
def this(conf: SparkConf, numCores: Int) {
this(
conf,
StaticMemoryManager.getMaxExecutionMemory(conf),
StaticMemoryManager.getMaxStorageMemory(conf),
numCores)
}
#getMaxStorageMemory
private def getMaxStorageMemory(conf: SparkConf): Long = {
....
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
}
#getMaxExecutionMemory
private def getMaxExecutionMemory(conf: SparkConf): Long = {
....
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
}
# UnifiedMemoryManager 构造器
def apply(conf: SparkConf, numCores: Int): UnifiedMemoryManager = {
// (10G-300M)*0.6
val maxMemory = getMaxMemory(conf)
// storage分配的内存:onHeapStorageRegionSize = (10G-300M)*0.6*0.5
//反之executor分配的内存:(10G-300M)*0.6*(1-0.5)
new UnifiedMemoryManager(
conf,
maxHeapMemory = maxMemory,
onHeapStorageRegionSize =
(maxMemory * conf.getDouble("spark.memory.storageFraction", 0.5)).toLong,
numCores = numCores)
}
#getMaxMemory,
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
//获取最大可管理内存,默认Memorymanager只能使用0.6的内存: (10G-300)*0.6
val memoryFraction = conf.getDouble("spark.memory.fraction", 0.6)
(usableMemory * memoryFraction).toLong
}
代码中的10G是假设给于分配系统10G内存,这里用于计算的。
- StaticMemoryManager:静态,老的内存管理,1.5以及1.5之前,从构造器源码可以看出,即使我们分配给了Executor 10G内存个,最大能用于计算的只有1.6G,用于存储5.4G,计算内存太少了会出现各种OOM情况,这时需要优化调整参数
- UnifiedMemoryManager:动态,新的内存管理,1.5以后出现的,spark默认使用此种内存管理方式,10G内存可用于内存管理的是(10G-300M)*0.6,另外的0.4用于维持数据结构等,
3.4.3 Configs about MemoryManager
参考官网http://spark.apache.org/docs/latest/configuration.html 的Memory Management模块。
3.5 GC Tuning(垃圾回收优化)
gc的次数越多越会使程序夯住,尤其是发生full gc的时候,整个世界都暂停了。
3.5.1如何观察GC情况?
通常通过 Spark UI界面我们可以轻松的观看的每个executor的gc的时间情况。
3.5.2常见GC类型
eden满 :mirror gc
old 满::Full gc(Major GC),频繁的full gc会很影响程序性能的,一定要避免
3.5.2 tuning gc
spark gc tuning 的最终目的是将生命周期长的RDD对象放在老年代,生命周期短的RDD放在年轻代。
- 若任务过程中Minor GC多full gc少,可将Eden区的大小占比调大一点儿,若oldgen 容易满(易发生full gc),可以将spark占jvm的内存比调低或者调低youngGEN占堆得大小,默认youngGEN占堆大小为1/3,可以考虑将老年区的大小调的比spark程序大。
- GC是开销的成本和对象数成正比,若gc有问题可以尝试一下序列化方式去storage RDD 减少占用内存以及gc时间(对象数少了)看是否能解决,再不行就需要配置一些参数来调整内存的分配,减少gc频率尤其是full gc。
参看文章: https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/index.html
扩展1:MR中的Mapper以及Reducer接口范型参数有4个,mapp以及reduce方法参数有3个
扩展2:REPL指的是(Read-eval-print-loop)交互式解释器,读,求值、打印、循环
扩展3:uname 获取当前系统名称,exec XXX 就是执行xxx脚本