一、资源优化1.在部署spark集群中指定资源分配的默认参数
在spark安装包的conf下spark-env.sh:
SPARK_WORKER_CORES :worker分配的核数
SPARK_WORKER_MEMORY:worker分配的内存
SPARK_WORKER_INSTANCES:每台机器启动worker数2.提交命令
--executor-cores :每个进程的核数(没有配置,默认把所有核数都分配给它)
--executor-memory:每个进程的内存(默认1G)
--total-executor-cores:所有进程分配的核数
配置信息:(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最小分配的executor数
spark.dynamicAllocation.maxExecutors 30 //每个Application最大并发分配的executor数
spark.dynamicAllocation.schedulerBacklogTimeout 1s
spark.dynamicAllocation.sustainedSchedulerBacklogTimeout 5s二、并行度调优
1) 如果读取的数据在HDFS中,降低block大小,相当于提高了RDD中partition个数
sc.textFile(xx,numPartitions)
2) sc.parallelize(xxx, numPartitions)
3) sc.makeRDD(xxx, numPartitions)
4) sc.parallelizePairs(xxx, numPartitions)
5) repartions/coalesce
6) redecByKey/groupByKey/join ---(xxx, numPartitions)
7) spark.default.parallelism net set
8) spark.sql.shuffle.partitions---200
9) 自定义分区器
10) 如果读取数据是在SparkStreaming中Receiver:
spark.streaming.blockInterval—200ms
Direct:读取的topic的分区数
自定义分区代码:
public class ParTionerTest {
public static void main(String[] args) {
SparkConf sparkConf = new SparkConf().setMaster("local").setAppName("partioner");
JavaSparkContext sc = new JavaSparkContext(sparkConf);
JavaRDD parallelize = sc.parallelize(Arrays.asList(
"a", "a", "b", "c",
"a", "a", "b", "c"),2);
parallelize.mapToPair(new PairFunction() {
@Override
public Tuple2 call(String s) throws Exception {
return new Tuple2<>(s,1);
}
}).reduceByKey(new Partitioner() {
@Override
public int getPartition(Object key) {
if("a".equals(key)){
return 0;
}else if("b".equals(key)){
return 1;
}else{
return 2;
}
}
@Override
public int numPartitions() {
return 3;
}
}, new Function2() {
@Override
public Integer call(Integer v1, Integer v2) throws Exception {
return v1 + v2;
}
}).foreach(new VoidFunction>() {
@Override
public void call(Tuple2 tuple2) throws Exception {
System.out.println(tuple2);
}
});
}
}
结果演示:【由数据读取的2分区更改为3分区】
(a,4)
(b,2)
(c,2)三、代码调优1.避免创建重复的rdd
对于同一份文件,一次读取创建rdd,多次使用。2.对多次使用的rdd进行持久化:
1.memory_only:
默认情况下,性能最高memory_only。前提是内存必须足够大,可以存放下整个rdd所有数据。
因为不进行序列化与反序列化操作,就避免了这部分的性能开销;对这个RDD的后续算子操作,都
是基于纯内存中的数据的操作,不需要从磁盘文件中读取数据,性能也很高;而且不需要复制一份数据
副本,并远程传送到其他节点上。
如果RDD中数据比较多时(比如几十亿),直接用这种持久化级别,会导致JVM的OOM内存溢出异常,
如果使用MEMORY_ONLY级别时发生了内存溢出,那么建议尝试使用MEMORY_ONLY_SER级别。
2.MEMORY_ONLY_SER:
该级别会将RDD数据序列化后再保存在内存中,此时每个partition仅仅是一个字节数组而已,大大
减少了对象数量,并降低了内存占用。
这种级别比MEMORY_ONLY多出来的性能开销,主要就是序列化与反序列化的开销。但是后续算子可以
基于纯内存进行操作,因此性能总体还是比较高的。
如果RDD中的数据量过多的话,还是可能会导致OOM内存溢出的异常。
如果纯内存的级别都无法使用,那么建议使用MEMORY_AND_DISK_SER策略,而不是MEMORY_AND_DISK策略
3.MEMORY_AND_DISK_SER:
RDD的数据量很大,内存无法完全放下。序列化后的数据比较少,可以节省内存和磁盘的空间开销。
同时该策略会优先尽量尝试将数据缓存在内存中,内存缓存不下才会写入磁盘。
4.DISK_ONLY:
因为完全基于磁盘文件进行数据的读写,会导致性能急剧降低,有时还不如重新计算一次所有RDD。