在大数据开发领域中,spark也成功受欢迎平台之一,我也基于spark开发过一些大数据计算作业,其中的调优过程也记录一下。
1、对重复的RDD作缓存处理
比如一个RDD多次使用那么应该对这个RDD作缓存处理,避免重复计算。
JavaRDD txtRdd = sc.textFile("spark-biz/src/main/resources/people.txt");
JavaRDD<Person> personJavaRdd = txtRdd.map(parFun);
JavaRDD<String> fileLine = txtRdd.filter(filterFun);
//对txtRdd进行了多次操作,需要进行缓存优化
//cache()方法缓存:缓存到内存,如果内存不够用则不缓存
JavaRDD txtRdd = sc.textFile("spark-biz/src/main/resources/people.txt").cache();
//或persist()方法缓存:手动选择持久化级别,并使用指定的方式进行持久化。
//StorageLevel.MEMORY_AND_DISK():表式内存不足再缓存到磁盘
//具体持久化方式可根据不同业务场景选择
JavaRDD txtRdd = sc.textFile("spark-biz/src/main/resources/people.txt").persist(StorageLevel.MEMORY_AND_DISK());
Spark的持久化级别
- MEMORY_ONLY:使用未序列化的Java对象格式,将数据保存在内存中。如果内存不够存放所有的数据,则数据可能就不会进行持久化。那么下次对这个RDD执行算子操作时,那些没有被持久化的数据,需要重新计算一遍。这是默认的持久化策略,使用cache()方法时,用的就是这种持久化策略。
- MEMORY_AND_DISK:使用未序列化的Java对象格式,优先尝试将数据保存在内存中。如果内存不够存放所有的数据,会将数据写入磁盘文件中,下次对这个RDD执行算子时,持久化在磁盘文件中的数据会被读取出来使用。
- MEMORY_ONLY_SER:基本含义同MEMORY_ONLY。唯一的区别是,会将RDD中的数据进行序列化,RDD的每个partition会被序列化成一个字节数组。这种方式更加节省内存,从而可以避免持久化的数据占用过多内存导致频繁GC。
- MEMORY_AND_DISK_SER:基本含义同MEMORY_AND_DISK。唯一的区别是,会将RDD中的数据进行序列化,RDD的每个partition会被序列化成一个字节数组。这种方式更加节省内存,从而可以避免持久化的数据占用过多内存导致频繁GC。
- DISK_ONLY:使用未序列化的Java对象格式,将数据全部写入磁盘文件中
- MEMORY_ONLY_2, MEMORY_AND_DISK_2:对于上述任意一种持久化策略,如果加上后缀_2,代表的是将每个持久化的数据,都复制一份副本,并将副本保存到其他节点上。这种基于副本的持久化机制主要用于进行容错。假如某个节点挂掉,节点的内存或磁盘中的持久化数据丢失了,那么后续对RDD计算时还可以使用该数据在其他节点上的副本。如果没有副本的话,就只能将这些数据从源头处重新计算一遍了。
2、使用高性能的算子
-
使用reduceByKey/aggregateByKey替代groupByKey
reduceByKey/aggregateByKey 会在shuffle write 之前会进行分区内同键记录聚合,而groupByKey不会。
不会对分区内同键记录聚合
分区内同键先进行了一次聚合 -
使用mapPartitions替代普通map
mapPartitions类的算子,一次函数调用会处理一个partition所有的数据,而不是一次函数调用处理一条,性能相对来说会高一些。但是有的时候,使用mapPartitions会出现OOM(内存溢出)的问题。因为单次函数调用就要处理掉一个partition所有的数据,如果内存不够,垃圾回收时是无法回收掉太多对象的,很可能出现OOM异常,可以适当的调整partition分区的大小。 -
使用foreachPartitions替代foreach
同 “使用mapPartitions替代map”操作类似,普通的foreach会一条一条处理,而使用foreachPartitions算子一次性处理一个partition的数据。特别是有数据库操作的情况下,如果是foreach 每条数据会建立一个数据库连接,而foreachpartitions一个分区只会建立一个数据库连接,同时还需考虑分区数大小。 -
有数据库操作时指定partitions大小
spark默认partitions大小是200,通常一般的操作我们不需指定,但如果用到数据库相关操作那么我们需要根据数据库性能来具体设置partition大小,如果分区太大 频繁的创建销毁数据库连接会消耗性能,如果太小那么spark集群得不到充会利用。 -
使用filter之后进行coalesce操作
通常对一个RDD执行filter算子过滤掉RDD中较多数据后(比如30%以上的数据),可以使用coalesce算子,手动减少RDD的partition数量,将RDD中的数据压缩到更少的partition中去,减少task数量。 -
避免使用collect – 所有executor的数据拉取到driver上
一,为了避免collect数据量过大而导致内存溢出;二,频繁收集分发数据也会浪费资源。