Spark和Hive中数据倾斜的情况分析及解决方案

1. 数据倾斜是什么

数据倾斜就是我们在计算数据的时候,数据的分散度不够,导致大量的数据集中到了集群中的一台或者几台机器上计算,而集群中的其他节点空闲。这些倾斜了的数据的计算速度远远低于平均计算速度,导致整个计算过程过慢。

2. 数据倾斜的表现

2.1 Hive中的数据倾斜

1)虽说Hive最后也是用MR来执行,但是毕竟写的内容逻辑区别很大,一个是程序,一个是 sql,因此这里稍作区分。
2)表现Hadoop中的数据倾斜主要表现在、Reduce 阶段卡在 99.99%,一直不能结束。这里如果详细的看日志或者和监控界面的话会发现:

  1. 有一个多几个Reduce卡住
  2. 各种container报错
  3. OOM
  4. 异常的Reducer读写的数据量极大,至少远远超过其它正常的Reducer伴随着数据倾斜,最长时长远大于平均时长。甚至会出现任务被kill等各种诡异的表现。

3)Hive的数据倾斜,一般都发生在Sql中group by、join和count distinct 上,而且和数据逻辑绑定比较深。

函数 情形 后果
Join 其中一个表较小,但是key集中 分发到某一个或几个Reduce上的数据远高于平均值
Join 大表与大表,但是分桶的判断字段0值或空值过多 这些空值都由一个reduce处理,灰常慢
group by group by 维度过小,某值的数量过多 处理某值的reduce灰常耗时
Count Distinct 某特殊值过多 处理此特殊值的reduce耗时

2.2 spark中的数据倾斜

Spark 中的数据倾斜也很常见,这里包括Spark Streaming 和Spark Sql,表现主要有下面几种:

  1. Executor lost,OOM,Shuffle 过程出错;
  2. Driver OOM;
  3. 单个Executor执行时间特别久,整体任务卡在某个阶段不能结束;
  4. 正常运行的任务突然失败;

需要注意的是,在Spark streaming 程序中,数据倾斜更容易出现,特别是在程序中包含一些类似sql的join、group这种操作的时候。 因为Spark Streaming程序在运行的时候,一般不会分配特别多的内存,因此一旦在这个过程中出现一些数据倾斜,就十分容易造成OOM。

3. 数据倾斜的原因

一般来说造成数据倾斜的主要原因可以总结为以下几点:

  1. key分布不均匀
  2. 业务数据本身的特性
  3. 建表时考虑不周
  4. 某些SQL语句本身就有数据倾斜

3.1 Shuffle

Hadoop 和 Spark 在 Shuffle 过程中产生数据倾斜的原理基本类似即数据不均匀。因为数据分布不均匀,导致大量数据分配到一个节点。

3.2 数据本身

举一个栗子,假设我们有两张表:

users(用户信息表):userid,register_ip
ip(IP 表):ip,register_user_cnt

这可能是两个不同的人开发的数据表。如果我们的数据规范不太完善的话,会出现一种情况:user表中的register_ip 字段,如果获取不到这个信息,我们默认为null;但是在ip表中,我们在统计这个值的时候,为了方便,我们把获取不到 ip 的用户,统一认为他们的 ip 为 0。 两边其实都没有错的,但是一旦我们做关联了,这个任务会在做关联的阶段,也就是 sql 的 join on 的阶段卡死。

3.3 业务逻辑

数据往往和业务是强相关的,业务的场景直接影响到了数据的分布。 再举一个栗子,比如就说订单场景吧,在某一天,北京和上海两个城市做了强力推广,结果这两个城市的订单量增长了10000倍,其余城市的数据量不变。 然后我们要统计不同城市的订单情况,这样,一做 group by 操作,可能直接就数据倾斜了。

4. 数据倾斜的解决方案

很多数据倾斜的问题,基本上都是由于数据设计与业务的理解不足而造成的。通常情况下,通过更适合的数据预处理,异常值的过滤等,就可以解决数据倾斜的大部分问题。

解决数据倾斜的基本思路大致分为为三种:

  1. 业务逻辑
    从业务逻辑的层面上来优化数据倾斜,比如上面的两个城市做推广活动导致那两个城市数据量激增的栗子,我们可以单独对这两个城市来做count,单独做时可用两次MR,第一次打散计算,第二次再最终聚合计算。完成后和其它城市做整合。

  2. 代码层面

    1. join
      关于驱动表的选取,选用join key分布最均匀的表作为驱动表。
      做好列裁剪和filter操作,以达到两表做join的时候,数据量相对变小的效果。
    2. 大小表Join
      使用map join让小的维度表(1000条以下的记录条数) 先进内存。在map端完成reduce.
    3. 大表Join大表
      把空值的key变成一个字符串加上随机数,把倾斜的数据分到不同的reduce上,由于null值关联不上,处理后并不影响最终结果。
    4. count distinct大量相同特殊值
      count distinct时,将值为空的情况单独处理,如果是计算count distinct,可以不用处理,直接过滤,在最后结果中加1。如果还有其他计算,需要进行group by,可以先将值为空的记录单独处理,再和其他计算结果进行union。
    5. group by维度过小
      采用sum() group by的方式来替换count(distinct)完成计算。
    6. 特殊情况特殊处理
      在业务逻辑优化效果的不大情况下,有些时候是可以将倾斜的数据单独拿出来处理。最后union回去。
    7. 自定义分区器
      根据数据分布情况,自定义散列函数,将 key 均匀分配到不同 Reducer
  3. 配置调优
    Hadoop 和 Spark都自带了很多的参数和机制来调节数据倾斜,合理利用它们就能解决大部分问题。
    举个栗子:

    • 设置该任务的每个 job 的 reducer 个数为 3 个。Hive 默认-1,自动推断。 set mapred.reduce.tasks=3
    • hive.map.aggr=true
      Map 端部分聚合,相当于Combiner
    • hive.groupby.skewindata=true
      有数据倾斜的时候进行负载均衡,当选项设定为 true,生成的查询计划会有两个 MR Job。第一个 MR Job 中,Map 的输出结果集合会随机分布到 Reduce 中,每个 Reduce 做部分聚合操作,并输出结果,这样处理的结果是相同的 Group By Key 有可能被分发到不同的 Reduce 中,从而达到负载均衡的目的;第二个 MR Job 再根据预处理的数据结果按照 Group By Key 分布到 Reduce 中(这个过程可以保证相同的 Group By Key 被分布到同一个 Reduce 中),最后完成最终的聚合操作。

5.举个栗子

5.1 由空值造成的数据倾斜

拿之前ip为null的栗子说明,并通过设计的角度尝试解决它。
1. 有损的方法:找到异常数据,比如 ip 为 0 的数据,过滤掉。
2. 无损的方法:对分布不均匀的数据,单独计算,先对 key 做一层hash、加盐等打散操作,先将数据随机打散让它的并行度变大,再汇集。

解决方法:

  1. 对空值行筛选出来,不进行操作
select * from ip a
  join users b
  on a.ip is not null
  and a.ip = b.register_ip
union all
select * from ip a
  where a.ip is null;
  1. 将空值插入随机值后操作
select *
  from ip a
  left outer join users b
  on case when a.ip is null then concat(‘hive’,rand() ) else a.ip end = b.register_ip;

方法2比方法1效率更好,不但io少了,而且作业数也少了。解决方法1中 log读取两次,jobs是2。解决方法2 job数是1 。这个优化适合无效 id (比如 -99 , ’’, null 等) 产生的倾斜问题。把空值的 key 变成一个字符串加上随机数,就能把倾斜的数据分到不同的reduce上 ,解决数据倾斜问题。

5.2 count(distinct)的倾斜问题

在 Hive 中,经常遇到 count(distinct)操作,这样会导致最终只有一个 Reduce 任务。 我们可以先 group by,再在外面包一层 count,就可以了。举个栗子,计算按用户名去重后的总用户量:

// 优化前只有一个reduce,先去重再count负担比较大
select name,count(distinct name)from user; 
// 优化后 
// 启动两个job,一个负责子查询(可以有多个reduce),另一个负责count(1)
select count(1) from (select name from user group by name) tmp;

5.3 不同数据类型关联产生数据倾斜

场景:用户表中user_id字段为int,log表中user_id字段既有string类型也有int类型。当按照user_id进行两个表的Join操作时,默认的Hash操作会按int型的id来进行分配,这样会导致所有string类型id的记录都分配到一个Reducer中。

解决方法:把数字类型转换成字符串类型

select * from users a
  left outer join logs b
  on a.usr_id = cast(b.user_id as string)

5.4 小表不小不大,怎么用 map join 解决倾斜问题

使用 map join 解决小表(记录数少)关联大表的数据倾斜问题,这个方法使用的频率非常高,但如果小表很大,大到map join会出现bug或异常,这时就需要特别的处理。 以下例子:

select * from log a
  left outer join users b
  on a.user_id = b.user_id;

users 表有 600w+ 的记录,把 users 分发到所有的 map 上也是个不小的开销,而且 map join 不支持这么大的小表。如果用普通的 join,又会碰到数据倾斜的问题。
在Hive0.11后,Hive默认启动该优化。只需调整以下参数即可自动判断并使用。

  1. hive.auto.convert.join
    默认值为true,自动开户MAPJOIN优化。
  2. hive.mapjoin.smalltable.filesize
    默认值为2500000(25M),通过配置该属性来确定使用该优化的表的大小,如果表的大小小于此值就会被加载进内存中。

6.总结

  1. 对于join,在判断小表不大于1G的情况下,使用map join。

  2. 对于group by或distinct,设定 hive.groupby.skewindata=true。决定 group by 操作是否支持倾斜数据。

    注意:只能对单个字段聚合。

  3. 尽量使用上述的SQL语句调节进行优化。

猜你喜欢

转载自blog.csdn.net/weixin_42526352/article/details/106307867
今日推荐