pyspark 解决数据倾斜demo(两端聚合)(全网之最)

什么是数据倾斜

数据倾斜是一种很常见的问题(依据二八定律),简单来说,比方WordCount中某个Key对应的数据量非常大的话,就会产生数据倾斜,导致两个后果:

  • OOM(单或少数的节点);
  • 拖慢整个Job执行时间(其他已经完成的节点都在等这个还在做的节点)。

Shuffle时,需将各节点的相同key的数据拉取到某节点上的一个task来处理,若某个key对应的数据量很大就会发生数据倾斜。比方说大部分key对应10条数据,某key对应10万条,大部分task只会被分配10条数据,很快做完,个别task分配10万条数据,不仅运行时间长,且整个stage的作业时间由最慢的task决定。

什么是两端聚合

  • 两阶段聚合(局部聚合+全局聚合)。
  • 场景:对RDD进行reduceByKey等聚合类shuffle算子,SparkSQL的groupBy做分组聚合这两种情况。
  • 思路:首先通过map给每个key打上n以内的随机数的前缀并进行局部聚合,即(hello, 1) (hello, 1) (hello, 1) (hello, 1)变为(1_hello, 1) (1_hello, 1) (2_hello, 1),并进行reduceByKey的局部聚合,然后再次map将key的前缀随机数去掉再次进行全局聚合。本文demo案例给出df下的倾斜处理方式。
  • 原理:对原本相同的key进行随机数附加,变成不同key,让原本一个task处理的数据分摊到多个task做局部聚合,规避单task数据过量。之后再去随机前缀进行全局聚合。
  • 优点:效果非常好(对聚合类Shuffle操作的倾斜问题)。
  • 缺点:范围窄(仅适用于聚合类的Shuffle操作,join类的Shuffle还需其它方案)。

案例图说明数据倾斜

本文demo案例是根据df的,不是rdd的,特此说明

在这里插入图片描述

如图所示,大量相同的key(上图的A)被分到同一分区,导致数据倾斜。

两端聚合解决数据倾斜案例图

在这里插入图片描述

将key加盐,实际上就是对key加随机整数。避免大量相同的key出现在同一分区。

案例code

假数据生成

from pyspark import SparkContext, SQLContext, SparkConf
from pyspark.sql import SparkSession
from pyspark.sql import functions as fn
from pyspark.sql.functions import udf

tmpdict = [
    {'Col1': 'A', 'Col2': 1},
    {'Col1': 'A', 'Col2': 1},
    {'Col1': 'A', 'Col2': 1},
    {'Col1': 'A', 'Col2': 1},
    {'Col1': 'A', 'Col2': 1},
    {'Col1': 'A', 'Col2': 1},
    {'Col1': 'A', 'Col2': 1},
    {'Col1': 'A', 'Col2': 1},
    {'Col1': 'B', 'Col2': 1},
    {'Col1': 'B', 'Col2': 1},
    {'Col1': 'B', 'Col2': 1},
    {'Col1': 'A', 'Col2': 1},
    {'Col1': 'A', 'Col2': 1},
    {'Col1': 'A', 'Col2': 1}
]
df = ss.createDataFrame(tmpdict)
df.show()
+----+----+
|Col1|Col2|
+----+----+
|   A|   1|
|   A|   1|
|   A|   1|
|   A|   1|
|   A|   1|
|   A|   1|
|   A|   1|
|   A|   1|
|   B|   1|
|   B|   1|
|   B|   1|
|   A|   1|
|   A|   1|
|   A|   1|
+----+----+

加盐

@udf
def adSalt(adid):
    salt = random.sample(range(0, 4), 1)[0]
    salt = str(salt) + '_'
    return salt + adid


df = df.withColumn('Col1', adSalt(fn.col('Col1')))
df.show()
+----+----+
|Col1|Col2|
+----+----+
| 1_A|   1|
| 0_A|   1|
| 1_A|   1|
| 1_A|   1|
| 1_A|   1|
| 1_A|   1|
| 2_A|   1|
| 1_A|   1|
| 3_B|   1|
| 0_B|   1|
| 0_B|   1|
| 3_A|   1|
| 1_A|   1|
| 2_A|   1|
+----+----+

局部聚合

def row_dealWith(data):
    Col1, Col2 = data[0], data[1]
    
    tups = (
        str(Col1),
        int(sum(Col2))
    )
    return tups
    
    
df = df.groupBy('Col1').agg(fn.collect_list('Col2').alias('Col2')).rdd.map(row_dealWith).toDF(schema=['Col1', 'Col2'])
df.show()
+----+----+
|Col1|Col2|
+----+----+
| 3_A|   4|
| 1_A|   1|
| 0_B|   3|
| 0_A|   3|
| 2_A|   3|
+----+----+

去掉随机串

getidUDF = fn.udf(lambda x: x.split('_')[1])
df = df.withColumn('Col1', getidUDF(fn.col('Col1')))
df.show()
+----+----+
|Col1|Col2|
+----+----+
|   A|   4|
|   A|   1|
|   B|   3|
|   A|   3|
|   A|   3|
+----+----+

全局聚合

df = df.groupBy('Col1').agg(fn.collect_list('Col2').alias('Col2')).rdd.map(row_dealWith).toDF(schema=['Col1', 'Col2'])
df.show()
+----+----+
|Col1|Col2|
+----+----+
|   B|   3|
|   A|  11|
+----+----+

汇总案例code

from pyspark import SparkContext, SQLContext, SparkConf
from pyspark.sql import SparkSession
from pyspark.sql import functions as fn
from pyspark.sql.functions import udf


tmpdict = [
    {'Col1': 'A', 'Col2': 1},
    {'Col1': 'A', 'Col2': 1},
    {'Col1': 'A', 'Col2': 1},
    {'Col1': 'A', 'Col2': 1},
    {'Col1': 'A', 'Col2': 1},
    {'Col1': 'A', 'Col2': 1},
    {'Col1': 'A', 'Col2': 1},
    {'Col1': 'A', 'Col2': 1},
    {'Col1': 'B', 'Col2': 1},
    {'Col1': 'B', 'Col2': 1},
    {'Col1': 'B', 'Col2': 1},
    {'Col1': 'A', 'Col2': 1},
    {'Col1': 'A', 'Col2': 1},
    {'Col1': 'A', 'Col2': 1}
]
df = ss.createDataFrame(tmpdict)
@udf
def adSalt(adid):
    salt = random.sample(range(0, 4), 1)[0]
    salt = str(salt) + '_'
    return salt + adid

def row_dealWith(data):
    Col1, Col2 = data[0], data[1]
    
    tups = (
        str(Col1),
        int(sum(Col2))
    )
    return tups    
df = df.withColumn('Col1', adSalt(fn.col('Col1')))

df = df.groupBy('Col1').agg(fn.collect_list('Col2').alias('Col2')).rdd.map(row_dealWith).toDF(schema=['Col1', 'Col2'])

getidUDF = fn.udf(lambda x: x.split('_')[1])
df = df.withColumn('Col1', getidUDF(fn.col('Col1')))

df = df.groupBy('Col1').agg(fn.collect_list('Col2').alias('Col2')).rdd.map(row_dealWith).toDF(schema=['Col1', 'Col2'])

两端聚合解决数据倾斜模板思路已给出,具体结合自己需求修改即可

原创不易,转载请注明出处

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WGS.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值