1.数据的并行度是资源的并行度的两到三倍
2.Spark的shuffle和MR的shuffle不同
3.Troubleshooting 解决算子函数返回NUll导致问题:
在返回的时候,返回一些特殊的值,不要返回null,比如“-999”;2、在通过算子获取到了一个RDD之后,可以对这个RDD执行filter操作,进行数据过滤。filter内,可以对数据进行判定,如果是-999,那么就返回false,给过滤掉就可以了。;3、算子调优的coalesce算子,在filter之后,可以用coalesce算子压缩一下RDD的partition的数量,让各个partition的数据比较紧凑一些,提升性能。
4.cache和checkpoint的正确使用方式:
缓存在内存中的数据,可能莫名其妙就丢失掉了,Executor进程挂掉了以及存储在磁盘文件中的数据丢失和文件被误删了等情况checkpoint,可以作为是cache的备胎。cache失效,checkpoint就可以上来使用了。checkpoint有利有弊,利在于,提高了spark作业的可靠性,不用重新计算大量的rdd;但是弊在于,进行checkpoint操作的时候,也就是将rdd数据写入hdfs中的时候,
用性能换可靠性。先做了缓冲后做了checkpoint,比如到上面进行checkpoint操作!后面我们再对这个RDD进行使用,然后有个组件叫CacheManager,然后CacheManager会到BlockManager上面去找数据,如果有就通过CacheManager拿到数据,如果没有就会从checkpoint的地方去拿数据
checkpoint原理:
1、在代码中,用SparkContext,设置一个checkpoint目录,可以是一个容错文件系统的目录,比如hdfs;
2、在代码中,对需要checkpoint的rdd,RDD.checkpoint();
3、RDDCheckpointData(spark内部的API),接管你的RDD,会标记为marked for checkpoint,准备进行checkpoint
4、job运行完,调用一个finalRDD.doCheckpoint()方法,会顺着rdd lineage,回溯扫描,发现有标记为待checkpoint的rdd,就会进行二次标记,
5、inProgressCheckpoint,正在接受checkpoint操作job执行后,启动一个内部的新job,去将标记为inProgressCheckpoint的rdd的数据,都写入hdfs文件中。(备注,如果rdd之前cache过,从缓存中获取数据,写入hdfs中;如果没有cache过,那么重新计算一遍这个rdd,再checkpoint)
6、将checkpoint过的rdd之前的依赖rdd,改成一个CheckpointRDD*,强制改变你的rdd的lineage。后面如果rdd的cache数据获取失败,直接会通过它的上游CheckpointRDD,去容错的文件系统,比如hdfs中,获取checkpoint的数据。
checkpoint的使用:
1、SparkContext,设置checkpoint目录;2、对RDD执行checkpoint操作,还是会消耗性能的。
shuffle:spark的reduce端默认的buffer是48MB,减少reduce端task缓冲的大小,buffer溢写系数降低,使HashMap处理的数据减小,虽然性能降低,但是传输平稳
map端的task是不断的输出数据的,数据量可能是很大的。
但是,其实reduce端的task,并不是等到map端task将属于自己的那份数据全部写入磁盘文件之后,再去拉取的。map端写一点数据,reduce端task就会拉取一小部分数据,立即进行后面的聚合
算子函数的应用。
每次reduece能够拉取多少数据,就由buffer来决定。因为拉取过来的数据,都是先放在buffer中的。然后才用后面的executor分配的堆内存占比(0.2),hashmap,去进行后续的聚合、函数的执行。
reduce端缓冲(buffer),可能是会出现,默认是48MB,也许大多数时候,reduce端task一边拉取一边计算,不一定一直都会拉满48M的数据。可能大多数时候,拉取个10M数据,就计算掉了。
大多数时候,也许不会出现什么问题。但是有的时候,map端的数据量特别大,然后写出的速度特别快。reduce端所有task,拉取的时候,全部达到自己的缓冲的最大极限值,缓冲,48M,全部填满。再加上你的reduce端执行的聚合函数的代码,可能会创建大量的对象。也许,一下子,内存就撑不住了,就会OOM。reduce端的内存中,就会发生内存溢出的问题。
针对上述的可能出现的问题,应该减少reduce端task缓冲大小。宁愿多拉取几次,但是每次同时能够拉取到reduce端每个task的数量比较少,不容易发生OOM内存溢出的问题。(比如,可以调节成12M)在实际生产环境中,我们都是碰到过这种问题的。这是典型的以性能换执行的原理。reduce端缓冲小了,不容易OOM了,但是,性能一定是有所下降的,你要拉取的次数就多了。就走更多的网络传输开销。这种时候,只能采取牺牲性能的方式了,spark作业,首先,第一要义,就是一定要让它可以跑起来。分享一个经验,曾经写过一个特别复杂的spark作业,写完代码以后,半个月之内,就是跑不起来,里面各种各样的问题,需要进行troubleshooting。调节了十几个参数,其中就包括这个reduce端缓冲的大小。总算作业可以跑起来了。然后才去考虑性能的调优。
reduce端缓冲大小的另外一面,关于性能调优的一面:
假如说,你的Map端输出的数据量也不是特别大,然后你的整个application的资源也特别充足。
200个executor、5个cpu core、10G内存。
可以尝试去增加这个reduce端缓冲大小的,比如从48M,变成96M。那么这样的话,每次reduce task能够拉取的数据量就很大。需要拉取的次数也就变少了。比如原先需要拉取100次,
现在只要拉取50次就可以执行完了。对网络传输性能开销的减少,以及reduce端聚合操作执行的次数的减少,都是有帮助的。
最终达到的效果,就应该是性能上的一定程度上的提升。
一定要注意,资源足够的时候,再去做这个事儿。
spark.reducer.maxSizeInFlight,48
spark.reducer.maxSizeInFlight,24

5.troubleshooting JVM GC导致的shuffle文件拉取失败
比如,executor的JVM进程,可能内存不是很够用了。那么此时可能就会执行GC。minor GC or full GC。总之一旦发生了JVM之后,就会导致executor内,所有的工作线程全部停止,比如BlockManager,基于netty的网络通信。有时会出现的一种情况,非常普遍,在spark的作业中;shuffle file not found。(spark作业中,非常非常常见的)而且,有的时候,它是偶尔才会出现的一种情况。有的时候,出现这种情况以后,会重新去提交stage、task。重新执行一遍,发现就好了。没有这种错误了。
log怎么看?用client模式去提交你的spark作业。比如standalone client;yarn client。一提交作业,直接可以在本地看到刷刷刷更新的log。
spark.shuffle.io.maxRetries 3
第一个参数,意思就是说,shuffle文件拉取的时候,如果没有拉取到(拉取失败),最多或重试几次(会重新拉取几次文件),默认是3次。
spark.shuffle.io.retryWait 5s
第二个参数,意思就是说,每一次重试拉取文件的时间间隔,默认是5s钟。
默认情况下,假如说第一个stage的executor正在进行漫长的full gc。第二个stage的executor尝试去
拉取文件,结果没有拉取到,默认情况下,会反复重试拉取3次,每次间隔是五秒钟。
最多只会等待3 * 5s = 15s。如果15s内,没有拉取到shuffle file。就会报出shuffle file not found。
针对这种情况,我们完全可以进行预备性的参数调节。增大上述两个参数的值,达到比较大的一个值,尽量保证第二个stage的task,一定能够拉取到上一个stage的输出文件。避免报shuffle file not found。然后可能会重新提交stage和task去执行。那样反而对性能也不好。
spark.shuffle.io.maxRetries 60
spark.shuffle.io.retryWait 60s
最多可以忍受1个小时没有拉取到shuffle file。只是去设置一个最大的可能的值,full gc不可能1个小时都没结束吧。这样就可以尽量避免因为gc导致的shuffle file not found,无法拉取到的问题。

6.troubleshooting 解决各种序列化导致的错误
你会看到什么样的序列化导致的报错?
用client模式去提交spark作业,观察本地打印出来的log。如果出现了类似于Serializable、Serialize等等字眼,报错的log,那么恭喜大家,就碰到了序列化问题导致的报错。虽然是报错,但是序列化报错,应该是属于比较简单的了,很好处理。
序列化报错要注意的点:
1、你的算子函数里面,如果使用到了外部的自定义类型的变量,那么此时,就要求你的自定义类型,必须是可序列化的。

final Teacher teacher = new Teacher(“yasaka”); 
 studentsRDD.foreach(new VoidFunction() { 
 public void call(Row row) throws Exception { 
 String teacherName = teacher.getName(); 
 …. 
 } 
 }); 
 public class Teacher implements Serializable { 
 }


2、如果要将自定义的类型,作为RDD的元素类型,那么自定义的类型也必须是可以序列化的
JavaPairRDD