Hive优化
Hive优化思想:
Hive是将符合SQL语法的字符串解析生成可以在Hadoop上执行的MapReduce的工具,所以学习MapReduce的原理对我们使用hive,优化hive有很大的帮助。
使用Hive尽量按照分布式计算的一些特点来设计sql,可以提升效率。
Hive性能优化时,把HiveQL当做M/R程序来读,即从M/R的运行角度来考虑优化性能,从更底层思考如何优化运算性能,而不仅仅局限于逻辑代码的替换层面。
前提:
(1)熟知业务要求
(2)熟悉业务数据(业务中各种数据类型,数据大小)
(3)了解数据分布,可以解决数据倾斜问题,想要解决特定业务场景下的问题,还必须熟悉业务逻辑。
例如:通过set hive.groupby.skewindata=true;解决数据倾斜问题,这是使用的算法优化,但算法优化有时不能适应特定业务背景,开发人员了解业务,了解数据,可以通过业务逻辑精确有效的解决数据倾斜问题。
一.使用合适的数据类型和存储格式
1.使用合适的数据类型
Hive 提供了基本数据类型和复杂数据类型,在特定的业务场景下,使用相应的数据类型可以减小内存开销,提升效率。
- 整型
- TINYINT : 微整型,只占用1个字节,只能存储0-255的整数。
- SMALLINT: 小整型,占用2个字节,存储范围–32768 到 32767。
- INT: 整型,占用4个字节,存储范围-2147483648到2147483647。
- BIGINT– 长整型,占用8个字节,存储范围-2^63到2^63-1。
- 字符串型
- STRING: 不设定长度
- VARCHAR: (从Hive0.12.0开始支持)字符数范围1-65535,长度不定字符串
- CHAR: (从Hive0.13.0开始支持)最大字符数:255,长度固定字符串
从以上数据类型的大小可以看出,在熟知业务数据以及业务要求之后,我们可以选择合适的数据类型。CHAR类型在字符串长度小于255的时候,会用空格补充,造成资源浪费,在对CHAR类型的数据运算的时候也会增大内存开销。
在我们QILAP项目中,学生姓名,班级名称,题目所属学科,题目所属阶段,题目难度等字段,由于我们已经知道字符串大概长度,所以全都可以使用VARCHAR类型。由于学生分数最大为100分,所以可以使用TINYINT类型。
注:
Hive-0.12.0版本引入了VARCHAR类型,VARCHAR类型使用长度指示器(1到65355)创建,如果一个字符串值转换为或者被赋予一个varchar值,其长度超过了长度指示器则该字符串值会自动被截断。
Hive-0.13.0版本引入了CHAR类型,CHAR类型与VARCHAR类型相似,但拥有固定的长度,也就是如果字符串长度小于指示器的长度则使用空格填充。CHAR类型的较大长度为255。String 类型 与Java中的类型很相似
2.存储格式(详情见MR教案-MR高阶知识)
① TEXTFILE //文本,默认值
② SEQUENCEFILE // 二进制序列文件
③ RCFILE //列式存储格式文件 Hive0.6以后开始支持
④ ORC //列式存储格式文件,比RCFILE有更高的压缩比和读写效率,Hive0.11以后开始支持
⑤ PARQUET //列出存储格式文件,Hive0.13以后开始支持
二.Hive的具体优化
由于hive底层使用的mapreduce计算框架,因此也会衍生出很多问题:
- Hive数据规模大不是问题,Hive本身就是用来对大规模数据查询、计算的。但是,发送数据倾斜会影响Hive运算效率。
- jobs数比较多的作业运行效率相对比较低,比如即使有几百行的表,如果多次关联对此汇总,产生几十个jobs,将会需要30分钟以上的时间且大部分时间被用于作业分配,初始化和数据输出。MR作业初始化的时间是比较耗时间资源的一个部分。
- 在使用SUM,COUNT,MAX,MIN等UDAF函数时,不怕数据倾斜问题,Hadoop在Map端的汇总合并优化过,使数据倾斜不成问题。
- COUNT(DISTINCT)在数据量大的情况下,效率较低,如果多COUNT(DISTINCT)效率更低,因为COUNT(DISTINCT)是按GROUP BY字段分组,按DISTINCT字段排序,一般这种分布式方式是很倾斜的;比如:男UV,女UV,淘宝一天30亿的PV,如果按性别分组,分配2个reduce,每个reduce处理15亿数据。
- 数据倾斜是导致效率大幅降低的主要原因,可以采用多次 Map/Reduce 的方法, 避免倾斜。
下面从多个角度对Hive进行性能优化:
1.map/reduce数目优化
- Map阶段优化
通过配置参数,设置合适的map数量:
- mapred.min.split.size: 指的是数据的最小分割单元大小;min的默认值是1B
- mapred.max.split.size: 指的是数据的最大分割单元大小;max的默认值是256MB
- dfs.block.size: 指的是HDFS设置的数据块大小。个已经指定好的值,而且这个参数默认情况下hive是识别不到的
通过调整这三个参数的大小来调整map的数量:
减小参数可以增加map数,增大参数可以减少map数。
注: (1)直接调整mapred.map.tasks这个参数是没有效果的。
(2)这个优化只是针对Hive 0.9版本。
- reduce阶段优化
Reduce阶段优化的主要工作也是选择合适的reduce task数量, 与map优化不同的是,reduce优化时,可以直接设置mapred.reduce.tasks参数从而直接指定reduce的个数。
- hive.exec.reducers.max:这个参数的含义是最多启动的Reduce个数(此参数从Hive 0.2.0开始引入。在Hive 0.14.0版本之前默认值是999;而从Hive 0.14.0开始,默认值变成了1009)
- hive.exec.reducers.bytes.per.reducer:这个参数的含义是每个Reduce处理的字节数。比如输入文件的大小是1GB,那么会启动4个Reduce来处理数据。(此参数从Hive 0.2.0开始引入。在Hive 0.14.0版本之前默认值是1G;而从Hive 0.14.0开始,默认值变成了256M)。
根据输入的数据量大小来决定Reduce数量,数据量=reduce个数*每个reduce处理的数据大小。
如果我们将hive.exec.reducers.max设置到最大1009个,通过调整每个reduce处理的数据大小(hive.exec.reducers.bytes.per.reducer)就可以调整reduce的数量。
注:
(1)Reduce的个数对整个作业的运行性能有很大影响。如果Reduce设置的过大,那么将会产生很多小文件,对NameNode会产生一定的影响,而且整个作业的运行时间未必会减少;如果Reduce设置的过小,那么单个Reduce处理的数据将会加大,很可能会引起OOM异常。
- 如果设置了mapred.reduce.tasks/mapreduce.job.reduces参数,那么Hive会直接使用它的值作为Reduce的个数;
- 如果mapred.reduce.tasks/mapreduce.job.reduces的值没有设置(也就是-1),那么Hive会根据输入文件的大小估算出Reduce的个数。根据输入文件估算Reduce的个数可能未必很准确,因为Reduce的输入是Map的输出,而Map的输出可能会比输入要小,所以最准确的数根据Map的输出估算Reduce的个数。
2.列裁剪
列裁剪就是只查询我们需要的列,而忽略不需要的列。我们在使用hive的时候,很多时候都用到了列裁剪,只是不知道这种方式叫列裁剪而已。
例:表T有5列(a,b,c,d,e)执行sql语句select a,c,d from T;
Hive只查询了a,c,d列,忽略了b,e列,这样降低了读取开销。
列裁剪所对应的参数项:hive.optimize.cp=true(默认值为真)。
3.设置分区
分区:每个表可以有一个或多个分区,用于确定数据的存储方式。只能在表的相关分区上运行该查询,从而显着加快分析速度。但请注意,仅仅因为分区名为2018-12-6并不意味着它包含该日期的全部,保证分区名称和数据内容之间的关系由用户的决定; 只是为方便起见,分区以日期命名;分区列是虚拟列,这些抽象允许系统在查询处理期间修剪大量数据,从而加快查询执行速度。
注:
· 在Hive Select查询中一般会扫描整个表内容,会消耗很多时间做没必要的工作。有时候只需要扫描表中关心的一部分数据,因此建表时引入了partition概念。
· 分区表指的是在创建表时指定的partition的分区空间。
· 如果需要创建有分区的表,需要在create表的时候调用可选参数partitioned by
4.join优化
在使用写有 Join 操作的查询语句时有一条原则:应该将条目少的表/子查询放在 Join 操作符的左边。原因是在 Join 操作的 Reduce 阶段,位于 Join 操作符左边的表的内容会被加载进内存,将条目少的表放在左边,可以有效减少发生 OOM 错误的几率。对于一条语句中有多个 Join 的情况,如果 Join 的条件相同,就将小表放在左边,前提条件是小表中包含了我们需要的全部字段。
hive中的join操作的关键字必须在on中指定,不能再where中指定,不然会先做笛卡尔积再过滤;
如果 Join 的 key 相同,不管有多少个表,都会则会合并为一个 Map-Reduce
- 在join前过滤数据
在join前过滤,减小了join的时候操作的数据量
例:
select
...
from
A
join
B
on
A.key = B.key
where
A.id>10
and B.id<10
and A.dt='20120417'
and B.dt='20120417';
改写为:
select
....
from
(
select
....
from
A
where
dt='201200417'
and
id>10
) a
join
(
select
....
from
B
where
dt='201200417'
and
id < 10
) b
on
a.key = b.key;
- map端join
mapJoin的主要意思就是,当链接的两个表是一个比较小的表和一个特别大的表的时候,我们把比较小的table直接放到内存中去,然后再对比较大的表格进行map操作。join就发生在map操作的时候,每当扫描一个大的table中的数据,就要去去查看小表的数据,哪条与之相符,继而进行连接。这里的join并不会涉及reduce操作。map端join的优势就是在于没有shuffle。
在实际的应用中,我们这样设置:set hive.auto.convert.join=true;
这样设置hive就会自动的识别比较小的表,继而用mapJoin来实现两个表的联合。看看下面的两个表格的连接。这里的dept相对来讲是比较小的。我们看看会发生什么,如图所示:
这里的第一句话就是运行本地的map join任务,继而转存文件到XXX.hashtable下面,在给这个文件里面上传一个文件进行map join,之后才运行了MR代码去运行计数任务。说白了,在本质上mapjoin根本就没有运行MR进程,仅仅是在内存就进行了两个表的联合。具体运行如下图:
- 使用相同的连接键
当对3个或者更多个表进行join连接时,如果每个on子句都使用相同的连接键的话,那么只会产生一个MapReduce job。
5.根据数据大小采用不同的hive模式
Hive,Map-Reduce和Local-Mode
Hive编译器为大多数查询生成map-reduce作业。然后将这些作业提交给变量指示的Map-Reduce集群: mapred.job.tracker
虽然这通常指向具有多个节点的map-reduce集群,但Hadoop还提供了一个漂亮的选项,可以在用户的工作站上本地运行map-reduce作业。这对于在小型数据集上运行查询非常有用 - 在这种情况下,本地模式执行通常比将作业提交到大型集群要快得多。从HDFS透明地访问数据。相反,本地模式仅使用一个reducer运行,并且处理较大的数据集可能非常慢。
从版本0.7开始,Hive完全支持本地模式执行。要启用此功能,用户可以启用以下选项: hive> SET mapreduce.framework.name = local;
此外,mapred.local.dir应指向在本地计算机上有效的路径(例如/tmp/<username>/mapred/local)。(否则,用户将获得分配本地磁盘空间的异常。)
从版本0.7开始,Hive还支持一种模式,可以自动在本地模式下运行map-reduce作业。
相关的选项有:hive.exec.mode.local.auto,hive.exec.mode.local.auto.inputbytes.max和hive.exec.mode.local.auto.tasks.max:
hive> SET hive.exec.mode.local.auto = false;
请注意,默认情况下禁用此功能。如果启用,Hive将分析查询中每个map-reduce作业的大小,如果满足以下阈值,则可以在本地运行它:
1.作业的总输入大小低于:(hive.exec.mode.local.auto.inputbytes.max默认为128MB)
2.map-tasks的总数小于:(hive.exec.mode.local.auto.tasks.max默认为4)
3.所需的reduce任务总数为1或0。
因此,对于小数据集的查询,或者对于具有多个map-reduce作业的查询,其中后续作业的输入要小得多(由于先前作业中的减少/过滤),可以在本地运行作业。
请注意,Hadoop服务器节点和运行Hive客户端的计算机的运行时环境可能存在差异(因为不同的jvm版本或不同的软件库)。在本地模式下运行时,这可能会导致意外的行为/错误。另请注意,本地模式执行是在一个单独的子jvm(Hive客户端)中完成的。如果用户愿意,可以通过该选项控制该子jvm的最大内存量hive.mapred.local.mem。默认情况下,它设置为零,在这种情况下,Hive允许Hadoop确定子jvm的默认内存限制。
6.数据倾斜问题
表现:任务进度长时间维持在99%(或100%),查看任务监控页面,发现只有少量(1个或几个)reduce子任务未完成。因为其处理的数据量和其他reduce差异过大。单一reduce的记录数与平均记录数差异过大,通常可能达到3倍甚至更多。 最长时长远大于平均时长。
原因:
- key分布不均匀
- 业务数据本身的特性
- 建表时考虑不周
- 某些SQL语句本身就有数据倾斜
关键词 | 情形 | 造成后果 |
join | 其中一个表较小,但是某一个key较多 | 分发到某一个或几个Reduce上的数据远高于平均值 |
join | 大表与大表,但是分桶的判断字段0值或空值过多 | 这些空值都由一个reduce处理 |
group by | group by 维度过小,某值的数量过多 | 处理某值的reduce非常耗时 |
count (distinct) | 某特殊值过多 | 处理此特殊值reduce耗时 |
设置hive.groupby.skewindata为true和hive.map.aggr为true避免数据倾斜。
设置hive.map.aggr为true:在map中会做部分聚集操作,效率更高但需要更多的内存。
此处需要设定 hive.groupby.skewindata,当选项设定为 true 是,生成的查询计划有两 个 MapReduce 任务。在第一个 MapReduce 中,map 的输出结果集合会随机分布到 reduce 中, 每个 reduce 做部分聚合操作,并输出结果。这样处理的结果是,相同的 Group By Key 有可能分发到不同的 reduce 中,从而达到负载均衡的目的;第二个 MapReduce 任务再根据预处 理的数据结果按照 Group By Key 分布到 reduce 中(这个过程可以保证相同的 Group By Key 分布到同一个 reduce 中),最后完成最终的聚合操作。
原理:
(1)会生成两个job来执行group by,第一个job中,各个map是平均读取分片的,在map阶段对这个分片中的数据根据group by 的key进行局部聚合操作,这里就相当于Combiner操作。
(2)在第一次的job中,map输出的结果随机分区,这样就可以平均分到reduce中
(3)在第一次的job中,reduce中按照group by的key进行分组后聚合,这样就在各个reduce中又进行了一次局部的聚合。
(4)因为第一个job中分区是随机的,所有reduce结果的数据的key也是随机的,所以第二个job的map读取的数据也是随机的key,所以第二个map中不存在数据倾斜的问题。
(5)在第二个job的map中,也会进行一次局部聚合。
(6)第二个job中分区是按照group by的key分区的,这个地方就保证了整体的group by没有问题,相同的key分到了同一个reduce中。
(7)经过前面几个聚合的局部聚合,这个时候的数据量已经大大减少了,在最后一个reduce里进行最后的整体聚合
注:
设置set hive.groupby.skewindata=true ,默认该参数的值为false,表示不启用,要启用时,可以set hive.groupby.skewindata=ture;进行启用。
当启用时,能够解决数据倾斜的问题,但如果要在查询语句中对多个字段进行去重统计时会报错。
hive> set hive.groupby.skewindata=true;
hive> select count(distinct id),count(distinct x) from test;
FAILED: SemanticException [Error 10022]: DISTINCT on different columns not supported with skew in data
下面这种方式是可以正常查询
hive>select count(distinct id, x) from test;
7.合并小文件
文件数目小,容易在文件存储端造成瓶颈,给HDFS 带来压力,影响处理效率。对此,可以通过合并Map和Reduce的结果文件来消除这样的影响。
hive.merg.mapfiles=true:合并map输出文件
hive.merge.mapredfiles=false:合并reduce输出文件
hive.merge.size.per.task=256*1000*1000:合并文件的大小
hive.mergejob.maponly=true:如果支持CombineHiveInputFormat则生成只有Map的任务执行merge
hive.merge.smallfiles.avgsize=16000000:文件的平均大小小于该值时,会启动一个MR任务执行merge。
8.排序优化
order by : 对查询结果进行全局排序,消耗时间长。需要 set hive.mapred.mode=nostrict
sort by : 局部排序,并非全局有序,提高效率。
9.从程序角度优化
想要详细了解如何从程序角度优化:
请参考:https://www.cnblogs.com/smartloli/p/4356660.html
参考自:
https://blog.csdn.net/chybin500/article/details/80988089
https://www.cnblogs.com/smartloli/p/4356660.html
https://www.cnblogs.com/sandbank/p/6408762.html
https://blog.csdn.net/mrlevo520/article/details/76339075/