在优化任务里,最常碰到的一个问题,就是数据倾斜。简单来说,数据倾斜就是一张表的关联键或者聚合键中,某个或者某些特定数值出现的频率远大于其它数值,经过shuffle之后,某些节点的计算量远大于其他节点,使得hadoop无法发挥分布式计算的优势,最终导致计算时间过长。
一般来说,数据倾斜主要分为JOIN倾斜和Group By倾斜。
JOIN倾斜出现的场景一般有以下几种:
1)大表join小表
使用mapjoin的方式(在select语句中使用Hint提示/*+ mapjoin(<table_name>) */才会执行mapjoin),这个操作会将所有的小表全量复制到每个map任务节点,然后再将小表缓存在每个map节点的内存里与大表进行join工作。这样即节省了shuffle的时间,又避免了shuffle后的倾斜。mapjoin在Map阶段会将指定表的数据全部加载在内存中,因此指定的表仅能为小表,且表被加载到内存后占用的总内存不得超过一定的阈值,阈值的上限在各家公司基于hadoop研发的大数据引擎里都不太一样。
2)大表join大表或者中表
一般有两种常用的思路:
a. 看是否关联键有异常值(比如空值,null等),可采用coalesce(column, rand()*9999)来进行空值分发或者先进行where uid is not null 的条件过滤。在使用不太熟悉的表数据前,建议对数据探查,对数据质量有个基本认识。
b. 采用手动切分热值的方法。可通过对关联键进行groupby取count值再倒叙排列的方式探查出热值有哪些(比如取top20),然后两张表分别过滤出热值的数据进行join(一般过滤完后数据量较小,可用mapjoin),而非热点值用正常的mergejoin,最后再将结果union all起来。目前hive已有对应的skewjoin参数可以设置,方便简单。
c. 如果其中一张表z的数据量并非很大,但又不能使用mapjoin时,可以采用膨胀法。简单来说,在表z中新增一列number,表x的每一行数都分别对应number列中的数值1-5(即将表z的每一行数据膨胀5倍),然后表y在跟表x关联时,不仅要关联之前的关联键,还要关联CAST(rand() * 5 + 1 AS INT) = 表z的number,这样就能起到打散的作用了。
聚合倾斜的解决方案一般有以下几种:
1)设置 set hive.groupby.skewindata=true。具体的处理方式是通过将倾斜的键进行拆分,生成多个子键(比如在原始键值的后面加上随机数1-5),然后将这些子键映射到不同的Reducer上进行处理。这样可以将原本倾斜的数据分散到多个Reducer上,减轻了单个Reducer的负载,实现了负载均衡。在计算结果返回之前,还需要通过合并子键的结果来得到最终的计算结果。相当于是做了两次groupby,先是带上随机数的预聚合,再是去掉随机数的最终聚合。
2)尽量少用count(distinct),采用先groupby再count的方式。groupby的时候如果还倾斜,就采用1)的方法进行处理。