hive调优

550 阅读14分钟

一、hive执行流程

要相对hive调优必须要了解hive的执行流程,hive就是一个查询引擎,再收到一个sql的时候会进行以下操作:

1、词法分析/语法分析使用antlr将语句解析城抽象语法树
2、语义分析,从megastore获取模式信息,验证sql语句中队表名,列名,以及数据类型的检测和隐式转换,以及hive提供的函数和用户的自定义函数
3、逻辑计划生成。生成逻辑计划--算子数
4、逻辑计划优化。对算子数进行优化,列剪枝,分区剪枝,谓词下推等
5、物理计划生成。将逻辑计划生成包含以后mapreduce任务组成的物理计划
6、物理计划执行。将dag发送到hadoop集群进行执行
7、最后把结果返回

例子:select dept,sum(salary) from emp group by dept;

1)首先sql输入的是字符串,hive需要通过antlr(解析器生成器)将字符串分解成自己能明白的树结构,生成的代码会返回给你astnode。如例子所示会转化成以tok_select标记为根结点的树,树的叶子结点ProjectionList子树(dept,count(*)),FROM子树、等等。AST会把一个长字符串转化成树结构,树本身的结构设计取决于你的语法定义,ANTLR会按照你的定义把树排列好。遍历完整个ast,hive把它关心的信息分类组织排列到一个结构,但是还没有进行元信息绑定和检查整理(语义分析)。

2)然后进行元数据绑定,首先调用MetastoreClient接口从hive元数据库中查询到相关的元信息。所谓元信息最基本地包含了表的schema,比如id是Integer类型,dept是string类型,(比如我们用的sum)和UDF等等这些信息都会注入本次之行Hive的符号解析空间。

3)然后Hive对Projection列表中的表达式进行解析,首先是dept,Hive会去搜索刚才提到的符号解析空间,找到了DEPT代表源表中的一个字段,类型是String。这里Hive看到一个函数调用,于是它搜索了函数表,找到了sum函数,并看到它是一个聚合函数(sum不是简单的一行数据能独立计算的函数,需要整个组一起算),这里就先标记起来,后面还有相关的语义检查。而继续遍历sum的子树又发现了一个salary列引用表达式,一样做一次解析找到salary列定义是Integer。最后是Group by检查,根据SQL语义,出现在聚合函数外的字段引用必须出现在Group By中,于是Hive开始检查sum(SALARY)之外的列引用,发现了DEPT,然后遍历Group By的列表进行匹配,发现所有非聚合列都已经在GROUP BY中定义了,于是Hive很满意,继续执行下去了。到这里为止Hive得到了一个语义正确的SQL查询信息结构。

4)逻辑执行计划可以简单地认为就是,按照顺序在单机上跑是能跑出结果的一个计算计划。接下去Hive会对执行计划进行优化比如列剪枝和分区优化。然后进行物理执行计划,物理执行计划才是真正映射底层计算引擎的计算策略。sum比较特殊,sum括号中表达式需要在mapper中计算,并在本地进行累加(combiner中根据分组进行本地求和)然后分发到reducer进行最终的累加。因此AggregationOperator在物理执行计划产生的时候会拆解成两部分,一部分是PartialAggregation对应Mapper端的Sum,一部分是FinalAggregation对应Reducer端的最终Sum。

二、导致数据倾斜的原因

sql执行的数据很慢大本分是由于数据倾斜导致的,那么什么会导致数据倾斜。

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

三、性能调优的工具

性能调优工具:explain与analyze还有日志

1、explain语句使用

explain [formatted|extended|dependency|authorization] hql_query 一个典型的执行计划主要包括三部分:

ast:抽象语法树。
stage dependencies:会列出运行查询所有的依赖以及stage的数量
stage plans:包含了重要信息,比如运行作业时的operator 和sort orders

4个可选的关键字具体含义如下:

formatted:对执行进行格式化,返回json格式的执行计划
extended:提供一些额外的信息,比如文件路径信息
dependency:以json格式返回查询所依赖的表和分区的列表
authorization:列出需要被授权的条目,包括输入和输出。

2、analyze语句使用

analyze语句可以收集一些详细的统计信息。比如表的行数、文件数、数据的大小。这些统计信息作为元数据存储在hive的元数据库中。

analyze table user compute statistics   		#收集表的统计信息,当制定noscan关键字时,会忽略扫描文件内容,仅仅统计文件的数量和大小。
analyze table user partition()				#收集分区表的统计信息
analyze table user partition()for columns user_id	#收集表的某个字段的信息
set hive.stats.autogather=true 				#会自动收集统计信息

统计信息收集完毕,可以通过describe extended/formatted语句来查询统计信息

四、作业执行优化

1、本地模式

当hive处理数据量较小的时候启动分布式处理数据事件比较长

set hive.exec.mode.local.auto=true; 			-- 默认 false
set hive.exec.mode.local.auto.inputbytes.max=50000000;
set hive.exec.mode.local.auto.input.files.max=5; 	-- 默认 4

作业输入的文件大小小于50000000,配置map任务的数量小于5,配置reduce任务数量是1或者0会启动本地

2、jvm重用

当hadoop运行几s的任务的时候启用jvm耗费的时间会比作业执行时间长。可以启用jvm串行的执行map或者reduce。jvm的重用适用于同一个作业的map和reduce,对于不同作业的task不能够共享jvm。如果要开启jvm重用,需要配置一个作业最大task数量,默认值为1,如果设置为-1,则表示不限制:

set mapreduce.job.jvm.numtasks=5;

缺点:开启之后会一直占用task插槽,以便进行重用直到任务完成后才能释放。如果某个不平衡的job中有某几个reduce task执行的时间要比其他Reduce task消耗的时间多的多的话,那么保留的插槽就会一直空闲着却无法被其他的job使用,直到所有的task都结束了才会释放。

3、并行执行

Hive的查询通常会被转换成一系列的stage,这些stage之间并不是一直相互依赖的,所以可以并行执行这些stage,可以通过下面的方式进行配置:

set hive.exec.parallel=true; 			-- 默认false
set hive.exec.parallel.thread.number=16; 	-- 默认8

并行执行可以增加集群资源的利用率,如果集群的资源使用率已经很高了,那么并行执行的效果不会很明显。

4、Fetch模式

Fetch模式是指Hive中对某些情况的查询可以不必使用MapReduce计算。可以简单地读取表对应的存储目录下的文件,然后输出查询结果到控制台。在开启fetch模式之后,在全局查找、字段查找、limit查找等都启动mapreduce,通过下面方式进行配置:

set hive.fetch.task.conversion=more

5、join优化

1)hive新版已经优化对大小表之间的join

2)map端join 可以先将小表加载至内存进行map端的join

set hive.auto.convert.join=true; 				--  hivev0.11.0之后默认true
set hive.mapjoin.smalltable.filesize=600000000; 		-- 默认 25m
set hive.auto.convert.join.noconditionaltask=true; 		-- 默认true,所以不需要指定map join hint
set hive.auto.convert.join.noconditionaltask.size=10000000; 	-- 控制加载到内存的表的大小

一旦开启map端join配置,hive会自动检测小表是否大于smalltable.filesize大小,小于的话进行map端join

3)分桶join

set hive.auto.convert.join=true;
set hive.optimize.bucketmapjoin=true; -- 默认false

参与的表必须是分桶表(通过clustered by指定)大表分桶的数量必须是小表分桶数量的倍数 分桶join还有Sort merge bucket (SMB) join、Sort merge bucket map (SMBM) join

4)当数据及其不均衡的时候,会造成数据倾斜现象,可以通过如下配置

set hive.optimize.skewjoin=true;		-- 默认false,如果数据倾斜,可以将其设置为true
set hive.skewjoin.key=100000;			-- 默认为100000,如果key的数量大于配置的值,则超过的数量的key对应的数据会被发送到其他的reduce任务

5)group by的情况下也会发生数据倾斜

set hive.groupby.skewindata=true		-- 优化group by

出现的数据倾斜,一旦开启之后,执行作业时会首先额外触发一个mr作业,该作业的map任务的输出会被随机地分配到reduce任务上,从而避免数据倾斜

6、执行引擎

Hive支持多种执行引擎,比如spark、tez。对于执行引擎的选择,会影响整体的查询性能。使用的配置如下:

set hive.execution.engine=<engine>; -- <engine> = mr|tez|spark

7、优化器

向量化优化器会同时处理大批量的数据,而不是一行一行地处理。要使用这种向量化的操作,要求表的文件格式为ORC,配置如下

set hive.vectorized.execution.enabled=true;			-- 默认 false

成本优化器Hive的CBO是基于apache Calcite的,Hive的CBO通过查询成本(有analyze收集的统计信息)会生成有效率的执行计划,最终会减少执行的时间和资源的利用,使用CBO的配置如下:

set hive.cbo.enable=true; 					--从 v0.14.0默认true
set hive.compute.query.using.stats=true; 			-- 默认false
set hive.stats.fetch.column.stats=true; 			-- 默认false
set hive.stats.fetch.partition.stats=true; 			-- 默认true

五、存储优化

1、文件格式

hive支持textfile、sequencefile、avro、rcfile、orc以及parquet文件 可以通过两种方式指定表的文件格式:

create table ... store as:		即在建表时指定文件格式,默认是textfile
alter table set fieformat:		修改具体表的文件格式

如果要改变创建表的默认文件格式,可以使用hive.default.fileformat=进行配置,改配置可以针对所有表。同时也可以使用hive.default.fileformat.managed =进行配置,改配置仅适用于内部表或外部表。

TEXT, SEQUENCE和 AVRO文件是面向行的文件存储格式,不是最佳的文件格式,因为即便是只查询一列数据,使用这些存储格式的表也需要读取完整的一行数据。另一方面,面向列的存储格式(RCFILE, ORC, PARQUET)可以很好地解决上面的问题。关于每种文件格式的说明,如下:

TEXTFILE:创建表时的默认文件格式,数据被存储成文本格式。文本文件可以被分割和并行处理,也可以使用压缩,比如GZip、LZO或者Snappy。然而大部分的压缩文件不支持分割和并行处理,会造成一个作业只有一个mapper去处理数据,使用压缩的文本文件要确保文件的不要过大,一般接近两个HDFS块的大小。
SEQUENCEFILE:key/value对的二进制存储格式,sequence文件的优势是比文本格式更好压缩,sequence文件可以被压缩成块级别的记录,块级别的压缩是一个很好的压缩比例。如果使用块压缩,需要使用下面的配置:set hive.exec.compress.output=true; set io.seqfile.compression.type=BLOCK
AVRO:二进制格式文件,除此之外,avro也是一个序列化和反序列化的框架。avro提供了具体的数据schema。
RCFILE:全称是Record Columnar File,首先将表分为几个行组,对每个行组内的数据进行按列存储,每一列的数据都是分开存储,即先水平划分,再垂直划分。
ORC:全称是Optimized Row Columnar,从hive0.11版本开始支持,ORC格式是RCFILE格式的一种优化的格式,提供了更大的默认块(256M)
PARQUET:另外一种列式存储的文件格式,与ORC非常类似,与ORC相比,Parquet格式支持的生态更广,比如低版本的impala不支持orc格式。

2、压缩

压缩技术可以减少map与reduce之间的数据传输,从而可以提升查询性能,关于压缩的配置可以在hive的命令行中或者hive-site.xml文件中进行配置

set hive.exec.compress.intermediate=true

3、存储优化

HDSF本身提供了应对小文件的解决方案:

Hadoop Archive/HAR:将小文件打包成大文件
SEQUENCEFILE格式:将小文件压缩成大文件
CombineFileInputFormat:在map和reduce处理之前组合小文件
HDFS Federation:HDFS联盟,使用多个namenode节点管理文件

对于Hive而言,可以使用下面的配置将查询结果的文件进行合并,从而避免产生小文件:

hive.merge.mapfiles: 在一个仅有map的作业中,合并最后的结果文件,默认为true
hive.merge.mapredfiles:合并mapreduce作业的结果小文件 默认false,可以设置true
hive.merge.size.per.task:定义合并文件的大小,默认 256,000,000,即256MB
hive.merge.smallfiles.avgsize: T触发文件合并的文件大小阈值,默认值是16,000,000

当一个作业的输出结果文件的大小小于hive.merge.smallfiles.avgsize设定的阈值,并且hive.merge.mapfiles与hive.merge.mapredfiles设置为true,Hive会额外启动一个mr作业将输出小文件合并成大文件。

六、hql优化的具体例子

1)慎用count(distinct col)

原因distinct会将b列所有的数据保存在内存中,形成一个类似hash的结构,速度快,但是大数据背景下,因为b列所有值会形成key值会造成oom 解决方法:可以使用group by或者row_number()over()

2)小文件造成资源多度占用以及影响查询速度

原因:小文件在hdfs存储中会占用过多的内存空间,mr查询过程中也会生成过多的mapper,每一个mapper都是一个后台线程,会占用jvm的空间 动态分区会产生零碎的小文件 不合理的reduce task数量的设置也会造成小文件生成,因为reduce会落地hdfs中 解决:采用Sequencefile作为表存储格式,不要用textfile,在一定程度上可以减少小文件(常见于在流计算的时候采用Sequencefile格式进行存储) 减少reduce的数量 数据校检工作,脚本方式检测hive表的文件数量,进行文件的合并 合并多个文件到一个文件,重新构建表

3)处理掉字段中带有null值的数据

原因:一个表内有很多null值时会导致mapreduce过程中,空会形成一个key,对应的会有大量的value值,而一个key的value可能会造成reduce造成内存不足

解决方法:用随机值或者过滤掉null值

4)设置合理的reduce个数

原因:过多的启动和初始化reduce也会消耗时间和资源,也可能会产生小文件 解决方法:每个Reduce处理的数据默认是256MB

hive.exec.reducers.bytes.per.reducer=256000000

每个任务最大的reduce数,默认为1009

hive.exec.reducers.max=1009

计算reduce数的公式N=min(参数2,总输入数据量/参数1)设置Reducer的数量

set mapreduce.job.reduces=n

5)为什么任务只有一个reduce

原因:使用了order by(全局排序)、使用了count(1)没有加group by、有笛卡尔积操作

解决办法:避免order by 使用sort by进行局部排序

6)选择使用严格模式

set hive.mapred.mode=strict

防止用户执行产生意想不到的结果影响查询。必须有分区、使用order by必须加limit

六、工作中遇到的问题

1、某个reduce task,卡在99.9%半天不动或者是任务超时被杀掉

原因:大key导致数据join数据倾斜,在join的时候join的表其中一个key上有几亿个数据 判断:某个reduce的运行时间比其他reduce的时间长很多 通过任务的counter的判断,counter会记录整个job以及每个task的统计记录。 找到对应大key:1、找到任务特别慢的task、搜索日志中出现的rows for joinkey ,时间跨度比较长的记录 2、通过jobname确定stage,一般通过hive默认jobname会带上stage阶段,如下为stage-1这时候,需要参考该SQL的执行计划。或者借助日志搜索 “CommonJoinOperator: JOIN struct”。Hive在做join的时候,会把join的key打印到日志中。然后根据执行阶段确定sql 解决:1、过滤点脏数据。2、数据预处理 3、增加reduce个数 4、如果一个表是小表可以转化成mapjoin 5、大的key单独处理 6、调整内存设置适用于那些由于内存超限内务被kill掉的场景。通过加大内存起码能让任务跑起来,不至于被杀掉。该参数不一定会降低任务执行时间。如:setmapreduce.reduce.memory.mb=5120 ;setmapreduce.reduce.java.opts=-Xmx5000M -XX:MaxPermSize=128m; 7、

1、某个reduce task,卡在99.9%半天不动或者是任务超时被杀掉