Blink解决Agg数据倾斜问题

聚合

聚合操作是SQL中比较常用的语法,形如Group By + Agg,Flink中最常用的Agg操作有COUNT/SUM/AVG等

Group By + Agg

1.从0点开始,每个类目的成交额
2.从0点开始,每个店铺的uv/pv
3.从0点开始,每个用户点击了多少商品,多少店铺

SELECT 
    date_format(ctime, '%Y%m%d') as cdate, -- 将数据从时间戳格式(2018-12-04 15:44:54),转换为date格式(20181204)
       category_id,
    sum(price) as category_gmv
FROM src
GROUP BY date_format(ctime, '%Y%m%d'), category_id; --按照天做聚合

Group by+Agg模式在底层的有一些特点:
1.Group by分组操作,会产生数据shuffle
2.按Key的agg操作,最终都需要落到同一个物理进程上才能保证计算的正确性
以这个最简单SQL为例,其数据流程图如下,不同颜色代表不同的category_id:
在这里插入图片描述
数据源进来的数据先经过group by进行分组,同一个key的数据被分到同一个worker上之后再进行聚合操作。特点2就决定了,Group By + Agg 模式中,SQL作业性能与数据分布非常相关,如果数据中存在数据倾斜,也就是某个key的数据异常的多,那么某个聚合节点就会成为瓶颈,作业就会有明显的反压及延时现象。
为了解决这个问题,就需要将堵住的聚合节点进行拆分,优化后的SQL如下:

SELECT cdate,category_id,sum(category_gmv_p) as category_gmv
FROM(
    SELECT 
        date_format(ctime, '%Y%m%d') as cdate, -- 将数据从时间戳格式(2018-12-04 15:44:54),转换为date格式(20181204)
           category_id,
        sum(price) as category_gmv_p
    FROM src
    GROUP BY category_id, mod(hash_code(FLOOR(RAND(1)*1000), 256),date_format(ctime, '%Y%m%d'); --按照天做聚合
)
GROUP BY cdate,category_id

SQL中做了将一个Group By+Agg拆称了两个,子查询里按照category_id和mod(hash_code(FLOOR(RAND(1)*1000), 256)分组,将同一个category_id上的数据打散成了256份,先做一层聚合。外层Group By+Agg,将子查询聚合后的结果再次做聚合。这样通过两层聚合的方式,即可大大缓解某聚合节点拥堵的现象。其数据流程图如下:
在这里插入图片描述

Group By+单Distinct Agg

计算从0点开始,每个店铺的uv/pv

SELECT 
    date_format(ctime, '%Y%m%d') as cdate, -- 将数据从时间戳格式(2018-12-04 15:44:54),转换为date格式(20181204)
       shop_id,
    count(distinct uid) as shop_uv, -- shop uv
    count(uid) as shop_pv -- show pv
FROM src
GROUP BY date_format(ctime, '%Y%m%d'), shop_id; --按照天做聚合

同样,按照上节所述,如果这个作业出现了数据倾斜的现象,就需要将SQL优化为:

select 
    cdate,
    shop_id, 
    sum(shop_uv_partial) as shop_uv,
    sum(shop_pv_partial) as shop_pv
from (
    select 
        date_format(ctime, '%Y%m%d') as cdate, -- 将数据从时间戳格式(2018-12-04 15:44:54),转换为date格式(20181204)
        shop_id, 
        count(distinct uid) as shop_uv_partial,
        count(uid) as shop_pv_partial
    from src
    group by shop_id, mod(hash_code(uid), 256),date_format(ctime, '%Y%m%d')
)
group by cdate,shop_id

本例子中,将原始SQL中的一层查询,拆成了两层查询。内层子查询,按照shop_id和mod(hash_code(uid),256)做聚合,将同一个shop_id的数据打散到多个节点中。外层查询,将子查询聚合后的结果,再按shop_id聚合。通过两层聚合即可大大缓解数据倾斜情况下聚合节点的压力。

Group By+Agg场景与Group By+Distinct Agg场景的主要区别,在于state中存储的数据。上一章中提到过,Flink是增量计算,state中会保存增量数据,比如上次SUM的值等等,但是在DISTINCT计算过程中,就需要保留所有的distinct的key,在本例子中,就是uid。且在每一次计算过程中,都要查询当前state中是否有同一个uid,并计数。因此在大数据量情况下distinct节点往往成为Flink作业的瓶颈。需要通过扩并发等方式解决。

Group By+多Distinct Agg

从0点开始,每个用户点击了多少商品,多少店铺,以及该用户总点击item次数。原始数据如下:

SELECT UDTF
    date_format(ctime, '%Y%m%d') as cdate, -- 将数据从时间戳格式(2018-12-04 15:44:54),转换为date格式(20181204)
       uid,
    count(distinct shop_id) as shop_cnt,
    count(distinct item_id) as item_cnt,
    count(item_id) as click_cnt

FROM src
GROUP BY date_format(ctime, '%Y%m%d'), uid;

具体解决办法如下:
1.先使用UDTF,将原始数据一行拆成多行,每行添加n+1列,n为distinct的个数。n列分别对distinct的值做hash
2.在SQL中,先在子查询中分别计算各指标的count值,在外层再做一层sum即可,SQL示例如下:

select 
    cdate,
    uid, 
    sum(shop_cnt_p) as shop_cnt, 
    sum(item_id_p) as item_id_cnt, 
    sum(item_cnt_p) as item_cnt
from (
    select
        date_format(ctime, '%Y%m%d') as cdate,
        uid,
        count(distinct shop_id) filter (where flag = flag0) as shop_cnt_p,
        count(distinct item_id) filter (where flag = flag1) as item_id_p ,
        sum(item_id) filter (where flag = flag2) as item_cnt_p
    from Expand_T
    group by uid, hash_user, hash_shop, date_format(ctime, '%Y%m%d')
    )
group by uid

这种问题可以解决多个distinct中的数据倾斜问题,但是会增加sql复杂度,并且计算过程中数量会膨胀,并且占用更多资源。

本文转载自Flink入坑指南 第四章:SQL中的经典操作Group By+Agg

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值