Spark如何处理数据倾斜

本文详细探讨了Spark中的数据倾斜问题,包括其定义、危害和产生原因。介绍了如何通过Spark UI快速定位数据倾斜,并提出了数据源不均匀、shuffle过程中数据分布不均等常见场景的解决方案,如调整并行度、自定义分区、使用广播join和采样处理倾斜键等方法,以优化计算性能。

什么是数据倾斜

数据倾斜是指我们在并行进行数据处理的时候,由于数据Spark的单个Partition)的分布不均,导致大量的数据集中分不到一台或者某几台计算节点上,导致处理速度远低于平均计算速度,从而拖延导致整个计算过程过慢,影响整个计算性能

数据倾斜的危害

  1. 单个或者某几个task拖延整个任务运行时间,导致整体耗时过大
  2. 单个task处理数据过多,很容易导致oom
  3. Executor Kill lost,Shuffle error 

数据倾斜的产生

 数据倾斜容易产生在两个过程,本身数据源读的倾斜,这个主要由于本身文件的分布不均,主要是不能切分的文件isSplitable=false 例如gz 另外的在shuffle阶段,key的分布不均,导致大量的数据集中到单个或者某几个task上导致数据整个stage,执行慢,影响整个job作业,总结主要有以下两个过程

  1. 数据源数据文件不均匀
  2. 计算过程中key的分布不均
    1. 单个rdd中进行groupby 的时候key分布不均
    2. 多个rdd进行join过程中key的不均匀

数据倾斜快速定位

1.我们可以根据Spark UI查看metrics,input 以及shuffle read 两个metrics判断task的min,跟max是否差异较大,如果差异非常大,并且影响运行,则需要优化task input 数据源倾斜,input size统计是从外部数据源读入的大小

2.task shuffle 数据倾斜,一般主要是shuffle read拉取数据的时候,数据partition分布不均,导致fetch拉取过程中数据倾斜,可以通过Shuffle Read Size查看min,和max 值,如果差异非常大,并且影响运行,则需要优化

3.另外就是我们在运行中个别task执行特别慢的时候,我们可以看一下该task的input或者shuffle reader的Summary Metrics里面min和max值,一般情况下处理的数据越多,task的运行时间越长,理想情况下所有的task数据均匀分布,运行时长均等,可以定位到task所属的stage,通过stage 描述,可以定位到所属的代码行,进而优化代码

数据倾斜的常见解决方法

  1. 数据源数据文件不均匀

    • 原理:

      对于spark读取文件主要通过sparkContext.textFile调用hadoop的TextInputFormat读取文件,然后实现两个方法isSplitable和getSplits,isSplitable判断文件是否切分,getSplits是切分文件生成partition,每个partition对应一个rdd task,blocksize 的计算如下,切分的partition数量=goalSize/splitSize,运行任务的task的数量等于依赖的切分的partition数量

      //默认blocksize为256M, minSize 默认1, Math.min(goalSize, blockSize) 计算文件的goalSize,如果文件goalSize小于blocksize则取goalSize,否则取blocksize

      protected long computeSplitSize(long goalSize, long minSize,

                                           long blockSize) {

        return Math.max(minSize, Math.min(goalSize, blockSize));

      }

      //根据总的goalSize/splitSize 如果小于1.1倍,则停止split

      while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {

        String[][] splitHosts = getSplitHostsAndCachedHosts(blkLocations,

            length-bytesRemaining, splitSize, clusterMap);

        splits.add(makeSplit(path, length-bytesRemaining, splitSize,

            splitHosts[0], splitHosts[1]));

        bytesRemaining -= splitSize;

      }

    • 案例:
            分别对于不能split的gz文件和可以split的文本文件进行计数统计,对于不能split的gz文件,spark只能启动一个task进行计数统计,对于可以split的文本文件,spark按照goalSize/splitSize切分文本生成多个task进行并行读取
      1. 对于不能split的gz文件进行读取,只能按照 文件数量生成task进行计算
        • 使用spark 简单的对gz文件进行读取统计行数

          val spark = SparkSession.builder()

            .appName("spark_read")

            .getOrCreate();

          spark.sparkContext.textFile("/user/xxx/example/gzip/lineitem.tbl.gz").count()

          spark.close()

        • 提交spark app 运行情况,按照文件数量,只有一个文件生成一个task进行计算

      2. 对于可以使用split的文件进行读取,任务可以被按照blocksize进行切分,进行并行计算
        • 使用spark 简单的对gz文件进行读取统计行数
           
        • 文件信息统计信息如下, task数量 =  (total size:11811160064)/(block size:268435456) 为44个task,进行并行计算

           

        • 提交spark app 运行情况,按照block 数量并行生成44个task进行计算

    • 总结:

       适用场景:对于数据源单个spark input read数据量过大,或者单个task 相对于其他task spark input read较大的情况,读取数据源明显不均匀
       解决方式:尽量使用可切割的文本存储,生成尽量多的task进行并行计算

       优点:从数据源避免倾斜,并且从源头增大并行度,避免倾斜
       缺点:需要改造数据源,支持可切割

       

  2. Shuffle过程中数据分布不均

    • 原理:
             Shuffle阶段在分布式并行计算引擎中是常见一个过程,在spark中当一个RDD的数据需要被多个子RDD所使用的时候,我们需要进行shuffle将数据打散,把数据均匀的分配给子RDD进行并行计算,Shuffle过程中spark默认使用HashPartitioner对数据进行分区,在这个过程中可能由于我们的数据分布不均,我们在进行hash取摸的时候,并行度设置不足,导致多数据分配到一个task上,导致倾斜,或者就是相同key的数据hash取摸之后就是比较大,分配同一个task导致数据倾斜等,对于这行情况我们分以下场景进行解决
    • 案例1:shuffle中部分数据分布不均
      spark shuffle默认使用HashPartitioner对数据进行分片,可能造成不同的key分配到一个task上,导致数据倾斜
      • spark 生成倾斜数据并提交任务,生成100w的数据,然后设置默认spark.default.parallelism并行的task为100,倾斜的分区为7,对大于100的数据,随按照new Random()).nextInt(defPar) * (skewPart)生成key,使key hash取摸的时候,都分配分区为7的task上,导致数据倾斜

        val numbers = 1000

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值