1、reduce task OOM?
增大reduce端的聚合操作的内存比例
增大executor memory 内存大小 --executor-memory
减少reduce task每次拉取的数据量 设置spak.reducer.maxSizeInFlight参数
2、在shuffle阶段executor挂掉?
分析原因:(1)map task阶段的所运行的executor的内存不足,导致iexecutor挂掉了。导致executor里面的blockmanager挂掉,不能和connctionmanager建立连接,
2.executor并没有挂掉
2.1 BlockManage之间的连接失败(map task所运行的executor正在GC)
2.2建立连接成功,map task所运行的executor正在GC
3.reduce task向Driver中的MapOutputTracker获取shuffle file位置的时候出现了问题
下面有几个重要的参数的调节:
spark.shuffle.file.buffer
默认值:32k
参数说明:该参数用于设置shuffle write task的BufferedOutputStream的buffer缓冲大小。将数据写到磁盘文件之前,会先写入buffer缓冲中待缓冲写满之后,才会溢写到磁盘。
调优建议:如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如64k),从而减少shuffle write过程中溢写磁盘文件的次数,也就可以减少磁盘IO次数,进而提升性能。在实践中发现,合理调节该参数,性能会有1%~5%的提升。
spark.reducer.maxSizeInFlight
默认值:48m
参数说明:该参数用于设置shuffle read task的buffer缓冲大小,而这个buffer缓冲决定了每次能够拉取多少数据。
调优建议:如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如96m),从而减少拉取数据的次数,也就可以减少网络传输的次数,进而提升性能。在实践中发现,合理调节该参数,性能会有1%~5%的提升。
错误:reduce oom
reduce task去map拉数据,reduce 一边拉数据一边聚合 reduce段有一块聚合内存(executor memory * 0.2)
解决办法:1、增加reduce 聚合的内存的比例 设置spark.shuffle.memoryFraction
2、 增加executor memory的大小 --executor-memory 5G
3、减少reduce task每次拉取的数据量 设置spark.reducer.maxSizeInFlight 24m
spark.shuffle.io.maxRetries
默认值:3
参数说明:shuffle read task从shuffle write task所在节点拉取属于自己的数据时,如果因为网络异常导致拉取失败,是会自动进行重试的。该参数就代表了可以重试的最大次数。如果在指定次数之内拉取还是没有成功,就可能会导致作业执行失败。
调优建议:对于那些包含了特别耗时的shuffle操作的作业,建议增加重试最大次数(比如60次),以避免由于JVM的full gc或者网络不稳定等因素导致的数据拉取失败。在实践中发现,对于针对超大数据量(数十亿~上百亿)的shuffle过程,调节该参数可以大幅度提升稳定性。
shuffle file not find taskScheduler不负责重试task,由DAGScheduler负责重试stage
spark.shuffle.io.retryWait
默认值:5s
参数说明:具体解释同上,该参数代表了每次重试拉取数据的等待间隔,默认是5s。
调优建议:建议加大间隔时长(比如60s),以增加shuffle操作的稳定性。
spark.shuffle.memoryFraction
默认值:0.2
参数说明:该参数代表了Executor内存中,分配给shuffle read task进行聚合操作的内存比例,默认是20%。
调优建议:在资源参数调优中讲解过这个参数。如果内存充足,而且很少使用持久化操作,建议调高这个比例,给shuffle read的聚合操作更多内存,
以避免由于内存不足导致聚合过程中频繁读写磁盘。在实践中发现,合理调节该参数可以将性能提升10%左右。
spark.shuffle.manager
默认值:sort
参数说明:该参数用于设置ShuffleManager的类型。Spark 1.5以后,有三个可选项:hash、sort和tungsten-sort。HashShuffleManager是Spark 1.2以前的默认选项,但是Spark 1.2以及之后的版本默认都是SortShuffleManager了。tungsten-sort与sort类似,但是使用了tungsten计划中的堆外内存管理机制,内存使用效率更高。
调优建议:由于SortShuffleManager默认会对数据进行排序,因此如果你的业务逻辑中需要该排序机制的话,则使用默认的SortShuffleManager就可以;而如果你的业务逻辑不需要对数据进行排序,那么建议参考后面的几个参数调优,通过bypass机制或优化的HashShuffleManager来避免排序操作,同时提供较好的磁盘读写性能。这里要注意的是,tungsten-sort要慎用,因为之前发现了一些相应的bug。
spark.shuffle.sort.bypassMergeThreshold
默认值:200
参数说明:当ShuffleManager为SortShuffleManager时,如果shuffle read task的数量小于这个阈值(默认是200),则shuffle write过程中不会进行排序操作,而是直接按照未经优化的HashShuffleManager的方式去写数据,但是最后会将每个task产生的所有临时磁盘文件都合并成一个文件,并会创建单独的索引文件。
调优建议:当你使用SortShuffleManager时,如果的确不需要排序操作,那么建议将这个参数调大一些,大于shuffle read task的数量。那么此时就会自动启用bypass机制,map-side就不会进行排序了,减少了排序的性能开销。但是这种方式下,依然会产生大量的磁盘文件,因此shuffle write性能有待提高。
spark.shuffle.consolidateFiles
默认值:false
参数说明:如果使用HashShuffleManager,该参数有效。如果设置为true,那么就会开启consolidate机制,会大幅度合并shuffle write的输出文件,对于shuffle read task数量特别多的情况下,这种方法可以极大地减少磁盘IO开销,提升性能。
调优建议:如果的确不需要SortShuffleManager的排序机制,那么除了使用bypass机制,还可以尝试将spark.shffle.manager参数手动指定为hash,使用HashShuffleManager,同时开启consolidate机制。在实践中尝试过,发现其性能比开启了bypass机制的SortShuffleManager要高出10%~30%。
1、资源调优
在开发过程中,增加或者分配更多的资源罪域任务的执行的效率是显而易见的。但是资源本身是受到限制的,那么我们该如何解决这个问题呢?
一般在开发的时候,我们的提交任务的脚本
Spark-submit.sh 内容如下:
#!/bin/bash /opt/modules/spark-1.6.1-bin-2.5.0-cdh5.3.6/bin/spark-submit \ --class com.hypers.sparkproject.spark.session.UserVisitSessionAnalyzeSpark \ --num-executors 3 \ --配置executor的数量 --driver-memory 1024M \ --配置driver的内存,影响不大 --executor-memory 2G \ --配置每个executor的内存大小 --executor-cores 3 \ --Spark standalone and YARN only --配置每个executor的cpu核数 /usr/loacl/recommend-1.0-SNAPSHOT.jar \ |
分配多少合理呢?
第一种:Spark Standalone即Spark运行在自己的分布式框架时,需要知道每台机器能够使用的内存,CPU核数,假如每台机器能够使用4G内存和2个CPU核数,一共20台机器,那么就可以executor数量设置20,每个executor内存设置4G,每个executor设置2 CPU core
第二种: Yarn 当Spark运行在yarn上时,需要查看资源队列有多少资源,假如资源队列有500G内存,100个CPU core可用,那么就可以设置50个executor,每个executor内存设置10G,每个executor设置2个CPU core
2、设置并行度
设置task的并行度
保证并行度与你设置的资源相匹配,不至于会浪费资源。例如10个executor,每个内存是4GB, 2个CPU, 10个task, 总的cpu20个,可以并行运行20个task。现在假设你要让每个executor都运行一个task. 这样的话,其实每一个executor上面就有一个空闲的cpu, 导致并行度与设置的任务不相符合,就浪费掉了资源。
那么根据官方的建议:task的数量设置成spark application的cpu总数的2-3倍。
3、重构RDD以及RDD序列化
原则一:尽量去复用RDD,
原则二:公共RDD进行持久化到内存或者磁盘上面,那么之后对于这个RDD的操作都是直接取的持久化的数据
原则三:持久化数据进行序列化的操作;
原则四:持久化+双副本机制
为了数据的高可靠性,而且内存充足,可以使用双副本机制进行持久化
持久化的双副本机制,持久化后的一个副本,因为机器宕机了,副本丢了,就还是得重新计算一次;持久化的每个数据单元,存储一份副本,放在其他节点上面;从而进行容错;一个副本丢了,不用重新计算,还可以使用另外一份副本。
- 广播变量:将大变量广播出去而不是直接使用的。
为什么要用Broadcast
当进行随机抽取一些操作,或者从某个表里读取一些维度的数据,比如所有商品品类的信息,在某个算子函数中要使用到,加入该数据大小为100M,那么1000个task将会消耗100G的内存, 集群损失不可估量
Broadcast的原理
默认的情况下,每个task执行的算子中,使用到了外部的变量,每个task都会获取一份变量的副本,所以会消耗很多的内存,进而导致RDD持久化内存不够等情况,大大影响执行速度
广播变量,在driver上会有一份初始的副本,task在运行的时候,如果要使用广播变量中的数据,首先会在自己本地的Executor对应的BlockManager中尝试获取变量副本,并保存在本地的BlockManager中,此后这个Executor上的所有task,都会直接使用本地的BlockManager中的副本,Executor的BlockManager除了从driver上拉取,也可能从其他节点的BlockManager上拉取变量副本,距离越近越好.
总而言之: 广播变量的好处不是每一个task一份变量副本,而是变成每个节点的executor才一份副本,这样的话就可以变量产生的副本大大减少
4、Kryo序列化的使用
默认是使用了java的序列化机制ObjectInoutStream/ObjectoutputStream ,,这种序列化的方式简单,并且便于处理。但是效率不高,而且在进行RDd的持久化操作的时候,内存占比相对比较大。那么利用kryo序列化的好处:
- 算子函数中使用的外部变量,在经过kryo序列化之后,会优化网络传输的性能,优化集群中内存的占用和消耗.
- 持久化RDD的时候,优化内存的占用和消耗
- 优化shuffle操作的网络传输的数据
设置kryo 并且要注册自动义的类 set("spark.serializer", "org.apache.spark.serializer.KryoSerializer") .registerKryoClasses(new Class[]{CategorySortKey.class}) |
5、Fastuil库
fastutil 是扩展了java标准集合框架(Map,List,Set,HashMap,ArrayList,HashSet)的类库,提供了特殊类型的Map,Set,List和queue,fastutil能够提供更小的内存占用,更快的存取速度,fastutil也提供了64位的array、set和list,以及高性能快速的,以及实用的IO类,来处理二进制和文本类型的文件;
fastutil最新版本要求Java 7以及以上版本
Spark中fastutil应用场景:
1.如果算子函数使用了外部变量,那么可以用三步来优化:
a.使用Broadcast广播变量优化,
b. 使用Kryo序列化类库优化,提升性能和效率,
c.如果外部变量是某种比较大的集合,可以使用fastutil改写外部变量
2.在算子函数中,如果要创建比较大的Map.List等集合,可以考虑将这些集合类型使用fastutil类库重写。
Maven库:
<dependency> <groupId>fastutil</groupId> <artifactId>fastutil</artifactId> <version>5.0.9</version> </dependency> |
6、数据本地化等待时长
Spark在Driver上面对application的每一个stage的task进行分配之前,都会计算每个task分配的是RDd的那个partitoion的数据呢?
Spark的task分配算法优先会希望每个task正好分配到它要计算的数据所在的节点,这样就避免了网络间传输数据。
但是,task可能没有机会分配到它的数据所在的节点,因为可能计算资源和计算能力都满了,这种情况下,Spark会等待一段时间,过了这个时间,才会选择一个比较差的本地化级别,比如将这个task分配到相邻的一个节点上,这个时候肯定发生网络传输,会通过一个getRemote()方法,通过TransferService(网络数据传输组件)从数据所在节点的BlockManager中获取数据,上述中的一段时间即为本地化等待时长。那么如何调整这个等待时间呢?
PROCESS_LOCAL:进程本地化,代码和数据在同一个进程中,也就是在同一个executor中;计算数据的task由executor执行,数据在executor的BlockManager中;性能最好 NODE_LOCAL:节点本地化,代码和数据在同一个节点中;比如说,数据作为一个HDFSblock块,就在节点上,而task在节点上某个executor中运行;或者是,数据和task在一个节点上的不同executor中;数据需要在进程间进行传输 NO_PREF:对于task来说,数据从哪里获取都一样,没有好坏之分 RACK_LOCAL:机架本地化,数据和task在一个机架的两个节点上;数据需要通过网络在节点之间进行传输 ANY:数据和task可能在集群中的任何地方,而且不在一个机架中,性能最差。 |
如果大多都是PROCESS_LOCAL,那就不用调节了
如果是发现,好多的级别都是NODE_LOCAL、ANY,那么最好就去调节一下数据本地化的等待时长
调节完,应该是要反复调节,每次调节完以后,再来运行,观察日志
看看大部分的task的本地化级别有没有提升;看看,整个spark作业的运行时间有没有缩短
调节方法:
spark.locality.wait,默认是3s;可以调节为6s,10s 默认情况下,下面3个的等待时长,都是跟上面那个是一样的,都是3s spark.locality.wait.process spark.locality.wait.node spark.locality.wait.rack new SparkConf() .set("spark.locality.wait", "10") |
Young/New Generation
年轻代与Spark调优息息相关,所以这里单独拿出来讲解
所有新生成的对象首先都是放在年轻代中,年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象
大部分对象在Eden区生成,当Eden区满时,还存活的对象将被复制到Survivor区中(两中的一个),当这个Survivor区满的时候,此区存放的对象会被放在另一个Survivor区中,当另一个Survivor也满的时候,从第一个Survivor复制过来的还存活的对象将被复制到老年代中,Survivor的两个区是对称的,没有先后关系,所以同一个Survivor取中可能存在从Eden复制过来的对象和从另一个Survivor复制过来的对象,而且Survivor总有一个是空的,而且可以配置多余两个
7、Spark的JVM调优
降低cache操作的内存占比
Spark task执行算子函数时会生成大量对象,这些对象会被放入年轻代中,当年轻代内存比较小时,会导致年轻代中Eden区和Survivor区频繁内存溢满,导致频繁的minor GC,而频繁的minorGC或导致一些存活的短声明周期(其实就是在后面用不到的对象)对象直接放入老年代中,而当老年代内存溢满是,则会导致Full GC
full gc / minor gc,无论是快,还是慢,都会导致jvm的工作线程停止工作,简而言之,就是说,gc的时候,spark停止工作了。等着垃圾回收结束。
总而言之,上面的情况都是由内存不足引起的即内存不充足的时候,问题:
1、频繁minor gc,也会导致频繁spark停止工作
2、老年代囤积大量活跃对象(短生命周期的对象),导致频繁full gc,full gc时间很长,短则数十秒,长则数分钟,甚至数小时。可能导致spark长时间停止工作。
3、严重影响咱们的spark的性能和运行的速度。
如何增大内存?
Spark中,堆内存又被划分成了两块,一块是专门用来给RDD的cache,persist操作进行RDD缓存用的,另一块就是用来给Spark算子函数用的,存放函数中自己创建的对象
默认情况下,给RDD的cache操作的内存占比是0.6,即百分之六十的内存用来给RDD做缓存用,但其实RDD并不需要这么大的内存,我们可以通过查看每个stage中每个task运行的时间,GC时间等来判断是否发生了频繁的minorGC和fullGC,从而来调低这个比例
调节方法:
spark.storage.memoryFraction,0.6 -> 0.5 -> 0.4 -> 0.2 |
Ececutor堆外内存
当Spark处理超大数据量时(数十亿,百亿级别),executor的堆外内存可能会不够用,出现shuffle file can’t find, task lost,OOM等情况
默认情况下,这个堆外内存是300M,当运行超大数据量时,通常会出现问题,因此需要调节到1G,2G,4G等大小
调节方法必须在spark-submit提交脚本中设置而不能在程序中设置
--conf spark.yarn.executor.memoryOverhead=2048 |
GC引起的连接等待时长
Spark在处理超大数据量时,task可能会创建很大很多的对象,频繁的让JVM内存溢满,导致频繁GC,而前面提到过executor获取数据优先的从本地关联的blockmanager获取,如果没有的话,会通过transferService去远程连接其他executor的blockmanager,如果正好碰到那个executor垃圾回收,那么程序就会卡住,spark默认网络连接时长是60s,当超过60s没有获取到数据,则直接宣告任务失败,也有可能DAGscheduler反复提交几次stage,TaskScheduler反复提交task,则会大大影响spark运行速度,所以可以考虑适当调节等待时长
调节方式同调节堆外内存一样,必须在提交spark程序的脚本中设置
--conf spark.core.connection.ack.wait.timeout=300 |
8、shuffle调优
什么情况下面会发生shuffle操作呢? 尽量使用高性能的算子(但是有要注意可能会发生OOM)
在Spark中,主要有以下几个算子
- groupByKey : 把分布在各个节点上的数据中的同一个key对应的value都集中到一块儿,集中到集群中的一个节点中,也即是集中到一个节点的executor的一个task中
- reduceByKey : 算子函数对values集合进行reduce操作,最后生成一个value
- countByKey : 在一个task中获取同一个key对应的所有value,然后计数,统计总共有多少value
- join : 两个RDD,Key相同的两个value都集中到一个executor的task中
Shuffle操作的过程:
在一个shuffle过程中,前半部分stage中,每个task都会创建后半部分stage中相同task数量的文件,比如stage后半部分有100个task,那么前半部分的每个task都会创建100个文件(先写入到内存缓冲中,后溢满写入到磁盘),会将同一个key对应的values写入同一个文件中,shuffle后半部分的stage中的task,每个task都会从各个节点的task创建其中一份属于自己的那份文件中,拉取属于自己的key-value对,然后task会有一个内存缓冲区,然后调用HashMap进行key-values的聚合,最终调用我们定义的聚合函数来进行相应的操作。
Shuffle调优合并map端输出的文件
默认情况下spark是不开启map端输出文件的合并的机制的,当spark在分批次执行task的时候,task每次都会创建新的文件,而不会共享这些数据的,所以开启合并机制能够提升性能:
new SparkConf().set("spark.shuffle.consolidateFiles", "true") |
设置合并机制之后:
第一个stage,并行运行2个task,运行这两个task时会创建下一个stage的文件,运行完之后,会运行下一批次的2个task,而这一批次的task则不会创建新的文件,会复用上一批次的task创建的文件
第二stage的task在拉取上一个stage创建的文件时就不会拉取那么多文件了,而是拉取少量文件,每个输出文件都可能包含了多个task给自己的map端输出。
shuffle调优之map端内存缓冲和reduce内存占比
默认情况下:
每个task的内存缓冲为32kb,reduce端内存占比为0.2(即默认executor内存中划分给reduce task的微20%)
所以在不调优的情况下,如果map端task处理的比较大,内存不足则溢满写入磁盘
比如:
每个task就处理320kb,32kb,总共会向磁盘溢写320 / 32 = 10次。
每个task处理32000kb,32kb,总共会向磁盘溢写32000 / 32 = 1000次。
同理,ruduce端也一样
何时调优?
通过Spark UI查看shuffle磁盘的read和write是不是很大,如果很大则应相应调优
如何调优?
spark.shuffle.file.buffer : 32kb -> 128kb spark.shuffle.memoryFraction: 0.2 -> 0.3 |
9、spark算子调优
MapPartitions提升Map类操作性能
spark中,最基本的原则是每个task处理一个RDD的partition
如果是普通的Map,假如一个partition中有一万条数据,那么map中的function就要执行和计算一万次,但是使用MapPartitions操作之后,一个task只会执行一次function,function一次接收了所有partition数据,性能比较高
MapPartitions的缺点:
如果是普通的Map,一条一条处理数据,当出现内存不够的情况时,那么就可以将已经处理掉的数据从内存里面垃圾回收掉,所以普通map通常不会出现OOM情况
如果是MapPartitions,对于大量数据来说,如果一个partiton数据有一百万条,一次性传入function之后,可能导致内存不足,但是又没办法腾出空间,直接就导致了内存溢出,OOM
所以,当使用MapPartitons算子时,要估算每个partiton的数据能不能一下子缓存到分配给executor的内存中,如果可以,就是用该算子,对性能有显著提升
所以对于map和mapPartition的区别使用。看具体场景
GroupByKey和ReduceByKey的区别
也是从存在效率问题的。
10、Spark算子调优之filter过后使用coalesce减少分区数量
Spark程序经过filter之后会出现以下两种情况的:
- 导致每个partition里面的数据不一样的,有的很多,而有的数据很少,导致在 后面的操作会数据倾斜的。
- 由于partition的数据量减少,在后面的计算还是按照跟partition的task相同的数据量,这样就导致了资源的浪费。
12. Spark算子调优之foreachPartition优化写数据库性能
foreach是对每条数据进行处理的,task对partition中的每一条数据都会执行function操作,如果function中有写数据库的操作,那么有多少条数据就会创建和销毁多少个数据库连接,这对性能的影响很大
在生产环境中,通常都是使用foreachPartition来写数据库的,使用了该算子之后,对于用户自定义的function函数,就调用一次,一次传入一个partition的所有数据,这里只需创建一个数据库连接,然后向数据库发送一条sql语句外加多组参数即可,但是这个时候要配合数据库的批处理
同样,该算子在超大数据量面前同样会出现OOM情况
13. Spark算子调优之使用repartition解决SparkSQL低并行度性能问题
通常情况下,在上面第二条的并行度调优时,使用spark.default.parallelism来设置并行度,那么这个设置在什么地方有效,什么地方无效?
当程序中没有使用SparkSQL,那么整个sparkapplication的所有stage的并行度都是设置的这个参数,除非使用了coalesce算子缩减过partition.
当程序中使用了SparkSQL,那么SparkSQl的那么stage的并行度无法设置,因为SparkSQL会默认的根据Hive表对应的hdfs文件的block,自动设置SparkSQL那个stage的并行度,所以这就导致出现了用少量task来处理复杂逻辑的情况, 这种情况下,需要使用repartition来设置SparkSQL的并行度,即对从Hive中读取出来的RDD,使用repartiton重新分区为预期的数量来设置并行度。
14、spark算子reduceBykey进行本地的聚合操作
在map端,给下个stage的每个task创建的输出文件中,写数据之前,会进行本地的combiner操作,也就是说,对每一个key,对应的values都会执行用户自定义的算子函数,比如+_,当进行了这个combiner操作之后,减少了数据量,也即是减少了磁盘IO,同时减少了网络传输,对性能有明显提升,所以,在实际的项目中,能用reduceByKey实现的就尽量用该算子实现。
15、数据倾斜
1、数据倾斜的现象:
数据倾斜是Spark中极其影响性能的现象,它甚至能导致程序无法跑完,更不用提性能调优什么的了
数据倾斜如何产生的?
在shuffle操作的时候,是按照key来进行value的数据的输出,拉取和聚合的,同一个key的values,一定是分配到同一个reduce task进行处理的,假如多个key对应的value一共有90万条数据,但是可能某条key对应了88万条,其他key最多也就对应数万条数据,那么处理这88万条数据的reduce task肯定会特别耗费时间,甚至会直接导致OOM,这就是所谓的数据倾斜
- 解决方案:
1.聚合源数据
Spark的数据源通常情况下都是来自于Hive表,(HDFS或其它大数据分布式系统),而Hive本身就是适合做离线数据分析的,所以说通常要变换一下思路,能在Hive中做聚合的,通常就可以跑定时任务在Hive中做聚合,最终spark拿到的只是每个key对应的一个value值,然后就可以使用map来对这个特殊的value串来处理,省去了groupByKey的过程
2.过滤掉导致倾斜的key
这种情况只适合用户能够接受摒弃某些特殊的数据,比如大部分key都对应了几十万条,而少数key只对应了几十条,那么直接在Hive中过滤掉这些key就从源头上避免了数据倾斜
3.提高shuffle操作reduce端并行度
提高shuffle操作reduce端并行度会有更多task来处理数据,那么每个task处理的数据会相对来说更少一些
如何操作?
给shuffle算子传递进去一个参数,即一个数字,这个数字就代表了shuffle操作时reduce端的并行度,然后在进行shuffle操作的时候,就会对应创建指定数量的reduce task
4.使用随机key实现双重聚合
这个场景主要用于reduceByKey,groupByKey,而非join
主要原理就是:
在第一轮聚合时,对key进行打散,将原先一样的key,变成不一样的key,相当于将每个key分组,然后针对key的多个分组,进行key的局部聚合,接着再去掉key的前缀,然后对所有key进行全局聚合,这种方案对解决这两个算子产生的数据倾斜有比较好的效果
5.join算子操作的数据倾斜解决方案
将reduce join转换为map join
说明:
普通的join,肯定是要走shuffle,那么,既然走shuffle,那么普通的join肯定是reduce join, 即将所有相同的key对应的values,聚合到一个task中,再进行join操作
那么如何将reduce join转换为map join?
当两个RDD要进行join时,其中一个RDD是比较小的,那么就可将该小数据量RDD广播出去,该RDD数据将会在每个executor的blockmanager中驻留一份数据,然后在map操作中就可以使用该数据,这种方式下,根本就不会发生shuffle操作,从而从根本上杜绝了数据倾斜
总结丰巢的面试问道了spark2,0参数调优