Fork me on GitHub

Hive Day07 hive的数据倾斜处理


hive的数据倾斜

hadoop(广义,包括hadoop以及所有依赖hadoop的组件(hive,hbase))不怕数据量大,怕数据倾斜;

数据倾斜:在进行数据计算的时候,由于数据分布不均匀,造成某一个节点上分配的数据量很多,造成这个节点的计算任务很大;也就是说压力放到了一个节点上;

负载均衡:存储层面上

表现:hive的数据倾斜,说到底就是mapreduce的数据倾斜;
本质上,reduce端的数据倾斜;
reduce端的数据分配取决于分区算法(默认 hash 自定义)

数据经常维持在90%到100%之间的时候;
产生数据倾斜了

不会产生数据倾斜的场景

  1. hive执行过程中,所有的fetch的过程;在fetch的过程中,所有的操作不需要转换为MR任务的;
  2. group by 和聚合函数一起使用的时候,会在默认在map端执行一次聚合函数,就可以大大减少reduce端的数据量;

会产生数据倾斜的场景

  1. 聚合函数不和group by一起使用的时候,容易产生数据倾斜;求的聚合函数就是全局的聚合,只能一个reducetask任务完成;
  2. count(disctinct );去重,多个reducetask,但是count(),全局的
  3. join;mapjoin,不会产生数据倾斜的,reducejoin很大程度上会产生数据倾斜;

产生数据倾斜的原因

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

分析场景

join的时候null值过多;

log --- 电商日志 10T, userid=null 4T
userid order money product ....
user---用户注册的时候生成的
userid name address num
selet from log a user b on a.user=b.userid;
所有的userid=null的数据全部分配到一个reducetask中,
此时就会产生数据倾斜;

解决方案:

  1. null值不参与连接

  2. null值分散开;给null值加随机数;

    使用case when语句给字段增加随机值;

    select 
    * 
    from log a join user b 
    on case when a.userid is null then concat(a.userid,rand())
    then a.userid=b.userid end;
    

方案二优于方案一;
方案一:如果需要null值的话,需要对表扫描两次

join的关联键的数据类型不统一

hive中默认关联的时候,会将string类型的转换为int类型,为了提高效率;
string类型 === “123” === int 123
string类型 === “123 ” === int null

解决方案:将其中一个表的数据类型转换;将两个表的类型进行统一处理;

大表和大表关联的时候

大表关联小表,小表关联小表

hive中是mapjoin的
hive.auto.convert.join 决定hive是否启动mapjoin的
true,默认启动mapjoin的,不是所有的join都执行mapjoin,有文件大小限制的;

hive.smalltable.filesize orhive.apjoin.smalltable.filesize
决定执行join的时候,小表的大小如果在下面的范围内则默认执行mapjoin
在进行join小表在25M以内,默认执行的都是mapjoin;
小表如果超过25M,默认执行的就是reducejoin;

大表与中表关联

中表指的是表的大小超过25M的,但是又不是很大的,每个节点的缓存是可以承受的;
大 2T
中 200M

如果默认执行,执行的reducejoin;效率低,容易产生数据倾斜;
此时需要强制执行mapjoin;

强制执行mapjoin的语法:

select 
/*+mapjoin(a)*/*
from log a join user b on a.userid=b.userid;

小表放左边;

大表与大表关联

user 3T 存储的是建站依赖的所有用户信息
log 某一天的日志 20G

解决方案

将一个大表进行切分

分区表,将user表切分成分区表
log表关联每一个分区表
大表关联大表 转换为 N个大表关联小表的任务

将其中一个表瘦身

将其中的一个表的数据进行一步过滤
抽取出来可以进行关联的数据,将不能进行关联的数据直接删除

先对user表进行瘦身;根据log表进行瘦身;

  1. 先求出log表中去重之后的userid;
select distinct userid from log;
  1. 根据1的结果对user表进行瘦身;
select 
/*mapjoin(a)*/b.*
from (select  distinct userid from log) a join user b on a.userid=b.userid;
  1. 开始最后关联
select 
/*mapjoin(c)*/*
from
(select 
/*mapjoin(a)*/b.*
from (select  distinct userid from log) a join user b on a.userid=b.userid) c join log d on c.userid=d.userid;

分区表如何获取一个分区的数据:
在查询的时候将分区字段进行过滤

select * from biao where dt="20181123"

分区表在查询的时候,将分区字段作为普通字段查询就可以了;

hive的优化

排序

order by 全局排序 reducetask可以有多个;
性能消耗大;
局部排序:
sort by 局部排序,对每一个reducetask结果进行排序的;
cluster by 现根据指定的字段分桶,再在每一个桶中排序;
distribute by + sort by| partition by + order by 指定字段分桶,指定字段排序

合理使用笛卡尔积

尽量避免使用笛卡尔积查询

场景中非要做笛卡尔积(两表关联,没有关联条件)

大表与小表关联

小表足够小的时候;直接写join就可以了;

select * form a,b;
select * from a join b;

开启hive的笛卡尔积的开关

set hive.strict.checks.cartesian.product=false;
set hive.mapred.mode=nonstrict;

大表与中表关联

hive中笛卡尔积可以执行,但是性能很低
map的kay不好确定

解决方案:人为添加关联键;

  1. 小表的关联键随机添加;
  2. 将大表复制多份,小表的去重之后的关联键的个数;给每一分大表数据添加关联键;都是小表的其中一个关联键;
  3. 开始真正的关联

in/exists 性能低

left semi join 代替 in/exists

合理设置maptask的个数

切片太小,造成maptask个数很多;大量的时间浪费在maptask启动和销毁上;不划算的;
切片太大,造成maptask的并行度不够;
一般一个切片对应一个block的大小,但是最后一个是1.1倍;

原始数据都是大量的小文件的时候,首先进行小文件合并,来减少maptask的个数;

增加maptask个数,主要是多job串联

JVM重用

一个maptask/reducetask 对应一个container ---对应一个yarnchild

set mapred.job.reuse.jvm.num.tasks=5;默认为1;默认情况下一个container只会运行一个maptask任务或者是reducetask任务,
在一个container中会运行多个task任务;运行完5个task任务才会销毁;

Uber:false 优步
拼车模式
针对maptask数据量比较小的时候
一个container中并行启动10maptask任务;
默认是关闭的;参数值改为true,即可使用

合理设计reducetask的个数

最多不要超过datanode*0.95

小文件合并

set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
执行 Map 前进行小文件合并

set hive.merge.mapfiles = true 在 map only 的任务结束时合并小文件

set hive.merge.mapredfiles = false true 时在 MapReduce 的任务结束时合并小文件

set hive.merge.size.per.task = 256*1000*1000 合并文件的大小
set mapred.max.split.size=256000000; 每个 Map 最大分割大小
set mapred.min.split.size.per.node=1; 一个节点上 split 的最少值

默认情况下,hive在map输入数据之前进行小文件合并的;
在生产上一般会提前进行手动合并;减轻hdfs的namenode压力;

合理进行分区和分桶

分区表

当一个表中的数据很大的时候,这个时候为了提升表的查询性能这个时候需要考虑将这个表建为分区表的
作用:减少以分区字段作为过滤条件的扫描范围的,提升性能

student_ptn 分组字段 grade
select * from student_ptn where grade=1303;
只扫描分区1303的

select * from student_ptn where yuwen>23;
全表扫描

分区字段:选经常用于过滤的字段,多个 --多级分区
分区字段在过滤查询的时候当做普通字段处理

分桶表

每个桶中的数据
分桶字段.hash%桶的个数

  1. 提升抽样性能,直接抽取某一个桶的数据作为样本数据;
  2. 提升join的性能,直接拿去两个表中对应的桶的数据进行关联就可以了;

select * from student_buk

如何获取某个桶中的数据:
实际上就是抽样查询

tablesample (bucket x out of y)
    y:桶簇的个数
    桶簇:一个(半个)或多个桶组成的集合
    student_buk 3个桶
    y=1 只有一个桶簇,这个桶簇中包含所有的桶的
    y=2 分为两个桶簇,每一个桶簇包含1.5个桶的
    y=3 分为3个桶簇,每一个桶簇包含1个桶
    y=6 分为6个桶簇,每个桶簇包含0.5个桶
    
    x:代表取得数据是第几个桶簇的;
    x=1 代表取得是第一个桶簇的;
    
select * from student_buk tablesample(bucket 1 out of 3);

轮训制;如果一个桶被分为多个桶簇;则桶簇的编号是对桶轮训编号的;

posted @ 2019-05-15 17:06  耳_东  阅读(256)  评论(0)    收藏  举报