Spark优化
一,资源调优
1.1 在部署spark集群中指定资源分配的默认参数
在spark安装包的conf下 spark-env.sh
SPARK_WORKER_CORES 每台worker节点分配的最大核数
SPARK_WORKER_MEMORY 每台worker节点分配的最大内存
SPARK_WORKER_INSTANCES 每台节点上启动的Worker数量
1.2 在提交Application的时候给当前的Application任务分配响应的资源命令
- 提交Application的时候使用选项
–executors-cores 指定每个executors 占用的核数
–executor-memory 指定每个Executor 占用的内存
–total-executors-cores 指定提交的Application占用的 核数
- 配置信息(Application的代码中设置或在Spark-default,conf 中设置)
spark.executor.cores
spark.executor.memory
spark.max.cores
- 动态分配资源
spark.shuffle.service.enabled true //启动 External shuffle Service 服务
spark.shuffle.service.port 7337 //Shuffle Service服务端口,必须和yarn-site中的一致
spark.dynamicAllocation.enabled true //开启动态资源分配
spark.dynamicAllocation.minExecutors 1 //每个Application最小分配的 executors 数
spark.dynamicAllocation.maxExecutors 30 //每个Application 最大并发分配的executors数
spark.dynamicAllocation.schedulerBackLogTimeout 1s
spark.dynamicAllcation.sustainedSchedulerBacklogTimeout 5s
二,并行度调优
- 如果读取的数据在HDFS,降低了block大小,相当于提高了RDD中的partition个数
sc.textFile("path",numPartitions)
sc.parallelize("path",numPartitions)
sc.makeRDD("path",numPartitions)
repartitions / coalesce
reduceByKey / groupByKey / join --- (xxx,numPartitions)
spark.default.parallelism numPartitions
spark.sql.shuffle.partitions 200
- 自定义分区器
package com.spark.spark.test;
import org.apache.spark.Partitioner;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.Function2;
import scala.Tuple2;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
public class ParTionerTest {
public static void main(String[] args) {
SparkConf sparkConf = new SparkConf().setMaster("local").setAppName("partioner");
JavaSparkContext sc = new JavaSparkContext(sparkConf);
JavaPairRDD<String, String> javaPairRDD = sc.parallelizePairs(Arrays.asList(
new Tuple2<>("1", "张三"),
new Tuple2<>("2", "李四"),
new Tuple2<>("3", "王五"),
new Tuple2<>("4", "小明"),
new Tuple2<>("5", "小红")
));
javaPairRDD.mapPartitionsWithIndex(new Function2<Integer, Iterator<Tuple2<String, String>>, Iterator<String>>() {
@Override
public Iterator<String> call(Integer integer, Iterator<Tuple2<String, String>> tuple2Iterator) throws Exception {
List<String> list = new ArrayList<>();
while(tuple2Iterator.hasNext()){
System.out.println("index: " + integer + " value" + tuple2Iterator.next());
}
return list.iterator();
}
},true).collect();
JavaPairRDD<String, String> partitionByPairRDD = javaPairRDD.partitionBy(new Partitioner() {
/**
* 定义分区规则
* @param key
* @return
*/
@Override
public int getPartition(Object key) {
Integer value = Integer.parseInt((String)key);
return value % 3;
}
/**
* 定义分成几个区
*
* @return
*/
@Override
public int numPartitions() {
return 3;
}
});
partitionByPairRDD.mapPartitionsWithIndex(new Function2<Integer, Iterator<Tuple2<String, String>>, Iterator<String>>() {
@Override
public Iterator<String> call(Integer integer, Iterator<Tuple2<String, String>> tuple2Iterator) throws Exception {
List<String> list = new ArrayList<>();
while(tuple2Iterator.hasNext()){
System.out.println("index: " + integer + " value" + tuple2Iterator.next());
}
return list.iterator();
}
},true).collect();
}
}
- 如果读取数据是在SparkStreaming中的,数据来源是kafka时
receiver模式:task的并行度,取决于 blockInterval 的间隔时间,默认是200ms,一般建议不低于 50ms
direct模式:取决于kafka中topic的分区数
三,代码调优
3.1 避免创建重复的RDD
val rdd1 = sc.textFile(path)
val rdd2 = sc.textFile(path)
//这就是创建了重复的 RDD
//有什么问题? 对于执行性能来说没有问题,但是呢,代码乱
3.2 复用同一个RDD
val rdd1 = RDD<String,String>
val rdd2 = rdd.map(_._2)
//这样的话 rdd2 是 rdd1 的子集。rdd2 执行了一个操作 filter
rdd2.filter() = rdd1.map((_._2)).filter()
3.3 对多次使用的RDD进行持久化
如果选择一种最合适的持久化策略?
-
默认情况下,性能最高的是 Memory_only ,但前提是你的内存足够大,可以放下整个RDD的所有数据,因为不进行序列化和反序列化的操作,避免了这部分的性能开销,对后续算子的操作,都是基于纯内存中的数据的操作,不需要从磁盘中读取,性能更高。在实际中,能用这种策略的场景有限的,如果数据比较多时,内存受限制。
-
Memory_only_Ser级别。该级别会将RDD数据序列化之后保存到内存中,此时每个partition仅仅是一个字节数组而已,大大减少了对象数量,并降低了内存占用。这种级别比Memory——only级别多出来的性能开销,主要是序列化和反序列化的开销。但是后续算子可以基于纯内存进行操作,因此性能总体还是比较高的。此外,如果是RDD数据量过多的话,还是会导致OOM内存溢出的异常。
-
Memory_AND_DISK_Ser策略。该策略会优先把数据缓存在内存中,内存缓存不下才会写入磁盘。同时会对数据进行序列化,这样数据大小会降低。
-
通常不建议使用Disk_Only 和后缀 为_2的级别:因为完全基于磁盘文件进行数据的读写,会导致性能的急剧降低,有时还不如重新计算一次RDD数据。后缀为 _2 的级别,必须将所有数据都复制一份副本,并发送到其他节点上,数据复制以及网络传输会导致较大的性能开销,除非是要求作业的高可用性,否则不建议使用。
持久化算子:
cache : Memory_Only
persist: Memory_Only Memory_Only_Ser Memory_And_Disk_ser 一般不选择带 _2的级别
checkpoint :
1.如果一个RDD的计算时间比较长或者计算起来比较复杂,一般将这个RDD的计算结果保存到HDFS上,这样数据会更加安全。
2.如果一个RDD的依赖关系非常长,也会使用checkpoint,切断依赖关系,提高容错的效率。
3.4 尽量避免使用shuffle类的算子
使用广播变量来模拟使用Join的使用情况,一个RDD比较大,一个RDD比较小。
join算子= 广播变量+filter 广播变量+map 广播变量+flatmap
3.5 使用map-side 预聚合的shuffle操作
即尽量使用有 combiner 的shuffle 类算子
combiner概念:在map端,每一个 map task计算完毕后进行的局部聚合操作
combiner好处:
1.降低shuffle write写磁盘的数据量
2.降低shuffle read 读磁盘的数据量
3.减少了reduce task聚合的次数
有combiner的 shuffle类算子:
- reduceByKey这个算子在map端是由combiner的,在一些场景中可以使用reduceByKey代替 groupByKey
- aggregateByKey --> 传入三个参数,初始值,匿名函数(map端聚合逻辑),匿名函数(reduce端聚合逻辑)
- combineByKey --> 传入三个参数,匿名函数(对传入的第一个值操作的函数),匿名函数(map端聚合逻辑),匿名函数(reduce端聚合逻辑)
3.6 尽量使用高性能的算子
- reduceByKey代替 groupByKey
- mapPartition 代替 map
- foreachPartitons 代替 foreach
- filter后使用 coalesce减少分区数
- 使用repartitionAndSortWithinPartitions (shuffle过程中排序) 代替repartition And sortByKey 类操作
- 使用repartition和coalesce算子操作分区
3.7 使用广播变量
开发过程中,会遇到需要在算子函数中使用外部变量的场景(尤其是大变量,比如100M以上的集合),此时就应该使用Spark的广播(BroadCast)功能来提升性能,函数中使用到外部变量时,默认情况下,Spark会将该变量赋值多个副本,通过网络传输到task中,此时每个task都有一个变量副本,如果变量本身比较大的话,那么大量的变量副本在网络中传输的性能开销,以及在各个节点的Executor中占用过多内存导致的频繁GC,都会极大的影响性能。如果使用的外部变量比较大,建议使用spark中的广播功能,对该变量进行广播。广播后的变量,会保证每个Executor中的内存中,只驻留一份变量副本,而Executor中的task执行时共享该Executor中的那份变量副本。这样的话,可以大大减少副本的个数,也减少了网络传输的性能开销,并减少了Executor的内存的占用开销,降低了GC的频率。
广播变量的发送方式:Executor一开始并没有广播到每个task中,而是在task运行中,需要用到广播变量,回去找Executor中的blockManager要,blockManager和Driver里面的Blockmanagermaster要。
使用广播变量可以大大的减少集群中变量的副本数,不使用广播变量,集群中的变量副本数是和task个数一样,使用广播变量集群中的变量副本数和executor的个数相同。
3.8 使用Kryo优化序列化性能
在Spark中,主要有三个地方设计到了序列化:
1.在算子函数中使用到外部变量时,改变量会被序列化后进行网络传输
2.将自定义的类型作为RDD的泛型类型时,所有自定义类型对象,都会进行序列化。因此这种情况下,也要求自定义的类型必须实现Seriallizable接口。
3.使用可序列化的持久化策略时(Memory_Only_Ser) ,Spark 会将 RDD中的每个partition都序列化成一个大的字节数组。
Kryo序列化器介绍:
Spark 支持使用 Kryo 序列化机制。Kryo 序列化机制,比默认的 Java 序列化机制,速度要快,序列化后的数据要更小,大概是 Java 序列化机制的 1/10。所以 Kryo 序列化优化以后,可以让网络传输的数据变少;在集群中耗费的内存资源大大减少。
对于这三种出现序列化的地方,我们都可以通过使用 Kryo 序列化类库,来优化序列化和反序列化的性能。Spark 默认使用的是 Java 的序列化机制,也就是 ObjectOutputStream/ObjectInputStream API 来进行序列化和反序列化。但是 Spark 同时支持使用 Kryo 序列化库,Kryo 序列类库的性能比 Java 序列化类库的性能要高很多。官方介绍,Kryo 序列化机制比 Java 序列化机制,性能高 10 倍左右。
Spark 之所以默认没有使用Kryo 作为序列化类库,是因为 Kryo 要求最好要注册所有需要进行序列化的自定义类型,因此对于开发者来说,这种方式比较麻烦。
Sparkconf.set("spark.serializer",
"org.apache.spark.serializer.KryoSerializer")
.registerKryoClasses(new Class[]{SpeedSortKey.class}) //SpeedSortkey 是自定义的一个类
3.9 优化数据结构
java中有三种类型比较消耗内存:
1.对象,每个Java对象都有对象头,引用等额外的信息,因此比较占用内存空间
2.字符串,每个字符串内部都有一个字符数组以及长度等额外信息
3.集合类型,比如HashMap,LinkedList等,因为集合类型内部通常会使用一些内部类来封装集合元素,比如Map.Entry
因此 Spark 官方建议,在 Spark 编码实现中,特别是对于算子函数中的代码,尽量不要使用上述三种数据结构,尽量使用字符串替代对象,使用原始类型(比如 Int、Long)替代字符串,使用数组替代集合类型,这样尽可能地减少内存占用,从而降低 GC 频率,提升性能。
3.10 使用高性能的库 fastutil
fasteutil 介绍:
fastutil 是扩展了 Java 标准集合框架(Map、List、Set;HashMap、ArrayList、HashSet)的类库,提供了特殊类型的 map、set、list 和 queue;
fastutil 能够提供更小的内存占用,更快的存取速度;我们使用 fastutil提供的集合类,来替代自己平时使用的 JDK 的原生的 Map、List、Set,好处在于,fastutil 集合类,可以减小内存的占用,并且在进行集合的遍历、根据索引(或者 key)获取元素的值和设置元素的值的时候,提供更快的存取速度。fastutil 的每一种集合类型,都实现了对应的 Java 中的标准接口(比如 fastutil 的 map,实现了 Java 的 Map 接口),因此可以直接放入已有系统的任何代码中。fastutil 最新版本要求 Java 7 以及以上版本。
四,数据本地化
4.1 数据本地化的级别
- Process_Local
task要计算的数据在本进程(Executor)的内存中。
- Node_Local
task要计算的数据在本节点磁盘上
task要计算的数据在本节点的其他Executor进程的内存中
- No_Pref
task计算的数据在关系型数据库中,如mysql
- Rack_Local
task所计算的数据在同机架的不同节点的磁盘或者Executor进程中的内存中。
- Any
跨机架。
4.2 Spark 数据本地化调优
Spark 中任务调度时,TaskScheduler 在分发之前需要依据数据的位置来分发,最好将 task 分发到数据所在的节点上,如果 TaskScheduler 分发的 task在默认 3s 依然无法执行的话,TaskScheduler 会重新发送这个 task 到相同的 Executor 中去执行,会重试 5 次,如果依然无法执行,那么 TaskScheduler会降低一级数据本地化的级别再次发送 task。如下图中,会先尝试 1,PROCESS_LOCAL 数据本地化级别,如果重试 5 次每次等待 3s,会默认这个 Executor 计算资源满了,那么会降低一级数据本地化级别到 2,NODE_LOCAL,如果还是重试 5 次每次等待 3s 还是失败,那么还是会降低一级数据本地化级别到 3,RACK_LOCAL。这样数据就会有网络传输,降低了执行效率。
- 如何提高数据本地化的级别?
可以增加每次发送task的等待时间(默认是3秒),将3s 倍数调大。
spark.locality.wait
spark.locality.wait.process
spark.locality.wait.node
spark.locality.wait.rack
注意:等待时间不能调的太大。调整数据本地化的级别不要本末倒置了,虽然每一个task的本地化级别是最高了,但反而整个Applcation的执行时间变长了。
五,内存调优
这里我们说的内存调优针对的是Executor端的内存调优,因为task运行在Executor端,可能出现计算内存不够用的情况。
Spark JVM 调优主要是降低 gc 时间,可以修改 Executor 内存的比例参数。RDD 缓存、task 定义运行的算子函数,可能会创建很多对象,这样会占用大量的堆内存。堆内存满了之后会频繁的 GC,如果 GC 还不能够满足内存的需要的话就会报 OOM。比如一个 task 在运行的时候会创建 N 个对象,这些对象首先要放入到 JVM 年轻代中。比如在存数据的时候我们使用foreach 来将数据写入到内存,每条数据都会封装到一个对象中存入数据库
中,那么有多少条数据就会在 JVM 中创建多少个对象。
Spark中如何内存调优?
SparkExecutor堆内存中存放( 以静态内存管理来说:) :RDD的缓存数据和广播变量(60%) , shuffle聚合内存(20%) ,task的运行 (20%)
-
提高Executor的内存(内存大了,自然20% 也跟着变大了)
-
降低存储内存比例或者降低聚合内存比例(降低其他占用内存比例)
六,Spark Shuffle调优
- Buffer大小(hash shuffle) – 32Kb
- Shuffle read(sort shuffle) 拉取数据量的大小 --48M
- Shuffle 聚合内存的比例 --20% (静态内存管理)
- 拉取数据量重试次数 --5次
- 重试的时间间隔
- Spark Shuffle的种类
- HashShuffle 合并机制
- SortShuffle byPass机制 200次
七,调节Executor的堆外内存
Spark底层 shuffle 的传输方式是 netty 传输,netty在进行网络传输的过程会申请堆外内存(netty是零拷贝),所以使用了堆外内存。默认情况下,这个堆外内存上线是默认Executor的内存大小的10%,真正处理大数据的时候,这里都会出现问题,导致Spark作业反复奔溃,无法运行;此时就会去调节这个参数,则至少1G,甚至2G,4G.
executor 在进行 shuffle write,优先从自己本地关联的 mapOutPutWorker中获取某份数据。如果本地 mapOutPutWorkers 没有的话,那么会通过TransferService,去远程连接其他节点上 executor 的 block manager 去获取,如果本地 block manager 没有的话,那么会通过 TransferService,去远程连接其他节点上 executor 的 block manager 去获取,尝试建立远程的网络连接,并且去拉取数据。频繁创建对象让 JVM 堆内存满溢,进行垃圾回收。正好碰到那个 exeuctor 的 JVM 在垃圾回收。处于垃圾回过程中,所有的工作线程全部停止;相当于只要一旦进行垃圾回收,spark / executor 停止工作,无法提供响应,spark 默认的网络连接的超时时长是 60s;如果卡住 60s 都无法建立连接的话,那么这个 task 就失败了。task 失败了就会出现 shuffle file cannot find 的错误。那么如何调节等待的时长呢?
在 ./spark-submit 提交任务的脚本里面添加:
--conf spark.core.connection.ack.wait.timeout=300 //默认是60s
Executor 由于内存不足或者堆外内存不足了,挂掉了,对应的 Executor 上面的 block manager 也挂掉了,找不到对应的 shuffle map output 文件,Reducer 端不能够拉取数据。我们可以调节堆外内存的大小,如何调节?
yarn下:
--conf spark.yarn.executor.memoryOverhead=2048 单位 M
standalone下:
--conf spark.executor.memoryOverhead=2048 单位 M
八,解决数据倾斜
8.1 使用Hive ETL 预处理数据
方案适用场景:
如果导致数据倾斜的是HIve表。如果Hive表中的数据本身很不均匀(比如说 某个key对应了100w数据,其他key才对应了10几条数据),而且业务场景需要频繁的使用Spark对Hive表执行某个操作,那么比较使用这种技术方案。
方案实现思路:
此时可以评估下,是否可以通过Hive 来进行数据预处理(即 通过Hive ETL预先对数据按照 key进行聚合,或者是预先和其他表进行 join ),然后在spark作业中针对的数据源就不是原来的Hive表,而是预处理后的Hive表。此时由于数据已经运行进行过聚合或join操作了,那么在Spark作业中也就不再需要使用原先的 shuffle类算子执行这类操作了。
方案实现原理:
这种方案从数据源的方向解决了数据倾斜的问题,因为彻底避免了Spark中执行shuffle类算子,那么肯定不会有数据倾斜的问题了。但是这种方式只是将数据倾斜的问题,放在了Hive层面,Hive在进行ETL操作,聚合或者join的时候,还是会出现数据倾斜,导致Hive ETL的速率很慢。
8.2 过滤少数导致数据倾斜的Key
方案使用场景:
如果发现导致数据倾斜的key就少数几个,而且对计算本身的影响并不大的话,那么很适合这种方案。比如99%的key就只对应 10多条数据,但是只有 个别的几个key对应了100w数据,从而导致了数据倾斜。
方案实现思路:
如果判断那少数几个数据量特别多的Key,对作业的执行和计算结果不是特别重要的话,那么干脆直接过滤调那少数的几个key。比如: 在SparkSql语句中可以直接使用 where 条件语句过滤调那些key,或者在spark core中对RDD执行 filter算子,过滤掉那些Key。如果需要每次作业执行时,动态判定哪些key的数据量特别多然后再进行过滤,那么可以使用 sample算子对RDD进行采样,然后计算出每个key的数量,取数据量最多的Key过滤掉即可。
方案实现原理:
将导致数据倾斜的key给过滤掉之后,这些key就不会参与计算了,自然不可能参数数据倾斜的情况了。
8.3 提高shuffle操作的并行度
方案实现思路:
在对RDD执行shuffle算子时,给shuffle算子传入一个参数,比如reduceByKey(1000),该参数就设置了这个shuffle算子执行时shuffle read task的数量。对于SparkSql中的 shuffle 类语句,比如 group by, join 等,需要设置一个参数, 即 spark.sql.shuffle.partitions,该参数代表了shuffle read task的并行度,该值默认是 200,对于很多场景来说都有点过小。
方案实现原理:
增加 shuffle read task的数量,可以让原本分配给一个task的多个key,分配给多个task,从而让每个task处理比原来更少的数据,那么自然而然每个task的执行时间都会变短了。
8.4 双重聚合
方案使用场景:
对RDD进行 reduceBykey等聚合类 shuffle算子或者在 sparkSql中使用 groupBy 语句进行分组聚合时,比较使用这种方案。
方案实现思路:
这个方案的核心实现思路就是进行两阶段聚合。第一次是局部聚合,先给每个 key 都打上一个随机数,比如 10 以内的随机数,此时原先一样的 key就变成不一样的了,比如(hello, 1) (hello, 1) (hello, 1) (hello, 1),就会变成(1_hello, 1) (1_hello, 1) (2_hello, 1) (2_hello, 1)。接着对打上随机数后的数据,执行 reduceByKey 等聚合操作,进行局部聚合,那么局部聚合结果,就会变成了(1_hello, 2) (2_hello, 2)。然后将各个 key 的前缀给去掉,就会变成(hello,2)(hello,2),再次进行全局聚合操作,就可以得到最终结果了,比如(hello, 4)。
方案实现原理:
将相同的key通过加前缀随机数的方式,变成多个不同的key,就可以让原本聚合到一个task中的数据,分配到多个task上去,然后进行一次聚合,再去掉前缀,再次聚合,就可以得到最终的结果。
如果一个RDD中有一个Key导致的数据倾斜,同时还有其他的key,那么一般先对数据集进行抽样,然后找出倾斜的key,再使用filter对原始的RDD进行分离为两个RDD,一个是由倾斜的Key组成的RDD1,一个是由其他key组成的RDD2,那么对于RDD1可以使用随机前缀进行多分区进行聚合,对于另一个RDD2可以直接聚合计算,最后将结果再合并起来。
8.5 将reduce join 转为 map join
BroadCast + filter (或者 map )
方案使用场景:
在对RDD使用Join类操作,或者是在SparkSql中使用join语句时,而且Join操作中的一个RDD或表的数据量比较小(比如几百M 或者 一两G ),比较适用此方案。
方案实现思路:
不使用Join算子进行连接操作,而是用Broadcast变量与map类算子实现Join操作,进而完成规避掉shuffle类的操作,彻底避免数据倾斜的发生和出现。将较小RDD中的数据直接通过collect算子拉取到Driver端的内存中来,然后对其创建一个Broadcast变量;接着对另外一个RDD执行map类算子,在算子函数内,从 Broadcast 变量中获取较小 RDD的全量数据,与当前 RDD 的每一条数据按照连接 key 进行比对,如果连接 key 相同的话,那么就将两个 RDD 的数据用你需要的方式连接起来。
方案实现原理:
普通的 join 是会走 shuffle 过程的,而一旦 shuffle,就相当于会将相同key的数据拉取到一个shuffle read task中再进行join,此时就是reduce join。但是如果一个 RDD 是比较小的,则可以采用广播小 RDD 全量数据+map 算子来实现与 join 同样的效果,也就是 map join,此时就不会发生 shuffle 操作,也就不会发生数据倾斜。
8.6 采样倾斜key并分析join操作
方案使用场景:
两个RDD/Hive表进行join的时候,如果数据量都比较大,无法采用解决方案五,那么此时可以看一下RDD/HIve中的key分布情况。如果出现数据倾斜,是因为其中某一个RDD/HIve表中的少数几个key的数据量过大,而另一个RDD/Hive表中的所有Key都分布比较均匀,那么采用这个解决方案是比较合适的。
方案实现思路:
对包含少数几个数据量过大的Key的RDD,通过sample算子采样出一份样本,然后统计一下每个key的数量,计算出来数据量最大的是哪几个key.然后将这几个key对应从原来的RDD中拆分出来,形成一个单独的RDD,并给每个key都打上n以内的随机数做为前缀,而不会导致倾斜的大部分key形成另外一个RDD.接着将需要join的另一个RDD,也过滤出那几个倾斜key对应的数据并形成一个单独的RDD,将每条数据膨胀成n条数据,这n条数据都按照顺序附加一个0-n的前缀,不会导致倾斜的大部分key也形成另外一个RDD.再将附加了随机前缀的独立RDD和另一个膨胀了的RDD进行Join,此时就可以将原先相同的key打散成多份,分散到多个task中去进行join了。二另外两个普通的RDD就照常join即可。最后将两次Join的结果使用union算子合并起来即可,就是最终的Join结果。
8.7 使用随机前缀和扩容RDD进行Join
方案使用场景:
如果在进行Join操作时,RDD中有大量的Key导致数据倾斜,那么进行分析Key也没什么意义,此时就只能使用最后一种解决方案来解决问题了。
方案实现思路:
该方案的实现思路基本和 方案六类似,首先查看RDD/Hive表中的数据分布情况,找到哪个造成数据倾斜的RDD/Hive表,比如有多个Key都对应超过1万条数据。然后将该RDD的每条数据都打上一个n以内的随机前缀,同时对另外一个RDD进行扩容,将每条数据都扩容成n条数据,扩容出来的每条数据都依次打上一个0-n的前缀。最后将两个处理后的RDD进行Join即可。
九,Spark故障解决
9.1 shuffle file cannot found
磁盘小文件找不到
- connection timeout
提高建立连接的超时时间,或者降低gc,降低了gc,那么spark不能堆外提供服务的时间就少了,那么超时的可能就会降低。
- fetch data fail
提高拉取数据的重试次数以及间隔时间。
- OOM/executor lost
提高堆外内存大小,提高堆内内存大小。
9.2 reduce OOM
BlockManager拉取的数据量大,reduce task处理的数据量小
- 降低每次拉取的数据量
- 提高shuffle 聚合的内存比例
- 提高Executor的内存
9.3 序列化问题
9.4 Null值问题
val rdd = rdd.map{x=>{
x+”~”;W
}}
rdd.foreach{x=>{
System.out.println(x.getName())
}}