Hive实战系列调优之数据倾斜

什么是数据倾斜?

数据倾斜,即单个节点任务所处理的数据量远大于同类型任务所处理的数据量,导致该作业成为整个作业的瓶颈,这是分布式不可避免的问题,从本质来说导致数据倾斜有两种原因,一是任务读取大文件(文件不可切分,如对文件使用GZIP压缩),二是任务需要处理大量相同键的数据,任务需要处理大量相同的数据,这种情况有一下4种类=表现形式:

  1. 数据含有大量无意义的数据,例如空值NULL、空字符串等;
  2. 含有数据倾斜在进行聚合时无法聚合中间结果,大量数据都需要经过Shuffle阶段的处理,引起数据倾斜。
  3. 数据在计算式做多维数据集合,导致维度膨胀引起的数据倾斜。
  4. 两表进行join,都含有大量相同的倾斜数据键。

案例1 无法切分的大文件

当集群的数据量增长到一定规模,有些数据需要归档或者转储,这时候往往会对数据进行压缩;当对文件使用GZIP压缩等不支持文件分割操作的压缩方式,在日后有作业涉及读取压缩后的文件时,该压缩文件只会被一个任务所读取。如果该压缩文件很大,则处理该文件的Map需要花费的时间会远多于读取普通文件的Map时间,该Map任务会成为作业运行的瓶颈。这种情况也就是Map读取文件的数据倾斜。

解决思路:这种数据倾斜问题没有什么好的解决方案,只能将使用GZIP压缩等不支持文件分割的文件转为bzip和zip等支持文件分割的压缩方式。

结论:我们在对文件进行压缩时,为避免因不可拆分大文件而引发数据读取的倾斜,在数据压缩的时候可以采用bzip2和Zip等支持文件分割的压缩算法

案例2 空值引发的数据倾斜

实际业务中有些大量的null值或者一些无意义的数据参与到计算作业中,表中有大量的null值,如果表之间进行join操作,就会有shuffle产生,这样所有的null值都会被分配到一个reduce中,必然产生数据倾斜。具体案例又可以分为一下两个Case:

Case1: 过滤NULL值,不让NULL值有shuffle阶段。

SELECT * FROM log a JOIN users b ON a.user_id IS NOT NULL 
AND a.user_id = b.user_id
UNION ALL
SELECT * FROM log a WHERE a.user_id IS NULL;

Case2: 因为NULL值参与shuffle时的hash结果是一样的,那么我们可以给null值随机赋值,这样它们的hash结果就不一样,就会进到不同的reduce中。

SELECT * FROM log a LEFT JOIN users b 
ON CASE 
WHEN a.user_id IS NULL 
THEN concat('hive_', rand()) 
ELSE a.user_id END = b.user_id;

案例3  不同数据类型引发的数据倾斜

对于两个表join,表a中需要join的字段key为int,表b中key字段既有string类型也有int类型。当按照key进行两个表的join操作时,默认的Hash操作会按int型的id来进行分配,这样所有的string类型都被分配成同一个id,结果就是所有的string类型的字段进入到一个reduce中,引发数据倾斜。

解决思路:如果key字段既有string类型也有int类型,默认的hash就都会按int类型来分配,那我们直接把int类型都转为string就好了,这样key字段都为string,hash时就按照string类型分配了。

案例4  数据膨胀引发的数据倾斜

在多维聚合计算时,如果进行分组聚合的字段过多,如下:

select a,b,c,count(*) from log group by a,b,c with rollup;

如果上面的log表的数据量很大,并且Map端的聚合不能很好地起到数据压缩的情况下,会导致Map端产出的数据急速膨胀,这种情况容易导致作业内存溢出的异常。如果log表含有数据倾斜key,会加剧Shuffle过程的数据倾斜。

解决思路:SQL拆分上面语句等价拆分为:

SELECT a, b, c, COUNT(1)    FROM log GROUP BY a, b, c
UNION ALL
SELECT a, b, NULL, COUNT(1) FROM log GROUP BY a, b
UNION ALL
SELECT a, NULL, NULL, COUNT(1) FROM log GROUP BY a
UNION ALL
SELECT NULL, NULL, NULL, COUNT(1) FROM log;

结论:上面这种方式不太好,因为现在是对3个字段进行分组聚合,那如果是5个或者10个字段呢,那么需要拆解的SQL语句会更多。在Hive中可以通过参数 hive.new.job.grouping.set.cardinality 配置的方式自动控制作业的拆解,该参数默认值是30。表示针对grouping sets/rollups/cubes这类多维聚合的操作,如果最后拆解的键组合大于该值,会启用新的任务去处理大于该值之外的组合。如果在处理数据时,某个分组聚合的列有较大的倾斜,可以适当调小该值。

案例5  表连接发生数据倾斜

通常做法是将倾斜的数据存到分布式缓存中,分发到各个 Map任务所在节点。在Map阶段完成join操作,即MapJoin,这避免了 Shuffle,从而避免了数据倾斜。MapJoin是Hive的一种优化操作,其适用于小表JOIN大表的场景,由于表的JOIN操作是在Map端且在内存进行的,所以其并不需要启动Reduce任务也就不需要经过shuffle阶段,从而能在一定程度上节省资源提高JOIN效率。

可以通过以下两个属性来设置该优化的触发时机:
hive.auto.convert.join=true 默认值为true,自动开启MAPJOIN优化。
hive.mapjoin.smalltable.filesize=2500000 默认值为2500000(25M),通过配置该属性来确定使用该优化的表的大小,如果表的大小小于此值就会被加载。
注意:使用默认启动该优化的方式如果出现莫名其妙的BUG(比如MAPJOIN并不起作用),就将以下两个属性置为fase手动使用MAPJOIN标记来启动该优化:
hive.auto.convert.join=false (关闭自动MAPJOIN转换操作)
hive.ignore.mapjoin.hint=false (不忽略MAPJOIN标记)
将表放到Map端内存时,如果节点的内存很大,但还是出现内存溢出的情况,我们可以通过这个参数mapreduce.map.memory.mb调节Map端内存的大小。

案例6  确实无法减少数据量引发的数据倾斜

在一些操作中,我们没有办法减少数据量,如在使用 collect_list 函数时:

select s_age,collect_list(s_score) list_score from student group by s_age;

在上述sql中,s_age有数据倾斜,但如果数据量大到一定的数量,会导致处理倾斜的Reduce任务产生内存溢出的异常。collect_list输出一个数组,中间结果会放到内存中,所以如果collect_list聚合太多数据,会导致内存溢出。

有小伙伴说这是group by分组引起的数据倾斜,可以开启hive.groupby.skewindata参数来优化。我们接下来分析下:
开启该配置会将作业拆解成两个作业,第一个作业会尽可能将Map的数据平均分配到Reduce阶段,并在这个阶段实现数据的预聚合,以减少第二个作业处理的数据量;第二个作业在第一个作业处理的数据基础上进行结果的聚合。
hive.groupby.skewindata的核心作用在于生成的第一个作业能够有效减少数量。但是对于collect_list这类要求全量操作所有数据的中间结果的函数来说,明显起不到作用,反而因为引入新的作业增加了磁盘和网络I/O的负担,而导致性能变得更为低下。

解决方案:
这类问题最直接的方式就是调整reduce所执行的内存大小。调整reduce的内存大小使用mapreduce.reduce.memory.mb这个配置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值