1、执行流程
1.1、MapReduce执行流程
核心思想:大问题拆分成多个小问题,然后分布式的并行执行
两个阶段:
1、mapper阶段: 提取数据,赋予特征 映射 value ====> key, value
mapreducce框架是怎么把相同特征的数据组合到一起来,然后交给reduceTask执行一次聚合操作的呢
2、reducer阶段: 把相同特征的数据进行聚合操作 key, (value, value, ...)
1.2、Spark应用程序执行流程
sparkContext.rextFile().flatMap().map().reduceByKey()
首先让你确认,导致分布式计算应用改程序出现数据倾斜的原因就是 Shuffle 数据倾斜的调优,都是围绕着:
- 要么就不要使用shuffle
- 要么就让shuffle在执行过程中均匀分发数据
最终的目的:Spark 中的同一个 stage 中的多个 Task 处理的数据量大小几乎是一致的。
2、数据倾斜解决方案
2.1、使用Hive ETL预处理数据
- 优点:实现起来简单便捷,效果还非常好,完全规避掉了数据倾斜,Spark作业的性能会大幅度提升。
- 缺点:治标不治本,Hive ETL中还是会发生数据倾斜。
2.2、调整shuffle操作的并行度
- 实用场景:大量不同的Key被分配到了相同的Task造成该Task数据量过大。
- 实现思路:在对RDD执行Shuffle算子时,给Shuffle算子传入一个参数,比如reduceByKey(1000),该参数就设置了这个shuffle算子执行时shuffle read task的数量。对于Spark SQL中的Shuffle类语句,比如group by、join等,需要设置一个参数,即spark.sql.shuffle.partitions,该参数代表了shufflereadTask的并行度,该值默认是200,对于很多场景来说都有点过小。
- 优点:实现起来比较简单,可以有效缓解和减轻数据倾斜的影响。实现简单,可在需要Shuffle的操作算子上直接设置并行度或者使用spark.default.parallelism设置。如果是Spark SQL,还可通过SET spark.sql.shuffle.partitions=[num_tasks]设置并行度。可用最小的代价解决问题。一般如果出现数据倾斜,都可以通过这种方法先试验几次,如果问题未解决,再尝试其它方法。
- 缺点:只是缓解了数据倾斜而已,没有彻底根除问题,根据实践经验来看,其效果有限。适用场景少,只能将分配到同一Task的不同Key分散开,但对于同一Key倾斜严重的情况该方法并不适用。并且该方法一般只能缓解数据倾斜,没有彻底消除问题。从实践经验来看,其效果一般。
2.3、过滤少数导致倾斜的key
- 优点:实现简单,而且效果也很好,可以完全规避掉数据倾斜。
- 缺点:适用场景不多,大多数情况下,导致倾斜的key还是很多的,并不是只有少数几个。
2.4、将reduce join转为map join
普通的join是会走shuffle过程的,而一旦shuffle,就相当于会将相同key的数据拉取到一个shuffle read task中再进行join,此时就是reduce join。但是如果一个RDD是比较小的,则可以采用广播小RDD全量数据+map算子来实现与join同样的效果,也就是map join,此时就不会发生shuffle操作,也就不会发生数据倾斜。
- 优点:对join操作导致的数据倾斜,效果非常好,因为根本就不会发生shuffle,也就根本不会发生数据倾斜。
- 缺点:适用场景较少,因为这个方案只适用于一个大表和一个小表的情况。毕竟我们需要将小表进行广播,此时会比较消耗内存资源,driver和每个Executor内存中都会驻留一份小RDD的全量数据。如果我们广播出去的RDD数据比较大,比如10G以上,那么就可能发生内存溢出了。因此并不适合两个都是大表的情况。
2.5、采样倾斜key并分拆join操作
- 优点:对于join导致的数据倾斜,如果只是某几个key导致了倾斜,采用该方式可以用最有效的方式打散key进行join。而且只需要针对少数倾斜key对应的数据进行扩容n倍,不需要对全量数据进行扩容。避免了占用过多内存。
- 缺点:如果导致倾斜的key特别多的话,比如成千上万个key都导致数据倾斜,那么这种方式也不适合。
2.6、两阶段聚合(局部聚合+全局聚合)
- 适用场景: 对RDD执行reduceByKey等聚合类shuffle算子或者在Spark SQL中使用group by语句进行分组聚合时,比较适用这种方案。
- 实现原理:将原本相同的key通过附加随机前缀的方式,变成多个不同的key,就可以让原本被一个task处理的数据分散到多个task上去做局部聚合,进而解决单个task处理数据量过多的问题。接着去除掉随机前缀,再次进行全局聚合,就可以得到最终的结果。
- 优点:对于聚合类的shuffle操作导致的数据倾斜,效果是非常不错的。通常都可以解决掉数据倾斜,或者至少是大幅度缓解数据倾斜,将Spark作业的性能提升数倍以上。
- 缺点:仅仅适用于聚合类的shuffle操作,适用范围相对较窄。如果是join类的shuffle操作,还得用其他的解决方案。
2.7、使用随机前缀和扩容RDD进行join
- 适用场景:如果在进行 join 操作时,RDD 中有大量的 key 导致数据倾斜,那么进行分拆 key 也没什么意义。
- 实现原理:将原先一样的 key 通过附加随机前缀变成不一样的key,然后就可以将这些处理后的“不同key”分散到多个task中去处理,而不是让一个task处理大量的相同key。该方案与“解决方案六”的不同之处就在于,上一种方案是尽量只对少数倾斜key对应的数据进行特殊处理,由于处理过程需要扩容RDD,因此上一种方案扩容RDD后对内存的占用并不大;而这一种方案是针对有大量倾斜key的情况,没法将部分key拆分出来进行单独处理,因此只能对整个RDD进行数据扩容,对内存资源要求很高。
- 优点:对join类型的数据倾斜基本都可以处理,而且效果也相对比较显著,性能提升效果非常不错。
- 缺点:该方案更多的是缓解数据倾斜,而不是彻底避免数据倾斜。而且需要对整个RDD进行扩容,对内存资源要求很高。
2.8、任务横切,一分为二,单独处理
- 适用场景:导致数据倾斜的因素比较多,比较复杂的场景中。
- 实现思路:在了解清楚数据的分布规律,以及确定了数据倾斜是由何种原因导致的,那么按照这些原因,进行数据的拆分,然后单独处理每个部分的数据,最后把结果合起来。
- 优点:将多种简单的方案综合起来,解决一个复杂的问题。可以算上一种万能的方案。
- 缺点:确定数据倾斜的因素比较复杂,导致解决该数据倾斜的方案比较难实现落地。代码复杂度也较高。
2.9、多种方案组合使用
即使用上述方案的组合。
2.10、自定义Partitioner
使用自定义的 Partitioner 实现类代替默认的 HashPartitioner,尽量将所有不同的 Key 均匀分配到不同的 Task中。
- 适用场景:大量不同的Key被分配到了相同的Task造成该Task数据量过大。
- 实现思路:先通过抽样,了解数据的key的分布规律,然后根据规律,去定制自己的数据分区规则,尽量保证所有的Task的数据量相差无几。
- 实现原理:使用自定义的Partitioner(默认为HashPartitioner),将原本被分配到同一个Task的不同Key分配到不同Task。
- 实现方案选择:
- 随机分区
- 优点:数据分布均匀
- 缺点:具有相同特点的数据不会保证被分配到相同的分区
- 轮询分区
- 优点:确保一定不会出现数据倾斜
- 缺点:无法根据存储/计算能力分配存储/计算压力
- Hash散列
- 优点:具有相同特点的数据保证被分配到相同的分区
- 缺点:极容易产生数据倾斜
- 范围分区
- 优点:相邻的数据都在相同的分区
- 缺点:部分分区的数据量会超出其他的分区,需要进行裂变以保持所有分区的数据量是均匀的,如果每个分区不排序,那么裂变就会非常困难
- 随机分区
- 优点:灵活,通用。
- 缺点:必须根据对应的场景设计合理的分区方案。没有现成的方案可用,需临时实现。
2.11、Spark整合BitMap求Join
- 优点:占用内存少,处理速度高,
- 缺点:维护BitMap要求高。