Hive 优化
引言
\quad \quad Hive的底层是MapReduce,当数据表太大时,往往可以通过并行来提高效率,比如通过分区实现运行多个reduce,可是如果处理不当则容易引发数据倾斜,从而导致效率较低,这就涉及到Hive 的优化。Hive的优化可以从以下几个方面进行
1、数据存储格式
Hive文件存储格式有以下几种:
-
TextFile是Hive默认的文件存储格式,存储方式为行存储;使用这种方式,hive不会对数据进行切分,从而无法对数据进行并行操作。
-
sequencefile二进制文件,以<key,value>的形式序列化到文件中;存储方式:行存储;
-
refile读取需要的列只需要读取每个row group 的头部定义;读取全量数据的操作 性能可能比sequencefile没有明显的优势
-
orc存储方式:数据按行分块,每块按照列存储;压缩快 快速列存取;效率比rcfile高,是rcfile的改良版本。
-
parquet类似于orc,相对于orc文件格式,hadoop生态系统中大部分工程都支持parquet文件。
比对三种主流的文件存储格式TEXTFILE 、ORC、PARQUET
压缩比:ORC > Parquet > textFile(textfile没有进行压缩)
查询速度:三者几乎一致
HDFS上显示的是原来的文件名,如果压缩的话,使用类似于000000_0的文件名
- 对于大量数据的情况下,工作中常用orc 存储格式
- 我们在建表的时候可以通过命令
stored as
指定文件存储格式为orc,从而提高查询效率。
create table tableName(字段名称 字段类型 [comment '中文注释说明'],字段名称 字段类型, ....)
row format delimited fields terminated by 'char分割符即列分割符'
lines terminated by '行分割符'
stored as orc;
2、MapReduce优化
如何设置Map数、Reduce数很关键,因为设置的不当,就容易造成数据倾斜。
2.1 Map端优化
1、合理设置Map数
(1)通常情况下,作业会通过input的目录产生一个或者多个map任务。
- 主要的决定因素有:input的文件总个数,input的文件大小,集群设置的文件块大小。
(2)是不是map数越多越好?
- 答案是否定的。如果一个任务有很多小文件(远远小于块大小128m),则每个小文件也会被当做一个块,用一个map任务来完成,而一个map任务启动和初始化的时间远远大于逻辑处理的时间,就会造成很大的资源浪费。而且,同时可执行的map数是受限的。
(3)是不是保证每个map处理接近128m的文件块,就高枕无忧了?
- 答案也是不一定。比如有一个127m的文件,正常会用一个map去完成,但这个文件只有一个或者两个小字段,却有几千万的记录,如果map处理的逻辑比较复杂,用一个map任务去做,肯定也比较耗时。
针对上面的问题2和3,我们需要采取两种方式来解决:即减少map数和增加map数;
2、小文件进行合并——减少map数
- 在Map执行前即Map输入合并小文件:
set hive.input.format= org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
3、复杂文件增加Map数
-
当input的文件都很大,任务逻辑复杂,map执行非常慢的时候,可以考虑增加Map数,来使得每个map处理的数据量减少,从而提高任务的执行效率。
-
增加map的方法为:根据
- computeSliteSize(Math.max(minSize,Math.min(maxSize,blocksize)))=blocksize=128M公式,调整maxSize最大值。让maxSize最大值低于blocksize就可以增加map的个数。
2.2 Reduce端优化
- 合理控制reducer数量
- 通过
set mapreduce.job.reduces=n
直接设置reducer数量,这个方法效率高
3、裁剪
\quad \quad 尽可能早地过滤掉尽可能多的数据量,避免大量数据流入外层SQL。
3.1 列裁剪
-
只获取需要的列的数据,减少数据输入。
-
少用select *
3.2 分区裁剪
-
分区在hive实质上是目录,分区裁剪可以方便直接地过滤掉大部分数据。
-
尽量使用分区过滤,也就是尽量先将所需的数据过滤出来,然后再进行其他
比如:有两个表:orders表记录了订单数,用户id;trains表记录了周几下单,用户id;先统计周一的下单数。
方式一:先关联再过滤,不推荐
select count(*) order_cnt
from orders ord
inner join trains tr
on ord.order_id=tr.order_id
where order_dow=1;
方式二:先过滤再关联,推荐
select count(*) order_cnt
from orders ord
inner join trains tr
on (ord.order_id=tr.order_id and order_dow=1 );
或者直接写成子查询
select count(*) order_cnt
from (select * from orders where order_dow=1) ord
inner join trains tr
on ord.order_id=tr.order_id;
4、join 优化
4.1 调整on后面的约束条件
- 一个MR job a,b,c
select a.val,b.val,c.val
from a
join b on (a.key = b.key1)
join c on (a.key = c.key1)
- 生成多个MR job a,b,b,c
select a.val,b.val,c.val
from a
join b on (a.key = b.key1)
join c on (c.key = b.key2)
4.2 表的连接顺序
- 小表 join 大表
- 按照JOIN顺序中的最后一个表应该尽量是大表
4.3 Mapjoin
-
MapJoin顾名思义,就是在Map阶段进行表之间的连接。而不需要进入到Reduce阶段才进行连接。这样就节省了在Shuffle阶段时要进行的大量数据传输。从而起到了优化作业的作用。
-
MapJoin适用于有一张十分小的表和一张大的表的场景,会把小表全部加载到内存中,在map阶段直接拿另外一个表的数据和内存中表数据做匹配,这样的话就可以在MapTask阶段将非常小的那几张表加载进内存,提前处理业务从而减少Reduce端的压力,以减少数据倾斜。
-
Mapjoin工作机制
假设a表为一张大表,b为小表,
-
首先是Task A,它是一个Local Task(在客户端本地执行的Task),负责扫描小表b的数据,将其转换成一个HashTable的数据结构,并写入本地的文件中,之后将该文件加载到DistributeCache中。
-
接下来是Task B,该任务是一个没有Reduce的MR,启动MapTasks扫描大表a,在Map阶段,根据a的每一条记录去和DistributeCache中b表对应的HashTable关联,并直接输出结果。
-
由于MapJoin没有Reduce,所以由Map直接输出结果文件,有多少个Map Task,就有多少个结果文件。
语法
select
/*+ MAPJOIN(aisles) */ a.aisle as aisle_name, b.product_name
from aisles a
inner join products b
on a.aisle_id=b.aisle_id
limit 10;
其中,b表是小表
5、笛卡尔积
\quad \quad 尽量避免笛卡尔积,即避免join的时候不加on条件,或者无效的on条件,Hive只能使用1个reducer来完成笛卡尔积。
6、count(distinct)—>group by优化
-
数据量小的时候无所谓,数据量大的情况下,由于count(distinct) 操作需要用一个reduce Task来完成,这一个Reduce需要处理的数据量太大,就会导致整个Job很难完成,一般count(distinct)使用先group by 再count的方式替换
-
注意:在工作中针对数据去重 能使用group by 就不使用distinct
- 使用group by 多个reduce进行处理
- distinct 所有数据在一个reduce中进行处理
SELECT count(DISTINCT id) FROM bigtable;
可以转化为
SELECT count(id) FROM (SELECT id FROM bigtable GROUP BY id) a;
7、压缩
压缩:减小数据的小大和数据磁盘读取时间
- map输出压缩
#设置为true为激活map输出数据压缩功能,默认是false,没有开启
set mapreduce.map.output.compress=true;
#设置map输出数据的压缩算法
set mapreduce.map.output.compress.codec= org.apache.hadoop.io.compress.SnappyCodec;
- 中间数据压缩:对hive查询多个job之间的数据进行压缩
#设置为true为激活中间数据压缩功能,默认是false,没有开启
set hive.exec.compress.intermediate=true;
#设置中间数据的压缩算法
set hive.intermediate.compression.codec= org.apache.hadoop.io.compress.SnappyCodec;
set hive.intermediate.compression.type=BLOCK;
- 结果数据压缩:reducer输出数据,结果数据作为其他查询任务数据源
set hive.exec.compress.output=true;
set mapreduce.output.fileoutputformat.compress.codec=org.apache.hadoop.io.compress.SnappyCodec;
set mapreduce.output.fileoutputformat.compress.type=BLOCK;
8、并行执行
- 同步执行hive的多个阶段
- hive可以在执行过程中,将一个查询转化为一个或多个阶段。某个特定的job可能包含众多的阶段,而这些阶段可能并非完全相互依赖的,也就是说可以并行执行的,这样可以使得整个任务的执行时间缩短,提高集群资源利用率。
--开启并行执行;默认false
set hive.exec.parallel=true;
--同一个sql允许最大并行度,默认为8。可设置
set hive.exec.parallel.thread.number=16;
9、动态分区优化
- 当你所执行的任务,只需要从源数据的一部分进行查询,那么你就可以使用分区进行优化,然后在你所需要的那个分区内执行任务即可,而不是扫描整个数据表,这样就可以提高你的查询效率。
- 使用动态分区,必须打开动态分区模式,并且设置分区模式为非严格模式
--1.打开动态分区模式:
set hive.exec.dynamic.partition=true;
--2.设置分区模式为非严格模式
set hive.exec.dynamic.partition.mode=nonstrict;
注意:以上设置,只在当前的会话窗口有效,关闭会话窗口,就不再有效了,需要重新设置
动态分区的话,不需要指定分区字段等于什么
- 加载本地数据到动态分区表中
load data local inpath ‘文件路径’ into table 表名 partition (分区字段);
- 加载本地数据到一个多分区的表中去
load data local inpath ‘/export/servers/hivedatas/score.csv’ into table score2 partition(year,month,day);
- 也可以通过将查询结果加载到表中
insert overwrite table udata_partition partition (dt)
select
user_id, item_id, rating
, to_date(from_unixtime(cast(`timestamp` as bigint), 'yyyy-MM-dd HH:mm:ss')) as res
from udata
where user_id='244';
应用场景:
- 不确定分区数量,数据量也不是很大,使用动态分区,以及在插入数据的分区字段是不确定的情况下。
- 实际工作中趋向于使用动态分区!!!
10、分桶优化
- 分桶优化也是减小查询量,提高查询效率
- 与分区不同的是,分区采用的分区字段不是原表中的字段名,比如年、月、日、城市这些常用的字段
- 如果你想知道某个时间段的相关信息,那么你就可以采用分区
- 但分桶采用的字段是原表中所拥有的字段
11、数据倾斜优化
原因:
- 对数据进行聚合比如分组,key分布不均匀
- 人为的建表疏忽
- 业务数据特点
- map和reduce个数设置不当
体现:
- 任务进度长时间维持在99%,查看任务监控页面,发现只有少量reduce子任务未完成
- 查看未完成的子任务,可以看到本地读写数据量积累非常大,通常超过10GB可以认定为发生数据倾斜。
万能解决方法
--有数据倾斜的时候进行负载均衡
set hive.groupby.skewindata = true;
11.1 异常值处理
现象:
- 当异常值比如一些特殊符号之类的、空值比较多时,比方说在进行分桶的过程中,会将空值分到一起,也容易产生数据倾斜
处理:
- 对异常值赋予随机变量来分散key,可以通过rand随机函数将为NULL值分散到不同的值上
case when uid is null then cast(rand(100)*10000 as int) else uid end
- 将异常值单独拿出来处理,最后再合并进去
11.2 大小表关联
现象:
- Hive在进行join时,因为join左边的表的会首先读入内存,如果左边的表的数据量过大,key比较集中,那么就容易导致数据倾斜
处理:
- 小表 join 大表
未完待续
参考资料:
https://blog.csdn.net/weixin_43520450/article/details/107872251