转至:http://www.alidata.org/archives/2109/comment-page-1#comment-5402
1、数据倾斜原因
1.1、操作
关键词 |
情形 |
后果 |
Join |
其中一个表较小,但是key集中 |
分发到某一个或几个Reduce上的数据远远高于平均值 |
大表与大表,但是分桶的判断字段0值或者空值过多 |
这些空值都由一个reduce处理,灰常慢 |
|
Group by |
Group by 维度过小,某个值的数量过多 |
处理某值的reduce灰常耗时 |
Count distinct |
某特殊值过多 |
处理此特殊值的reduce耗时 |
1.2原因:
1)、key分布不均匀
2)、业务数据本身的特性
3)、建表时考虑不周
1.3、表现
任务进度长时间维持在99%(或100%),查看任务监控页面,发现只有少量(1个或几个)reduce子任务未完成。因为其处理的数据量和其他reduce差异过大(key分布不均匀)。
单一reduce的记录数与平均记录数差异过大,通常可能达到3倍甚至更多。最长时长远大于平均时长。
2、数据倾斜解决方案
2.1、参数调节
hive.map.aggr = true
Map端聚合,相当于Combiner
hive.groupby.skewindata=true
有数据倾斜的时候进行负载均衡,当选为true时候,生成的执行计划会有两个MR Job。第一个MR Job中,Map的输出结果集合会随机分布到Reduce中,每个Reduce做部分聚合操作,并输出结果,这样处理的结果集合会随机分布到Reduce中(这个过程可以保证相同的Group By Key被分布到同一个Reduce中),最后完成最终的聚合操作。
2.2、SQL语句调节
如何Join:
关于驱动表的选取,选用join key分布最均匀的表作为驱动表
做好列裁剪(就是选择哪些列)和filter操作,以达到两表做join的时候,数据量相对变小的效果(这里减少了shuffle的I/O)。
大小表Join:
使用map join让小的维度表(1000条以下的记录条数-----这里仅仅是一个估算值,应该按照分布式缓存的size来算)先进内存。在map端完成reduce。
大表Join大表:
把空值的key变成一个字符串加上随机数,把倾斜的数据分布到不同的reduce上,由于null值关联不上,处理后并不影响最终结果。
Count distinct大量相同特殊值:
count distinct时,将值为空的情况单独处理,如果计算count distinct,可以不用处理,直接过滤,在最后结果中加1。如果还有其他计算,需要进行group by ,可以先将值为空的记录单独处理,再和其他结果进行union。
group by 维度过小:
采用sum() group by 的方式来替换count(distinct)完成计算。
具体操作是:
http://superlxw1234.iteye.com/blog/1534779
原因:count(distinct)是最终在reduce端对distinct的字段进行排序去重的相对直接sum等map端的操作效率低。
特殊请特殊处理:
在业务逻辑优化效果不大情况下,有些时候是可以将倾斜的数据单独拿出来处理的。最后union回去。
3、典型的业务场景
3.1空值产生的数据倾斜
场景:
如日志中,常会有信息丢失的问题,比如日志中user_id,如果取其中的user_id和用户表中的user_id关联,会碰到数据倾斜的问题。
解决办法1:
user_id为空的值不参与关联
解决办法2:赋予空值新的key值
这样的好处就是可以将null均匀的分布到多个reduce去而会发送到一个reduce。
结论:
方法2比方法1效率更高,不但io减少,而且作业数也少了。解决办法1中log读取了两次,jobs数是2,解决办法2 jobs数是1。这个优化适合无效id(比如-99,'',null等)产生的倾斜问题,把空值的key变成一个字符串加上随机数,就能够把倾斜数据分布到不同的reduce了,解决数据倾斜问题。
3.2、不同数据类型关联产生的数据倾斜
场景:
用户表user_id字段为int,log表中字段既有string类型也有int类型。当按照user_id进行两个表的Join操作时,默认的Hash操作会按int型的id来进行分配,这样会导致的所有string类型的id的记录都分配到一个Reducer中。
解决办法:把数字类型转换成字符串类型
3.3、小表不小不大,怎么用map join解决倾斜问题
使用map join 解决小表(记录数少)关联大表的数据倾斜问题,这个方法使用频率非常高,但如果小表很大,大到map join会出现Bug或异常,这时候就需要特别处理。以下是例子:
users表有600W+的记录,把users分发到所有的map上也是个不小的开销,而且map join不支持这么大的小表。如果用普通的join,又会碰到数据倾斜的问题。
解决办法:
假如,log里的user_id有上百万个,这就又回到原来map join的问题上来了。所幸,每日的会员UV不会太多,有交易的会员不会太多,有点击的会员不会太多,有佣金的会员不会太多等等。所以这个方法能解决很多场景下的数据倾斜问题。
我这里再详述下:如果直接left outer join的话有时候会遇到bug,左边a表输出11条记录,右边1条记录,最后a表的方的数据11条正常,右表的记录只输出1行,其他都是null。
但是上面的SQL执行步骤会变的非常多,测算有原来的一步变成现在的7步。
4、总结
使用map的输出数据更均匀的分布到reduce中去,是我们的最终目标。由于Hash算法的局限性,按key hash会或多或少的造成数据倾斜。大量的经验表明数据倾斜的原因是人为的建表疏忽或业务逻辑可以规避的。在此给出较为通用的步骤:
如果确认业务需要这样的倾斜逻辑,考虑以下优化方案:
1、对于join,在判断小表不大于1G的情况下,使用map join
2、对于group by或distinct,设定 hive.groupby.skewindata=true
3、尽量使用上述的SQL语句调节进行优化