hive 优化

hive outline

链接

hive 优化前言

一切优化,仅在瓶颈情况下,资源能够满足运行,绝不调试

hive 优化可以从以下几方面考虑:

  1. 表设计
  2. 数据存储格式
  3. 数据压缩
  4. mr属性控制
  5. 表之间的join
  6. SQL语句

hive 表设计

  • 采用分区表,减少全表扫描

  • 采用列式存储

hive 数据存储格式

hive支持的数据存储格式有TextFile(默认) SequenceFile Parquet ORC,但是尽量使用Parquet和ORC文件存储格式

  1. TextFile和 SequenceFile 的存储格式都是基于行存储的
  2. ORC 和 Parquet 是基于列式存储的
  3. Hive行式存储和列式存储的优缺点

(1)行式存储:

优点:数据被保存在一起了,insert和update更加容易
缺点:如果查询只涉及某几个列,它会把整行数据都读取出来,不能跳过不必要的列读取

(2)列式存储:

优点:最大的优势是查询时可以快速跳过没有涉及到的列,从而避免全表扫描;支持编码压缩;谓词下推、映射下推
缺点:insert/update会比较麻烦并且不适合扫描小量的数据

  • Parquet&ORC

相同点:

  1. 均为二进制列式存储,均有行组、列块的概念(二进制列式存储,可以快速跳过没有涉及到的列,从而避免全表扫描)
  2. 均支持基本数据类型、复杂数据类型(复杂数据类型不等同嵌套数据类型)
  3. 均支持压缩
  4. 均支持谓词下推、映射下推

选择parquet原因:

  1. 支持嵌套数据类型(eg:json字符串),ORC不支持。 虽然ORC不支持嵌套数据类型(eg:json字符串),但可以通过复杂数据类型(eg:array和struct)把嵌套类型给表达出来
  2. Parquet 存储格式对Spark非常友好

选择ORC原因:

(1)比Parquet 的压缩效率高(eg:snappy)
(2)ORC存储格式对hive非常友好

(3)支持事务ACID(支持事务的表必须为分桶表)

ACID就是常见数据库事务的四大特性:Atomicity(原子性)、Consistency(一致性)、Isolation(隔离性)、Durability(持久性)

(4)支持不同的索引索引机制(意味着orc查询速度快)

ORC为我们提供了两种索引机制:Row Group Index(行组索引) 和 Bloom Filter Index(布隆过滤器)

  1. Row Group Index(行组索引)

在stripe(行组)记录了每个字段的最大最小值,当查询中有<,>,=的操作时,会根据最大最小值,跳过扫描不包含的stripes

  1. Bloom Filter Index(布隆过滤器)

判读数据在不在,当它说一个值不存在的时候,一定不存在,当它说一个值存在的时候,可能存在

优点:由于存放的不是完整的数据,所以占用的内存很少,而且新增,查询速度够快
缺点: 无法做到删除数据;只能精准的判断数据不存在问题,而无法精准的判断数据存在问题,并且随着数据的增加,误判率也随之增加

hive 数据压缩

在数据规模很大和工作负载密集的情况下,采用数据压缩对磁盘I/O操作、网络数据传输有极大的帮助

hive 中的压缩就是使用了hadoop中的压缩实现的,所以hadoop中支持的压缩在hive 中都可以直接使用

hadoop中支持的压缩算法:

压缩方式选择时重点考虑:解压缩速度、压缩率、压缩后是否可以支持切片

压缩格式是否支持切片解压缩速度压缩率
snappyno最快很差
lzoyes很快很高
bzip2yes最慢最高
gzipno一般很高

mr压缩方案建议
在这里插入图片描述

lzo压缩算法的缺点:需要手动为文件创建索引,没有索引不支持文件切片

所有压缩算法的缺点:加重CPU负荷,算法越复杂,解压时间越长

参数设置:

设置map后输出压缩

1.开启hive中间传输数据压缩功能
set hive.exec.compress.intermediate=true;
 
2.开启mapreduce中map输出压缩功能
set mapreduce.map.output.compress=true;
 
3.设置mapreduce中map输出数据的压缩方式
set mapreduce.map.outout.compress.codec=
org.apache.hadoop.io.compress.SnappyCodec
org.apache.hadoop.io.compress.GzipCodec 
org.apache.hadoop.io.compress.BZip2Codec 
org.apache.hadoop.io.compress.Lz4Codec

设置reduce后输出压缩

1.开启hive最终输出数据压缩功能
set hive.exec.compress.output=true;
 
2.开启mapreduce最终输出数据压缩
set mapreduce.output.fileoutputformat.compress=true;
 
3.设置mapreduce最终数据输出压缩方式
set mapreduce.output.fileoutputformat.compress.codec =
org.apache.hadoop.io.compress.SnappyCodec
org.apache.hadoop.io.compress.GzipCodec 
org.apache.hadoop.io.compress.BZip2Codec 
org.apache.hadoop.io.compress.Lz4Codec
 
4.设置mapreduce最终数据输出压缩为块压缩
set mapreduce.output.fileoutputformat.compress.type=BLOCK;

mr属性优化

  • 开启本地模式

原因:有时候数据量不大的表用本地计算模式处理,时间会很短,但是默认情况下会将任务提交到集群,等待资源分配,这个过程不仅繁琐,而且更浪费时间

限制条件:如果以下任意一个条件不满足,那么即使开启了本地模式,将依旧会提交给YARN集群运行

  1. 处理的数据量不超过128M
  2. MapTask的个数不超过4个
  3. ReduceTask的个数不超过1个
-- 开启本地模式,默认false
set hive.exec.mode.local.auto = true;
  • 开启JVM重用

原理:JVM指代一个Java进程,假设一个mr程序中有100个MapTask,那么Hadoop默认会为每个MapTask启动一个JVM(共100个),JVM频繁的创建和销毁,会导致内存开销较大,为了解决上述问题,Hadoop中提供了JVM重用机制来,JVM重用机制可以使得一个JVM实例在同一个job中被使用N次(不同mr job中不可以),即当一个MapTask运行结束以后,JVM不会进行释放,而是继续供让一个MapTask使用,直到运行了N个以后,就会释放,N的值可以在Hadoop的mapred-site.xml文件中进行配置,通常在10-20之间,也可以再hive中直接进行设置

使用条件:适用于大量小文件场景,没有大量小文件,就不需要开启。因为开启JVM重用将一直占用使用到的task插槽,以便进行重用,直到任务完成后才能释放

注意事项:hadoop3.x后已经不再支持该选项

mapred-site.xml

<property>
  <name>mapreduce.job.jvm.numtasks</name>
  <value>15</value>
  <description>How many tasks to run per jvm. If set to -1, there is
  no limit. 
  </description>
</property>

hive

set mapred.job.reuse.jvm.num.tasks=10;
  • 开启Stage的并行执行

原因:Hive会将一个查询解析为多个Stage,即阶段(例如MapReduce阶段、抽样阶段、合并阶段、limit阶段),有时候Stage彼此之间有依赖关系,只能挨个执行,但是在一些别的场景下,很多的Stage之间是没有依赖关系的,例如Union语句,Join语句等等,这些Stage没有依赖关系,但是Hive依旧默认挨个执行每个Stage,这样会导致性能非常差,我们可以通过修改参数,开启并行执行,当多个Stage之间没有依赖关系时,允许多个Stage并行执行,提高性能

注意:线程数设置的越多,程序运行速度越快,但同样更消耗CPU资源

-- 打开任务并行执行,默认为false
 set hive.exec.parallel=true; 
-- 同一个sql允许最大并行度,默认为8 
set hive.exec.parallel.thread.number=16;
  • 开启关联优化

当我们执行以下SQL语句:

select id from dept group by id order by id desc;

Hive会使用两个MapReduce来完成这两个操作,第一个MapReduce做group by,经过shuffle阶段对id做分组,第二个MapReduce对第一个MapReduce的结果做order by,经过shuffle阶段对id进行排序,这样会导致性能相对较差,当我们在Hive中开启关联优化后,可以把分组和排序放在同一个MapReduce中实现

-- 默认false
set hive.optimize.correlation=true;

表之间的Join

Hive实现Join时,为了提高性能,提供了多种Join方案,例如适合小表Join大表的Map Join,大表Join大表的Reduce Join,以及大表Join的优化方案Bucket Join等

  • 如果至少有一个表是小表,则尽量使用Map Join
-- 默认已经开启了Map Join
hive.auto.convert.join=true
-- hive会自动对表数据量进行判读,若到达小表条件,就将小表就加入内存

注意:Hive会自动判断是否满足Map Join,如果不满足Map Join的条件,则自动执行Reduce Join,但是需要了解Hive是怎么判断哪张表是小表的,以及一些使用限制?

怎么判断是不是小表的?

-- 2.0版本之前的控制属性 (默认25M以下认为是小表)
set hive.mapjoin.smalltable.filesize=25000000

使用限制:

  • left join 的左表必须是大表
  • right join 的右表必须是大表
  • inner join 无要求
  • full join 不能使用 map join
  • map join 支持小表为子查询
  • 使用 map join 时,若引用小表或子查询时,需要引用别名
  • 在map join 中,可以使用不等值连接或者使用or连接多个条件
  • 在 map join 中最多支持指定6张小表,否则报语法错误

原理:

小表数据首会分发给每个MapTask的内存一份,然后逐次取出大表部分数据和小表进行join,底层不需要经过shuffle

  • 如果2个表都是大表且频繁join,则可以选择Bucket Join

Bucket Join(桶表join又分为2种)

  1. 普通的Bucket Join
  2. 排序的Bucket Join,简称SMB Join(不仅分桶了,而且在桶内进行了排序)

注意:分桶字段 = Join字段 ,桶的个数相等或者成倍数

-- 开启分桶普通join,默认false
set hive.optimize.bucketmapjoin = true;
-- 开启分桶SMB join
set hive.optimize.bucketmapjoin = true;
-- 默认true
set hive.auto.convert.sortmerge.join=true;
-- 默认false
set hive.optimize.bucketmapjoin.sortedmerge = true;
-- 默认true
set hive.auto.convert.join.noconditionaltask=true;
  • 如果实在没有办法避免大表之间的join,且无法使用Bucket Join,则老老实实的使用 Reduce Join

原理:经shuffle分组后,将key相同的数据分发到同一个reducer,实现大表之间的join

注意:Hive会自动判断是否满足Map Join,如果不满足Map Join,则自动执行Reduce Join

SQL语句

  • 使用 from … insert 代替 union all

insert into table stu1 partition(dt1) 
select max(s_birth)
from stu_ori
group by s_age

union all

insert into table stu1 partition(dt2) 
select min(s_birth) 
from stu_ori
group by s_age;

分析:
上面的SQL语句,就是将每个年龄段的最大和最小的生日获取出来放到同一张表的不同分区中,union all 前后的两个语句都是对同一张表的s_age进行分组,然后分别取最大值和最小值,由于对同一张表的相同字段进行两次分组,这显然造成了极大浪费,使用 from … insert … ,这个语法可避免这种情况

--开启动态分区 
set hive.exec.dynamic.partition=true; 
set hive.exec.dynamic.partition.mode=nonstrict; 

from stu_ori 

insert into table stu1 partition(dt1)
select max(s_birth) 
group by s_age

insert into table stu2 partition(dt2)
select min(s_birth) 
group by s_age;
  • 使用group by 代替 count(distict)

我们有如下的用户访问数据 visit.txt

用户id 和 该用户访问的店铺名称

u1	a
u2	b
u1	b
u1	a
u3	c
u4	b
u1	a
u2	c
u5	b
u4	b
u6	c
u2	c
u1	b
u2	a
u2	a
u3	a
u5	a
u5	a
u5	a

建表并导入数据

create table visit
    (user_id string,shop string)
row format delimited fields terminated by '\t';

load data local inpath '/opt/modules/input/visit.txt' into table visit;

需求:求每个店铺的UV(访客数)

分析:

需要对顾客进行去重,但要避免使用 distict 关键字,因为这个关键字在使用后所有数据会到一个Reduce Task,有可能会导致内存溢出,所以可以使用 GROUP BY 再 COUNT 的方式替换 distict

-- 原语句
SELECT
    shop,
    count(distinct user_id) uv
FROM
    visit
group by shop;

-- 优化后语句
SELECT
    shop,
    count(*) uv 
FROM
    ( SELECT shop, user_id FROM visit GROUP BY shop, user_id ) t1 
GROUP BY
    shop;

在这里插入图片描述

原理:

  1. distinct的命令会在内存中构建一个hashtable,查找去重的时间复杂度是O(1),但这个关键字在使用后所有数据只会由一个Reduce Task进行处理,在大数据背景下,可能会导致Reduce Task内存溢出
  2. group by 会分组,然后在分组内排序,如果排序方式为快速排序的话,时间复杂度是比较高O(nlogn),虽然排序花费时间长,但是不会内存溢出;另外(group by)去重会转化为两个任务,会消耗更多的磁盘网络I/O资源

总结:distinct 耗费内存,但是效率极高,若数据较大时,可能会产生OOM,如果distinct遇到瓶颈,想要调优,第一时间要想到用group by

  • 谓词下推

谓词下推 Predicate Pushdown(PPD)的简单点说就是在不影响最终结果的情况下,尽量将过滤条件提前执行。谓词下推后,过滤条件在map端执行,减少了map端的输出,降低了数据在集群上传输的量,降低了Reduce端的数据负载

谓词下推,这里主要说的是 on 中谓词 where 中谓词,on不仅可以作为连接条件,也可以作为过滤条件

(1)left join:右侧的表过滤条件写在on后面,左侧的表过滤条件写在where后面,性能上有提高
(2)join:写在哪里都一样
(3)full join:过滤条件写在 where 后可以谓词下推,写在 on 后不可以谓词下推(默认cbo引擎下)

为什么?

  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值