1、reducetaskOOM?
增大reduce端的聚合操作的内存比例
增大executormemory内存大小--executor-memory
减少reducetask每次拉取的数据量设置参数
2、在shuffle阶段executor挂掉?
分析原因:(1)maptask阶段的所运行的executor的内存不足,导致executor挂掉了。导致executor里面的blockmanager挂掉,不能和connctionmanager建立连接,
2.executor并没有挂掉
2.1BlockManage之间的连接失败(maptask所运行的executor正在GC)
2.2建立连接成功,maptask所运行的executor正在GC
3.reducetask向Driver中的MapOutputTracker获取shufflefile位置的时候出现了问题
下面有几个重要的参数的调节:
默认值:32k
参数说明:该参数用于设置shufflewritetask的BufferedOutputStream的buffer缓冲大小。将数据写到磁盘文件之前,会先写入buffer缓冲中待缓冲写满之后,才会溢写到磁盘。
调优建议:如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如64k),从而减少shufflewrite过程中溢写磁盘文件的次数,也就可以减少磁盘IO次数,进而提升性能。在实践中发现,合理调节该参数,性能会有1%~5%的提升。
默认值:48m
参数说明:该参数用于设置shufflereadtask的buffer缓冲大小,而这个buffer缓冲决定了每次能够拉取多少数据。
调优建议:如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如96m),从而减少拉取数据的次数,也就可以减少网络传输的次数,进而提升性能。在实践中发现,合理调节该参数,性能会有1%~5%的提升。
错误:reduceoom
reducetask去map拉数据,reduce一边拉数据一边聚合reduce段有一块聚合内存(executormemory*0.2)
解决办法:1、增加reduce聚合的内存的比例设置
2、增加executormemory的大小--executor-memory5G
3、减少reducetask每次拉取的数据量设置
默认值:3
参数说明:shufflereadtask从shufflewritetask所在节点拉取属于自己的数据时,如果因为网络异常导致拉取失败,是会自动进行重试的。该参数就代表了可以重试的最大次数。如果在指定次数之内拉取还是没有成功,就可能会导致作业执行失败。
调优建议:对于那些包含了特别耗时的shuffle操作的作业,建议增加重试最大次数(比如60次),以避免由于JVM的fullgc或者网络不稳定等因素导致的数据拉取失败。在实践中发现,对于针对超大数据量(数十亿~上百亿)的shuffle过程,调节该参数可以大幅度提升稳定性。
shufflefilenotfindtaskScheduler不负责重试task,由DAGScheduler负责重试stage
默认值:5s
参数说明:具体解释同上,该参数代表了每次重试拉取数据的等待间隔,默认是5s。
调优建议:建议加大间隔时长(比如60s),以增加shuffle操作的稳定性。
默认值:0.2
参数说明:该参数代表了Executor内存中,分配给shufflereadtask进行聚合操作的内存比例,默认是20%。
调优建议:在资源参数调优中讲解过这个参数。如果内存充足,而且很少使用持久化操作,建议调高这个比例,给shuffleread的聚合操作更多内存,
以避免由于内存不足导致聚合过程中频繁读写磁盘。在实践中发现,合理调节该参数可以将性能提升10%左右。
默认值:sort
参数说明:该参数用于设置ShuffleManager的类型。以后,有三个可选项:hash、sort和tungsten-sort。HashShuffleManager是以前的默认选项,但是以及之后的版本默认都是SortShuffleManager了。tungsten-sort与sort类似,但是使用了tungsten计划中的堆外内存管理机制,内存使用效率更高。
调优建议:由于SortShuffleManager默认会对数据进行排序,因此如果你的业务逻辑中需要该排序机制的话,则使用默认的SortShuffleManager就可以;而如果你的业务逻辑不需要对数据进行排序,那么建议参考后面的几个参数调优,通过bypass机制或优化的HashShuffleManager来避免排序操作,同时提供较好的磁盘读写性能。这里要注意的是,tungsten-sort要慎用,因为之前发现了一些相应的bug。
默认值:200
参数说明:当ShuffleManager为SortShuffleManager时,如果shufflereadtask的数量小于这个阈值(默认是200),则shufflewrite过程中不会进行排序操作,而是直接按照未经优化的HashShuffleManager的方式去写数据,但是最后会将每个task产生的所有临时磁盘文件都合并成一个文件,并会创建单独的索引文件。
调优建议:当你使用SortShuffleManager时,如果的确不需要排序操作,那么建议将这个参数调大一些,大于shufflereadtask的数量。那么此时就会自动启用bypass机制,map-side就不会进行排序了,减少了排序的性能开销。但是这种方式下,依然会产生大量的磁盘文件,因此shufflewrite性能有待提高。
默认值:false
参数说明:如果使用HashShuffleManager,该参数有效。如果设置为true,那么就会开启consolidate机制,会大幅度合并shufflewrite的输出文件,对于shufflereadtask数量特别多的情况下,这种方法可以极大地减少磁盘IO开销,提升性能。
调优建议:如果的确不需要SortShuffleManager的排序机制,那么除了使用bypass机制,还可以尝试将参数手动指定为hash,使用HashShuffleManager,同时开启consolidate机制。在实践中尝试过,发现其性能比开启了bypass机制的SortShuffleManager要高出10%~30%。
1、资源调优
在开发过程中,增加或者分配更多的资源对于任务的执行的效率是显而易见的。但是资源本身是受到限制的,那么我们该如何解决这个问题呢?
一般在开发的时候,我们的提交任务的脚本
内容如下:
!/bin/bash
Infile=$1
Outfile=$2
awk-F“,”‘BEGIN{id=0;}{
if($1$2$3$4!$5){
id=id+1;
print$1”,”$2”,”$3”,”$4”,”1!/bin/bash
Infile=$1
Outfile=$2
awk-F“,”‘BEGIN{id=0;}{
if($1$2$3$4($5!=-1)){
id=id+1;
print$1”,”$2”,”$3”,”$4”,”$5kmeans
基本概念
#缺点:
k值很难确定
复杂度与样本呈线性关系
很难发现任意形状的簇比如环装的簇
针对上面的k值的优化(这样对于迭代的效率会有很好的提升)
出现了k-means++。
K-means距离计算的优化
在原来的迭代的过程中需要计算很多的距离,那么我可以考虑在这块进行优化,利用三角形的三条边的关系优化,这样可以减少计算的次数。
大样本进行优化操作minibatch
面试:讲解一下k-means的原理,算法的改进异常值的处理
改进k-means++.对于你初始化k的随机选择进行了优化,在选择的时候这个被选择的点尽可能的远,避免局部最优。
Minibatchkmeans:每次只是选择一个子集作为重入类,
距离的计算:利用欧式距离来计算。但是迭代的过程中会有大量的距离计算,考虑利用三角形的边的关系进行优化。
异常值的处理:局部异常因子,多元高斯异常因子检测(计算点的密度)
SVM的核函数的选择:
SVM核函数的选择对于其性能的表现有至关重要的作用,尤其是针对那些线性不可分的数据,因此核函数的选择在SVM算法中就显得至关重要。对于核技巧我们知道,其目的是希望通过将输入空间内线性不可分的数据映射到一个高纬的特征空间内使得数据在特征空间内是可分的,我们定义这种映射为ϕ(x)ϕ(x),那么我们就可以把求解约束最优化问题变为
但是由于从输入空间到特征空间的这种映射会使得维度发生爆炸式的增长,因此上述约束问题中内积ϕi⋅ϕjϕi⋅ϕj的运算会非常的大以至于无法承受,因此通常我们会构造一个核函数:
从而避免了在特征空间内的运算,只需要在输入空间内就可以进行特征空间的内积运算。通过上面的描述我们知道要想构造核函数κκ,我们首先要确定输入空间到特征空间的映射,但是如果想要知道输入空间到映射空间的映射,我们需要明确输入空间内数据的分布情况,但大多数情况下,我们并不知道自己所处理的数据的具体分布,故一般很难构造出完全符合输入空间的核函数,因此我们常用如下几种常用的核函数来代替自己构造核函数:
线性核函数:
线性核,主要用于线性可分的情况,我们可以看到特征空间到输入空间的维度是一样的,其参数少速度快,对于线性可分数据,其分类效果很理想,因此我们通常首先尝试用线性核函数来做分类,看看效果如何,如果不行再换别的。
多项式核函数:
多项式核函数可以实现将低维的输入空间映射到高纬的特征空间,但是多项式核函数的参数多,当多项式的阶数比较高的时候,核矩阵的元素值将趋于无穷大或者无穷小,计算复杂度会大到无法计算。
高斯(RBF)核函数:
高斯径向基函数是一种局部性强的核函数,其可以将一个样本映射到一个更高维的空间内,该核函数是应用最广的一个,无论大样本还是小样本都有比较好的性能,而且其相对于多项式核函数参数要少,因此大多数情况下在不知道用什么核函数的时候,优先使用高斯核函数。
Sigmoid核函数:
采用sigmoid核函数,支持向量机实现的就是一种多层神经网络。
当然,那么我们对面对线性不可分的数据的时候,如何选择合适的核函数呢?一般如果我们有一定的先验知识的话,就可以选择符合数据分布特征的核函数;如果要是不知道的话,则利用交叉验证的方式选择不同的核函数,最后选择误差最小的那个。
如果特征的数量大到和样本数量差不多,则选用LR或者线性核的SVM;
如果特征的数量小,样本的数量正常,则选用SVM+高斯核函数;
如果特征的数量小,而样本的数量很大,则需要手工添加一些特征从而变成第一种情况。
Spark的GC调优
由于Spark立足于内存计算,常常需要在内存中存放大量数据,因此也更依赖JVM的垃圾回收机制(GC)。并且同时,它也支持兼容批处理和流式处理,对于程序吞吐量和延迟都有较高要求,因此GC参数的调优在Spark应用实践中显得尤为重要。
在运行Spark应用时,有些问题是由于GC所带来的,例如垃圾回收时间久、程序长时间无响应,甚至造成程序崩溃或者作业失败。
按照经验来说,当我们配置垃圾收集器时,主要有两种策略——ParallelGC(吞吐量优先)和CMSGC(低延迟响应)。
前者注重更高的吞吐量,而后者则注重更低的延迟。两者似乎是鱼和熊掌,不能兼得。在实际应用中,我们只能根据应用对性能瓶颈的侧重性,来选取合适的垃圾收集器。例如,当我们运行需要有实时响应的场景的应用时,我们一般选用CMSGC,而运行一些离线分析程序时,则选用ParallelGC。
那么对于Spark这种既支持流式计算,又支持传统的批处理运算的计算框架来说,是否存在一组通用的配置选项呢?
通常CMSGC是企业比较常用的GC配置方案,并在长期实践中取得了比较好的效果。例如对于进程中若存在大量寿命较长的对象,ParallelGC经常带来较大的性能下降。因此,即使是批处理的程序也能从CMSGC中获益。不过,在从1.7开始的HOTSPOTJVM中,我们发现了一个新的GC设置项:Garbage-FirstGC(G1GC)。Oracle将其定位为CMSGC的长期演进,这让我们重燃了鱼与熊掌兼得的希望!
GC算法原理
在传统JVM内存管理中,我们把Heap空间分为Young/Old两个分区,Young分区又包括一个Eden和两个Survivor分区,如下图所示。新产生的对象首先会被存放在Eden区,而每次minorGC发生时,JVM一方面将Eden分区内存活的对象拷贝到一个空的Survivor分区,另一方面将另一个正在被使用的Survivor分区中的存活对象也拷贝到空的Survivor分区内。
在此过程中,JVM始终保持一个Survivor分区处于全空的状态。一个对象在两个Survivor之间的拷贝到一定次数后,如果还是存活的,就将其拷入Old分区。当Old分区没有足够空间时,GC会停下所有程序线程,进行FullGC,即对Old区中的对象进行整理。注意:FullGC时,所有线程都暂停,所以这个阶段被称为Stop-The-World(STW),也是大多数GC算法中对性能影响最大的部分。
而G1GC则完全改变了这一传统思路。它将整个Heap分为若干个预先设定的小区域块,每个区域块内部不再进行新旧分区,而是将整个区域块标记为Eden/Survivor/Old。当创建新对象时,它首先被存放到某一个可用区块(Region)中。当该区块满了,JVM就会创建新的区块存放对象。当发生minorGC时,JVM将一个或几个区块中存活的对象拷贝到一个新的区块中,并在空余的空间中选择几个全新区块作为新的Eden分区。当所有区域中都有存活对象,找不到全空区块时,才发生FullGC。即G1GC发生FullGC的频次要比其他GC更低,因为内存使用率很高。
而在标记存活对象时,G1使用RememberSet的概念,将每个分区外指向分区内的引用记录在该分区的RememberSet中,避免了对整个Heap的扫描,使得各个分区的GC更加独立。
在这样的背景下,我们可以看出G1GC大大提高了触发FullGC时的Heap占用率,同时也使得MinorGC的暂停时间更加可控,对于内存较大的环境非常友好。因为G1GC对于内存的使用率特别高,内存越大,此优势越明显。
关于HotspotJVM所支持的完整的GC参数列表,可以参见Oracle官方的文档中对部分参数的解释。
Spark的内存管理
Spark的核心概念是RDD,实际运行中内存消耗都与RDD密切相关。Spark允许用户将应用中重复使用的RDD数据持久化缓存起来,从而避免反复计算的开销,而RDD的持久化形态之一就是将全部或者部分数据缓存在JVM的Heap中。当我们观察到GC延迟影响效率时,应当先检查Spark应用本身是否有效利用有限的内存空间。RDD占用的内存空间比较少的话,程序运行的heap空间也会比较宽松,GC效率也会相应提高;而RDD如果占用大量空间的话,则会带来巨大的性能损失。
下面从某个用户案例来说明:
这个应用其本质就是一个简单的迭代计算。而每次迭代计算依赖于上一次的迭代结果,因此每次迭代结果都会被主动持久化到内存空间中。当运行用户程序时,我们观察到随着迭代次数的增加,进程占用的内存空间不断快速增长,GC问题越来越突出。
造成这个问题的原因是没有及时释放掉不再使用的RDD,从而造成了内存空间不断增长,触发了更多GC执行。
小结:当观察到GC频繁或者延时长的情况,也可能是Spark进程或者应用中内存空间没有有效利用。所以可以尝试检查是否存在RDD持久化后未得到及时释放等情况。
选择垃圾收集器
在解决了应用本身的问题之后,我们就要开始针对Spark应用的GC调优了。
Spark默认使用的是ParallelGC。经调研我们发现,ParallelGC常常受困于FullGC,而每次FullGC都给性能带来了较大的下降。而ParallelGC可以进行参数调优的空间也非常有限,我们只能通过调节一些基本参数来提高性能,如各年代分区大小比例、进入老年代前的拷贝次数等。而且这些调优策略只能推迟FullGC的到来,如果是长期运行的应用,ParallelGC调优的意义就非常有限了。
ConfigurationOptions-XX:+UseParallelGC-XX:+UseParallelOldGC-XX:+PrintFlagsFinal-XX:+PrintReferenceGC-verbose:gc-XX:+PrintGCDetails-XX:+PrintGCTimeStamps-XX:+PrintAdaptiveSizePolicy-Xms88g-Xmx88g
G1GC的配置
ConfigurationOptions-XX:+UseG1GC-XX:+PrintFlagsFinal-XX:+PrintReferenceGC-verbose:gc-XX:+PrintGCDetails-XX:+PrintGCTimeStamps-XX:+PrintAdaptiveSizePolicy-XX:+UnlockDiagnosticVMOptions-XX:+G1SummarizeConcMark-Xms88g-Xmx88g
大多数情况下,最大的性能下降是由FullGC导致的,G1GC也不例外,所以当使用G1GC时,需要根据一定的实际情况进行参数配置,这需要很丰富的工作经验和运维经验,以下仅提供一些处理思路。
比如G1GC收集器在将某个需要垃圾回收的分区进行回收时,无法找到一个能将其中存活对象拷贝过去的空闲分区。这种情况被称为EvacuationFailure,常常会引发FullGC。对于这种情况,我们常见的处理办法有两种:
将InitiatingHeapOccupancyPercent参数调低(默认值是45),可以使G1GC收集器更早开始MixedGC(MinorGC);但另一方面,会增加GC发生频率。(启动并发GC周期时的堆内存占用百分比.G1之类的垃圾收集器用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比.值为0则表示"一直执行GC循环".默认值为45.)
降低此值,会提高MinorGC的频率,但是会推迟FullGC的到来。
提高ConcGCThreads的值,在MixedGC阶段投入更多的并发线程,争取提高每次暂停的效率。但是此参数会占用一定的有效工作线程资源。
调试这两个参数可以有效降低FullGC出现的概率。FullGC被消除之后,最终的性能获得了大幅提升。
此外,可能还会遇到这样的情况:出现了一些比G1的一个分区的一半更大的对象。对于这些对象,G1会专门在Heap上开出一个个HumongousArea来存放,每个分区只放一个对象。但是申请这么大的空间是比较耗时的,而且这些区域也仅当FullGC时才进行处理,所以我们要尽量减少这样的对象产生。或者提高G1HeapRegionSize的值减少HumongousArea的创建。(G1HeapRegionSize=n使用G1时Java堆会被分为大小统一的的区(region)。此参数可以指定每个heap区的大小.默认值将根据heapsize算出最优解.最小值为1Mb,最大值为32Mb.)
不过在内存比较大的时,JVM默认把这个值设到了最大(32M),此时我们只能通过分析程序本身找到这些对象并且尽量减少这样的对象产生。当然,相信随着G1GC的发展,在后期的版本中相信这个最大值也会越来越大,毕竟G1号称是在1024~2048个Region时能够获得最佳性能。
ConfigurationOptions-XX:+UseG1GC-XX:+PrintFlagsFinal-XX:+PrintReferenceGC-verbose:gc-XX:+PrintGCDetails-XX:+PrintGCTimeStamps-XX:+PrintAdaptiveSizePolicy-XX:+UnlockDiagnosticVMOptions-XX:+G1SummarizeConcMark-Xms88g-Xmx88g-XX:InitiatingHeapOccupancyPercent=35-XX:ConcGCThread=20
总结
对于大量依赖于内存计算的Spark应用,GC调优显得尤为重要。在发现GC问题的时候,不要着急调试GC。而是先考虑是否存在Spark进程内存管理的效率问题,例如RDD缓存的持久化和释放。至于GC参数的调试,首先我们比较推荐使用G1GC来运行Spark应用。相较于传统的垃圾收集器,随着G1的不断成熟,需要配置的选项会更少,能同时满足高吞吐量和低延迟的寻求。当然,GC的调优不是绝对的,不同的应用会有不同应用的特性,掌握根据GC日志进行调优的方法,才能以不变应万变。最后,也不能忘了先对程序本身的逻辑和代码编写进行考量,例如减少中间变量的创建或者复制,控制大对象的创建,将长期存活对象放在Off-heap中等等。
---------------------
作者:Bonyin
原文:





