1、数据倾斜的表现
数据倾斜是由于数据分布不均匀,造成数据大量的集中到一点,造成数据热点的现象。
主要表现:任务进度长时间维持在 99%或者 100%的附近,查看任务监控页面Yarn(8088),发现只有少量 reduce 子任务未完成,因为其处理的数据量和其他的 reduce 差异过大。 单一 reduce 处理的记录数和平均记录数相差太大,通常达到好几倍之多,最长时间远大于平均时长。
大数据领域:不患多而患不均
2、出现的原因
其实数据倾斜这个问题,在MapReduce编程模型中十分常见,根本原因就是大量相同的key被分配到一个reduce里,造成一个reduce任务处理不过来,但是其他的reduce任务没有数据可以处理。下面罗列一下常见的数据倾斜有哪些原因 :
1)数据类型不一致造成数据倾斜
情形: 比如用户表users中user_id字段为string,log表中user_id字段int类型。当按照user_id进行两个表的Join操作时。
解决方式:把数字类型转换成字符串类型
2)数据中出现大量的null值
分为两种情况,第一种情况,null值是异常值,就不应该出现,比如 userId 出现 null
对于异常值如果不需要的话,最好是提前在where条件里过滤掉,这样可以使计算量大大减少。
第二种情况,出现null的数据不是异常数据,需要保留。
虽然某个 key 为空对应的数据很多,但是相应的数据不是异常数据,必须要包含在join 的结果中,此时我们可以表 a 中 key 为空的字段赋一个随机的值,使得数据随机均匀地分不到不同的 reducer 上。
3)单表group by 出现数据倾斜
导致数据倾斜的主要原因在于按照 Key 分组以后,少量的计算资源负责绝大部分数据的计算,也就是说产生数据倾斜的 HQL 中一定存在分组操作,那么从 HQL 的角度,我们可以将数据倾斜分为单表携带了 GroupBy 字段的查询和两表(或者多表)Join 的查询。
解决方案:
第一种方案:使用参数优化
当任务中存在group by操作同时聚合函数为count或者sum,可以设置参数来处理数据倾斜的问题。
并不是所有的聚合操作都需要在Reduce端完成,很多聚合操作都可以先在Map端进行部分聚合,最后在Reduce端得出最终结果。 突然让我们想起了Combine操作,就是运行在map端的reduce.
1、是否在Map端进行聚合,默认为True
hive(default)> set hive.map.aggr = true
2、在Map端进行聚合操作的条目数目
hive(default)> set hive.groupby.mapaggr.checkinterval = 100000
3、有数据倾斜的时候进行负载均衡(默认是false)
hive(default)> set hive.groupby.skewindata = true
4、当开启数据负载均衡时,生成的查询计划会有两个MRJob。
第一个MRJob中,Map的输出结果会随机分布到Reduce中,每个Reduce做部分聚合操作,并输出结果,这样处理的结果是相同的Group By Key有可能被分发到不同的Reduce中,从而达到负载均衡的目的;
第二个MRJob再根据预处理的数据结果按照Group By Key分布到Reduce中(这个过程可以保证相同的Group By Key被分布到同一个Reduce中),最后完成最终的聚合操作。
第二种方案:增加Reduce数量
当数据中的多个key同时导致数据倾斜,可以通过增加reduce的数量解决数据倾斜问题
1)调整Reduce个数方法1:
① 每个Reduce处理的数据量默认是256MB hive(default)> set hive.exec.reducers.bytes.per.reducer=256000000
② 每个任务最大的reduce数,默认为1009
hive(default)> set hive.exec.reducers.max=1009
③ 计算reducer数的公式
N=min(参数2,总输入数据量/参数1)
2)调整Reduce个数方法2:
通过参数配置的方式(三种)直接指定reduce的个数,参数mapreduce.job.reduces。 hive(default)> set mapreduce.job.reduces = 15;
3、多表join出现数据倾斜
解决方案一:使用参数解决
在编写 Join 查询语句时,如果确定是由于 join 出现的数据倾斜,那么请做如下设置:
#join的键对应的记录条数超过这个值则会进行分拆,值根据具体数据量设置
set hive.skewjoin.key=100000;
如果是join过程出现倾斜应该设置为true
set hive.optimize.skewjoin=false;
如果开启了,在Join过程中Hive会将计数超过阈值hive.skewjoin.key(默认100000)的倾斜key对应的行临时写进文件中,然后再启动另一个mr做map join生成结果。
通过 hive.skewjoin.mapjoin.map.tasks参数还可以控制第二个mr job的mapper数量,默认10000。
set hive.skewjoin.mapjoin.map.tasks=10000;
第二种解决方案:大小表join
MapJoin原理: 小表缓存并发送到各个节点,没有Shuffle的过程
如果不指定MapJoin或者不符合MapJoin的条件,那么Hive解析器会将Join操作转换成Common Join(Reduce join),即:在Reduce阶段完成Join。
总结:
1) mapjoin --只有map没有reduce,当然也不会有shuffle
2) common join : 就是普通的join,走MR程序
实战:
实战设置:
(1)设置自动选择Mapjoin
set hive.auto.convert.join = true; 默认为true
(2)大表小表的阈值设置(默认25M以下认为是小表):
set hive.mapjoin.smalltable.filesize = 25000000;1) 内连接,小表放到左边或者右边都可以,hive底层会自动优化
2)左连接,小表放到后边,也就是 from 大表 left join 小表 on 大表.字段名 = 小表.字段名,底层会将小表数据加载到内存,
走mapjoin,效率更高
3) 右连接,小表放到左边,也就是 from 小表 right join 大表 on 大表.字段名 = 小表.字段名,底层会将小表数据加载到内存,
走mapjoin,效率更高
4)全连接, 无法触发mapjoin,无法小表放到前边或者后边MapJoin不触发的可能得原因:内存不足
修改Hadoop中的配置文件: /opt/installs/hadoop3.1.4/etc/hadoop/mapred-site.xml
一个 Map Task 可使用的内存上限(单位:MB),默认为 1024。
<property>
<name>mapreduce.map.memory.mb</name>
<value>4096</value>
</property>
还可以使用大表打散、小表扩容的手段解决。
具体如下:首先看如下数据:
从上面的图可以得知,数据出现了倾斜。
使用大表打散小表扩容:
方案:
第三种解决方案:大表大表join
大表关联大表,使用分而治之思想+map join,对关联两表做成以join条件为分桶字段的表,并且按照同样的排序方式组织分桶数据,两表的分桶个数必须成整数倍数关系。在表数据关联的时候,将小表的数据加载到内存中,开启大表桶个数的map任务,并且将小表桶数据加载到与大表对应桶位置对应(相同或者成倍数关系)的map任务的内存中去,然后以map join的方式执行,这种方式与map join的区别一是数据进行过滤,并不是全数加载到内存中,二是数据是有序的,降低扫描次数,提升效率。由此看来SMB join 使用分而治之思想转换为多个小的map join 操作,规避shuffle 操作。