学习美团的spark性能优化指南笔记。
优化主要从4个方面进行:
1. 开发调优
- 避免创建重复的RDD
当多个算子都用到一个RDD的时候,尽量只创建一个RDD,算子只使用一个RDD
- 尽可能复用同一个RDD
不要因为要用一些数据,创建过多的RDD,比如:
// 错误的做法。
// 有一个<Long, String>格式的RDD,即rdd1。
// 接着由于业务需要,对rdd1执行了一个map操作,创建了一个rdd2,而rdd2中的数据仅仅是rdd1中的value值而已,也就是说,rdd2是rdd1的子集。
JavaPairRDD<Long, String> rdd1 = ...
JavaRDD<String> rdd2 = rdd1.map(...)
// 分别对rdd1和rdd2执行了不同的算子操作。
rdd1.reduceByKey(...)
rdd2.map(...)
// 正确的做法。
// 上面这个case中,其实rdd1和rdd2的区别无非就是数据格式不同而已,rdd2的数据完全就是rdd1的子集而已,却创建了两个rdd,并对两个rdd都执行了一次算子操作。
// 此时会因为对rdd1执行map算子来创建rdd2,而多执行一次算子操作,进而增加性能开销。
// 其实在这种情况下完全可以复用同一个RDD。
// 我们可以使用rdd1,既做reduceByKey操作,也做map操作。
// 在进行第二个map操作时,只使用每个数据的tuple._2,也就是rdd1中的value值,即可。
JavaPairRDD<Long, String> rdd1 = ...
rdd1.reduceByKey(...)
rdd1.map(tuple._2...)
// 第二种方式相较于第一种方式而言,很明显减少了一次rdd2的计算开销。
// 但是到这里为止,优化还没有结束,对rdd1我们还是执行了两次算子操作,rdd1实际上还是会被计算两次。
// 因此还需要配合“原则三:对多次使用的RDD进行持久化”进行使用,才能保证一个RDD被多次使用时只被计算一次。
- 对多次使用的RDD进行持久化
spark对于一个RDD执行多次算子的原理是每次对一个RDD执行一个算子操作时,都会重新从源头计算一遍,算出RDD,然后再对RDD执行你的算子,这样的话,RDD就重复计算了。所以需要对使用的RDD进行持久化。两个方法persist(参数), chache(), persist(MEMORY_ONLY) = cache(),数据太大时,MEMORY_ONLY会发生内存溢出,可使用MEMORY_ONLY_SER(会将RDD数据序列化后再保存到内存中,可以大大减少数据的大小),参数中后缀是2的,表示要备份一份,慎用。
- 尽量避免使用shuffle类算子
spark作业中最消耗性能的地方就是shuffle过程,当一个大文件和一个小文件进行的join的时候,可以将小文件进行广播(broadcast),这样就避免了把数据从其他节点拉取到一个节点的情况
- 使用map-side预聚合的shuffle操作
预聚合就是在进行shuffle算子的时候,先在每个节点进行一次聚合计算,之后再进行聚合(拉取到统一节点)这样数据量会少很多。尽量使用reduceByKey,aggregateByKey代替groupBykey
- 使用高性能的算子
a. 使用reduceByKey,aggregateByKey代替groupBykey
b. 使用mapPartitions替代普通的map, mapPartitions一次处理一个partition所有的数据,而不是一次函数处理一条,但是可能发生内存不够
c. 使用foreachPartition替换foreach.类似上述方法,很明显的案例,往mysql写数据的时候,如果使用foreach一条一条往mysql中写数据,每次写入创建一个数据库连接,性能非常低下,但是使用foreachPartition,性能可以提升很多。
d.filter之后进行coalesce操作
e. repartitionAndSortWithinPartitions替代repartition与sort类操作。进行分区的同时进行排序操作。
2. 资源调优
3. 数据倾斜调优
现象:个别task执行极慢
原因:在进行shuffle的时候,必须将各个节点上相同的key拉取到某个节点上的一个task来进行处理,此时如果某个key对应的数据量特别大,就会发生数据倾斜。
方案一:通过ETL预处理
方案二:过滤掉不需要的倾斜的key
方案三:提高shuffle的并发度
方案四:两阶段聚合,先进行一次预聚合,再进行一次全局聚合
方案五:大表和小表进行join的时候,将小表广播出去,将reduce join 转化为map join
方案六:倾斜key分拆join,加随机数,扩容
适用场景:两个量很大的数据进行join A join B,且倾斜的key不多
实现思路:
1. 通过sample算子进行采样,计算出量很大的那几个key;2. 将造成倾斜的key的数据和其他正常的数据形成a1,a2两份数据;3. 将a1的key上随机打上n以内的随机数,B的key上也打上随机数,每条数据膨胀成n条数据Bn(扩容), a1 join Bn; 4. a2正常进行join即可;5. 最后将两份数据union 到一起。(Bn 扩容成n倍, 这样a1中的key不管打上的随机数是多少,都可以在Bn中找到)
方案七:随机数,扩容RDD进行join
适用场景:两个量很大的数据进行join A join B,且倾斜的key很多
实现思路:
类似方案六,只是A不进行数据拆分,而是将A的所有key上都打上n以内的随机数,将B扩容n倍,然后进行join即可。
4. shuffle调优
set spark.sql.caseSensitive=FALSE; 设置不区分大小写