Hive 调优总结

前言

调优须知

  1. Hive 一个常用的大数据组件,影响它的性能的从来都不是因为数据量过大的问题,而是数据倾斜,冗余,Job 和 IO 过多,MapReduce 分配不合理···等等

所以我们可以从它的 建表设计,HiveSQL优化,组件配置参数和底层的 MapReduce 这几个方面进行调整,总之就是在保证业务结果不变的前提下,尽可能降低资源的损耗及任务的运行时间。

  1. HiveSQL 底层会转换层多个 Job 并行串行执行,Job 数较多的作业自然会慢,就算你的表数据量很小,由于 MapReduce 作业初始化的时间十分长,所以它耗时也会很长

  2. Hive 进行大数据分析时,mapper 阶段的局部聚合和两阶段聚合可以解决常见的类似于 sum ,count,max/min UDAF 所造成的数据倾斜问题,当然如果是 join 的情况就会更为复杂

  3. 好的建表设计也算是一种优化

  4. 设置一个合理的 Task 并行度可以有效提升性能

  5. 直接了解数据分布,针对性处理也是不错的方法

  6. 数据量很大的情况使用 count(distinct),group by 很容易出问题,但是 group by 和聚合类操作一起使用的话,问题不会很大。

  7. 对小文件合并,还有调优手段本身就是针对资源不足的情况下产生的,所以,一切的调优手段都比不上直接加资源来的最简单粗暴,要是直接能加机器,那就别想太多了😂

一、建表设计层面

1.1 分区表与分桶表

我们都听过内部表,外部表,分区表,分桶表,内部表和外部表在性能上是没有什么区别的,分区表就是在某几个维度对数据进行分类存储,一个分区对应一个目录,当你查询的时候带有分区字段,那么 Hive 只需要遍历对应的分区目录(其实就是一个 HDFS 的目录)即可。这样属于减少了处理的数据量,例如

select ··· where day = '昨天'

我们的 Hive 表很多都是按天进行分区的,但假如当时建的不是分区表,那我们查询的时候,就还是会遍历所有的数据,然后筛选出昨天产生的数据,但是如果是分区表的话,它查询的时候就直接去昨天的文件夹那里拿了。

所以一张表如果大部分情况下都是使用这个字段进行 where 过滤的话,记得将此表改成分区表

分桶和分区的概念稍稍有些不一样,它是将数据以指定列的值作为 key 进行 Hash 到指定数目的桶中。道理也是避免遍历所有的数据,例如

select a.*,b.* from a join b on a.id = b.id

此时如果a,b为两张大表这个任务的执行效率势必会成为问题,还有可能存在数据倾斜。此时如果a表的 id 经常用于做 join 查询,那此时就应该把 a 表设计为分桶表,并把 id 作为分桶字段。

而且这样可以方便之后对数据进行采样

这里顺带提一下最近兴起的 Doris ,它也是一个 OLAP 引擎,它里面就会有一个 colocation join,上面的例子中,分桶其实隐藏了两个条件,一个是a,b表都必须是分桶表,还有就是它俩的分桶数量必须成倍数关系。而 Doris 中则要求它们的 分桶数量 相等,这样它能保证 两张表相同分桶编号的数据,在同一个节点上

顺带一提 Doris 的 SQL 的执行效率是会比 Hive 高很多的。且常见的 OLAP 引擎一般会存在下面的优势:列存储 + 范围分区 + 排序 + 索引 + 压缩 + 预结算 + 数据倾斜解决方案

1.2 合适的存储格式

Hive 默认存储格式为 TextFile,例如 student(id,name,sex,department····100个字段)

select department,count(*) as total from student group by department

此时我们就会发现,其实我们根本就没用到除了 department 以外的属性,那此时如果我们是列存储的话,就可以避免每次读取一条数据时都附带上了 student 所有的字段。那此时如果我们是使用 100 个文件来分别存储 student 表的 100 个属性,就可以解决我们的问题了。

所以在遇到宽表时,尽量使用 orc,parquet 这两种列式存储格式,因为列式存储的表每一列的数据在物理层面上是存储在一起的。这样能只处理 1/100 的数据量

1.3 合适的压缩格式

压缩算法需要判断当前任务为 IO 密集型,还是计算密集型。如果是 IO 密集型的任务,大量的资源用在这块的话,压缩才可以考虑。当然我们这时候会用 CPU 的资源去换网络资源,所以我们还必须保证这时候的 CPU 资源够用才行。如果是计算密集型的任务,那就不能再给 CPU 加重负担了。

压缩算法的选择就从 压缩率,压缩速率,是否可拆分 来判断。一个文件,被压缩之后会变成类似于这样的格式

header + body

header 是存放元数据信息的,而 body 是真实数据,header 会记录数据压缩前后的大小及压缩的算法···等相关信息,只有这样在解压缩的时候才知道使用哪种算法来解。而可拆分和不可拆分就是指这个 header 的元数据信息,在原来的真实数据被切分之后,会不会给每个切分出来的块都保留一份 header 。以避免之后无法进行解压缩的问题

压缩格式是否可拆分是否自带压缩率速度是否 Hadoop 自带
gzip很高比较快
lzo比较高很快
snappy比较高很快
bzip2最高
1.3.1 压缩使用

Job 输出文件按照 Block 以 GZip 的方式进行压缩:

## 默认值是false
set mapreduce.output.fileoutputformat.compress=true;   

## 默认值是Record
set mapreduce.output.fileoutputformat.compress.type=BLOCK 

## 默认值是org.apache.hadoop.io.compress.DefaultCodec
set mapreduce.output.fileoutputformat.compress.codec=org.apache.hadoop.io.compress.G
zipCodec

Map 输出结果也以 Gzip 进行压缩:

## 启用map端输出压缩
set mapred.map.output.compress=true
## 默认值是org.apache.hadoop.io.compress.DefaultCodec
set mapreduce.map.output.compress.codec=org.apache.hadoop.io.compress.GzipCodec

对 Hive 输出结果和中间都进行压缩:

set hive.exec.compress.output=true  ## 默认值是false,不压缩
set hive.exec.compress.intermediate=true   ## 默认值是false,为true时MR设置的压缩才启用

二、HQL 语法和运行参数层面

2.1 查看 Hive 执行计划

需要了解具体的转换过程,可以在 SQL 语句中输入如下命令查看具体的执行计划

## 查看执行计划,添加extended关键字可以查看更加详细的执行计划
explain [extended] query

2.2 列裁剪和分区裁剪

这其实就是查询时只读取需要的列,分区裁剪就是只读取需要的分区。所以尽量不要 select * ,还有就是要指定分区

Hive 在读数据的时候,可以只读取查询中所需要用到的列,而忽略其他的列。这样做可以节省读取开销:中间表存储开销和数据整合开销。    

set hive.optimize.cp = true; ## 列裁剪,取数只取查询中需要用到的列,默认是true

分区裁剪就是只读取需要的分区。

set hive.optimize.pruner=true;   ## 默认是true

在 HiveQL 解析阶段对应的则是 ColumnPruner 逻辑优化器。

2.3 谓词下推

很多人不了解这个概念,我来帮助大家理解一下,例如

bcaf6790e17ca10e9f438370a57f5555.png

这个场景,很明显我们在每个节点的时候就应该针对 where 进行过滤,减少节点传输给客户端的数据量,因为传输是需要网络资源的。比如4个节点计算了 1W 个学生出来,经过客户端 where 之后只得出了1个25岁以上的,那就白白传输了9999条结果,这么一想,是不是就很亏

所以什么叫谓词下推,在例子中的体现就是指这个过滤行为,下发到计算节点来进行操作。例如 HBase 的协处理器,工作模式也是一个谓词下推。

将 SQL 语句中的 where 谓词逻辑都尽可能提前执行,减少下游处理的数据量。对应逻辑优化 PredicatePushDown。

set hive.optimize.ppd=true;   ## 默认是true

示例:

select a.*, b.* from a join b on a.id = b.id where b.age > 20;

// 这条就预先把 b 表的满足 age>20 的条件先筛选出来为一个c表再进行聚合
// 这是一种手动谓词下推
select a.*, c.* from a join (select * from b where age > 20) c on a.id = c.id;

2.4 合并小文件

2.4.1 Map 输入合并

在执行 MapReduce 程序的时候,一般情况是一个文件的一个数据分块需要一个 mapTask 来处理。但是如果数据源是大量的小文件,这样就会启动大量的 mapTask 任务,这样会浪费大量资源。可以将输入的小文件进行合并,从而减少 mapTask 任务数量。

如果你要合并,那就设置

set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;

当然你也可以不合并

set hive.input.format=org.apache.hadoop.hive.ql.io.HiveInputFormat;

请结合自身场景考虑,Hive 的默认为 TextInputFormat

2.4.2 Map/Reduce 输出合并

大量的小文件会给 HDFS 带来压力,影响处理效率。可以通过合并 Map 和 Reduce 的结果文件来消除影响。

## 是否合并Map输出文件, 默认值为true
set hive.merge.mapfiles=true;

## 是否合并Reduce端输出文件,默认值为false
set hive.merge.mapredfiles=true;

## 合并文件的大小,默认值为256M
set hive.merge.size.per.task=256000000;

## 每个Map 最大分割大小
set mapred.max.split.size=256000000; 

## 一个节点上split的最少值
set mapred.min.split.size.per.node=1;  // 服务器节点

## 一个机架上split的最少值
set mapred.min.split.size.per.rack=1;   // 服务器机架

set mapred.min.split.size.per.node=1 之后,如果当前节点有 300 个 1M 的小文件,指定的切片大小为 256M ,那剩余的 44M 就会传输到另外的节点做合并,但是如果节点存在 500 个 1M 的小文件,剩余还有 244M ,此时就 set mapred.min.split.size.per.node=2 这样会比较合理一点了。

2.5 合理设置 MapTask 并行度

基于当前 Mapper 阶段计算的数据来源有可能是原始数据,也有可能是上一个 Map 的输出结果。我们得分开情况讨论,首先是原始数据,这个是相对比较好控制的。

当输入文件特别大,MapTask 特别多,每个计算节点分配执行的 MapTask 都很多,这时候可以考虑减少 MapTask 的数量。增大每个 MapTask 处理的数据量。而且 MapTask 过多,最终生成的结果文件数也太多。当输入文件都很大,任务逻辑复杂,MapTask 执行非常慢的时候,可以考虑增加 MapTask 数,来使得每个 MapTask 处理的数据量减少,从而提高任务的执行效率。

我们知道,一个MapReduce Job 的 MapTask 数量是由输入分片 InputSplit 决定的。而输入分片是由 FileInputFormat.getSplit() 决定的。一个输入分片对应一个 MapTask,而输入分片是由三个参数决定的:

参数默认值意义
dfs.blocksize128MHDFS默认数据块大小
mapreduce.input.fileinputformat.split.minsize1最小分片大小(MR)
mapreduce.input.fileinputformat.split.maxsize256M最大分片大小(MR)

如果对这块不了解的伙计们,我之后也会在另一篇文章补充说明。针对原始数据的情况 输入分片大小 的计算是这么计算出来的:

long splitSize = Math.max(minSize, Math.min(maxSize, blockSize))

而且调整的 splitSize 大小最好是 blockSize 的整数倍,不然就会出现大量的刚刚提到的传输到另外的节点做合并的情况    默认情况下,输入分片大小和 HDFS 集群默认数据块大小一致,也就是默认一个数据块,启用一个MapTask 进行处理,这样做的好处是避免了服务器节点之间的数据传输,提高 job 处理效率

2.5.1 合理控制 MapTask 数量

1、如果想增加 MapTask 个数,可以设置 mapred.map.tasks 为一个较大的值

2、如果想减少 MapTask 个数,可以设置 maperd.min.split.size 为一个较大的值

3、如果输入是大量小文件,想减少 mapper 个数,可以通过设置 hive.input.format 合并小文件

MapReduce 中提供了如下参数来控制 map 任务个数,

输入文件总大小:total_size  

HDFS 设置的数据块大小:dfs_block_size   

default_mapper_num = total_size / dfs_block_size

从字面上看,貌似是可以直接设置 MapTask 个数的样子,但是很遗憾不行,这个参数设置只有在大于 default_mapper_num 的时候,才会生效。

set mapred.map.tasks=10; ## 默认值是2

那如果我们需要减少 MapTask 数量,但是文件大小是固定的,那该怎么办呢?

可以通过  mapred.min.split.size 设置每个任务处理的文件的大小,这个大小只有在大于dfs_block_size 的时候才会生效

split_size = max(mapred.min.split.size, dfs_block_size)

split_num = total_size / split_size

map_num = Math.min(split_num, Math.max(default_mapper_num,
mapred.map.tasks))

2.6 合理设置 ReduceTask 并行度

如果 ReduceTask 数量过多,一个 ReduceTask 会产生一个结果文件,这样就会生成很多小文件,那么如果这些结果文件会作为下一个 Job 的输入,则会出现小文件需要进行合并的问题,而且启动和初始化ReduceTask 需要耗费资源。

如果 ReduceTask 数量过少,这样一个 ReduceTask 就需要处理大量的数据,并且还有可能会出现数据倾斜的问题,使得整个查询耗时长。Hive 怎样决定 ReducerTask 个数成为一个关键问题。遗憾的是 Hive 的估计机制很弱,不指定 ReducerTask 个数的情况下,Hive 会猜测确定一个 ReducerTask 个数

// 每个 reduceTask 处理的最大数据大小
参数1:hive.exec.reducers.bytes.per.reducer (默认256M)

// reduceTask 的个数上限
参数2:hive.exec.reducers.max (默认为1009)

参数3:mapreduce.job.reduces (默认值为-1,表示没有设置,那么就按照以上两个参数
进行设置)

如果你设置了 mapreduce.job.reduces ,那在没有 order by 的场景中 ReducerTask 就是你设置的个数,不然 reduceTask 为

reduceTask_num = Math.min(参数2,总输入数据大小 / 参数1)

依据经验,可以将 参数2 设定为 M * (0.95 * N) (N为集群中 NodeManager 个数)。一般来说,NodeManage 和 DataNode 的个数是一样的。0.95 是给予一定的容错,可能会出现节点宕机的情况

2.7 Join 优化

2.7.1 整体原则

1、优先过滤后再进行 join 操作,最大限度的减少参与 join 的数据量

我们必须尽量减少每个阶段的数据量,对于分区表能用上分区字段的尽量使用,同时只选择后面需要使用到的列,最大限度的减少参与 Join 的数据量。

2、小表 join 大表,最好启动 mapjoin,hive 自动启用 mapjoin, 小表不能超过25M,可以更改

小表 join 大表的时应遵守小表 join 大表原则,原因是 join 操作的 reduce 阶段,位于 join 左边的表内容会被加载进内存,将条目少的表放在左边,可以有效减少发生内存溢出的几率。join 中执行顺序是从左到右生成 Job,应该保证连续查询中的表的大小从左到右是依次增加的。

3、Join on的条件相同的话,最好放入同一个 job,并且 join 表的排列顺序从小到大

select a.*,b.*, c.* from a join b on a.id = b.id 
       join c on a.id = c.i

4、如果多张表做 join, 如果多个链接条件都相同,会转换成一个 job

在 hive 中,当对 3 个或更多张表进行 join 时,如果 on 条件使用相同字段,那么它们会合并为一个MapReduce Job,利用这种特性,可以将相同的 join on 放入一个 job 来节省执行时间。

尽量避免一个SQL包含复杂的逻辑,如果遇到了,那可以使用中间表来完成复杂的逻辑。

如果是大表 join 大表的情况,我们也有以下两种措施

1、空 key 过滤:有时 join 超时是因为某些 key 对应的数据太多,而相同key对应的数据都会发送到相同的 reducer 上,从而导致内存不够。此时我们应该仔细分析这些异常的 key,很多情况下,这些key对应的数据是异常数据,我们需要在 SQL 语句中进行过滤。

2、空 key 转换:有时虽然某个 key 为空对应的数据很多,但是相应的数据不是异常数据,必须要包含在 join 的结果中,此时我们可以表a中 key 为空的字段赋一个随机的值,使得数据随机均匀地分不到不同的 reducer 上

2.7.2 MapJoin

MapJoin 是将 join 双方比较小的表直接分发到各个 map 进程的内存中,在 map 进程中进行 join 操作,这样就不用进行 reduce 步骤,从而提高了速度。只有 join 操作才能启用 MapJoin。

## 是否根据输入小表的大小,自动将reduce端的common join 转化为map join,将小表刷入内存中。
## 对应逻辑优化器是MapJoinProcessor
## 这个参数其实就是一个开关
set hive.auto.convert.join = true;

## 刷入内存表的大小(字节)
## 也就是小表的大小最大为25M,最大一般我们可以调整到2G
set hive.mapjoin.smalltable.filesize = 25000000;

## hive会基于表的size自动的将普通join转换成mapjoin
set hive.auto.convert.join.noconditionaltask=true;

## 多大的表可以自动触发放到内层LocalTask中,默认大小10M
## 这个是一个单机模式的大小判断,因为我们会有些任务是单机模式效率更高的
set hive.auto.convert.join.noconditionaltask.size=10000000;

也可以自己手动开启 mapjoin,通过一个特定语法实现

/*+mapjoin(smalltable)*/

SELECT  /*+ MAPJOIN(smallTable) */ smallTable.key, bigTable.value FROM
    smallTable  JOIN bigTable  ON smallTable.key = bigTable.key;
2.7.3 Sort-Merge-Bucket(SMB) Map Join

使用这个技术的前提是所有的表都必须是分桶表和分桶排序的。总结就是以下2个要求

1、针对参与join的这两张做相同的hash散列,每个桶里面的数据还要排序

2、这两张表的分桶个数要成倍数。

## 当用户执行bucket map join的时候,发现不能执行时,禁止查询
set hive.enforce.sortmergebucketmapjoin=false; 

## 如果join的表通过sort merge join的条件,join是否会自动转换为sort merge join
set hive.auto.convert.sortmerge.join=true;

## 当两个分桶表 join 时,如果 join on的是分桶字段,小表的分桶数是大表的倍数时,可以启用
mapjoin 来提高效率。
# bucket map join优化,默认值是 false
set hive.optimize.bucketmapjoin=false; 

## bucket map join 优化,默认值是 false
set hive.optimize.bucketmapjoin.sortedmerge=false;

2.8 Join 数据倾斜优化

在编写 join 查询语句时,如果确定是由于 join 出现的数据倾斜,可以做如下设置:

# key 对应的记录条数超过这个值则会进行处理
set hive.skewjoin.key = 100000;  

# 功能开关
set hive.optimize.skewjoin = false;

如果开启了,在 Join 过程中 Hive 会将计数超过阈值 hive.skewjoin.key(默认100000)的倾斜 key 对应的行写进临时文件中,然后再启动另一个 job 做 map join 生成结果。

通过  hive.skewjoin.mapjoin.map.tasks 参数还可以控制第二个 job 的 mapper 数量,默认10000。

set hive.skewjoin.mapjoin.map.tasks=10000;

2.9 CBO 优化

CBO,成本优化器,代价最小的执行计划就是最好的执行计划。传统的数据库,成本优化器做出最优化的执行计划是依据统计信息来计算的。Hive 的成本优化器也一样。join 的时候表的顺序的关系:前面的表都会被加载到内存中。后面的表进行磁盘扫描

Hive 自 0.14.0 开始,加入了一项 "Cost based Optimizer" 来对 HQL 执行计划进行优化,这个功能通过 "hive.cbo.enable" 来开启。在 Hive 1.1.0 之后,这个 feature 是默认开启的,它可以自动优化 HQL中多个 Join 的顺序,并选择合适的 Join 算法。

当你要做那种连结条件相同的表和表 join ,例如

select a.*, b.*, c.* from a join b on a.id = b.id join c on a.id = c.id;

如果你要使用这个功能,可以把下面的参数都打开即可

set hive.cbo.enable=true;
set hive.compute.query.using.stats=true;
set hive.stats.fetch.column.stats=true;
set hive.stats.fetch.partition.stats=true;

2.10 怎样做笛卡尔积

当 Hive 设定为严格模式(hive.mapred.mode=strict)时,不允许在 HQL 语句中出现笛卡尔积

2.11 Group By 优化

默认情况下,Map 阶段同一个 Key 的数据会分发到一个 Reduce 上,当一个 Key 的数据过大时会产生数据倾斜。进行 group by 操作时可以从以下两个方面进行优化

2.11.1 Map 端部分聚合

事实上并不是所有的聚合操作都需要在 Reduce 部分进行,很多聚合操作都可以先在 Map 端进行部分聚合,然后在 Reduce 端的得出最终结果。

## 开启 Map 端聚合参数设置
set hive.map.aggr=true;

## 设置 map 端预聚合的行数阈值,超过该值就会拆分job,默认值100000
set hive.groupby.mapaggr.checkinterval=100000
2.11.2 有数据倾斜时进行负载均衡

当 HQL 语句使用 group by 时数据出现倾斜时,如果该变量设置为 true,那么 Hive 会自动进行负载均衡。策略就是把 MapReduce 任务拆分成两个:第一个先做预汇总,第二个再做最终汇总

# 自动优化,有数据倾斜的时候进行负载均衡(默认是false)
set hive.groupby.skewindata=false;

当选项设定为 true 时,生成的查询计划有两个 MapReduce 任务。

1、在第一个 MapReduce 任务中,map 的输出结果会随机分布到 reduce 中,每个 reduce 做部分聚合操作,并输出结果,这样处理的结果是相同的group by key有可能分发到不同的 reduce 中,从而达到负载均衡的目的;

2、第二个 MapReduce 任务再根据预处理的数据结果按照 group by key 分布到各个 reduce 中,最后完成最终的聚合操作。

看起来好像很复杂,实际画个图就很容易去理解这个两阶段聚合了

2b5abba80f9d0d5528bcfa0265a1a51f.png

经过这个操作,就算还存在数据倾斜,也基本不会过分严重了。

2.12 Order By优化

order by 只能是在一个 reduce 进程中进行,所以如果对一个大数据集进行 order by ,会导致一个reduce 进程中处理的数据相当大,造成查询执行缓慢。

1、在最终结果上进行 order by,尽量不要在中间的大数据集上进行排序。如果最终结果较少,可以在一个reduce 上进行排序时,那么就在最后的结果集上进行 order by。

2、如果是取排序后的前N条数据,可以使用 distribute by 和 sort by 在各个 reduce 上进行排序后前N条,然后再对各个 Reduce 的结果集合合并后在一个 Reduce 中全局排序,再取前N条,因为参与全局排序的order by的数据量最多是 Reduce 个数 * N,所以执行效率会有很大提升。

前面提到的都很容易理解。还有在Hive中,关于数据排序,提供了四种语法,一定要区分这四种排序的使用方式和适用场景。这里就不再展开了

1、order by:全局排序,缺陷是只能使用一个reduce
2、sort by:单机排序,单个reduce结果有序
3、cluster by:对同一字段分桶并排序,不能和sort by连用
4、distribute by + sort by:分桶,保证同一字段值只存在一个结果文件当中,结合sort by保证每个reduceTask结果有序

2.13 Count Distinct 优化

当要统计某一列去重数时,如果数据量很大,count(distinct) 就会非常慢,原因与 order by 类似,count(distinct) 逻辑只会有很少的 reducer 来处理。这时我们可以用 group by 来改写

优化前 ,一个普通的只使用一个reduceTask来进行count(distinct) 操作

-- 优化前(只有一个reduce,先去重再count负担比较大):
select count(distinct id) from student;

// 优化后
select count(1) from (select id from student group by id) tmp;

但是这样写会启动两个MR job(单纯 distinct 只会启动一个),所以要确保数据量大到启动 job 的 overhead 远小于计算耗时,才考虑这种方法。当数据集很小或者 key 的倾斜比较明显时,group by 还可能会比 distinct 慢。

2.14 in/exists

在Hive的早期版本中,in/exists语法是不被支持的,但是从 hive-0.8x 以后就开始支持这个语法。但是不推荐使用这个语法。虽然经过测验,Hive-2.3.6 也支持 in/exists 操作,但还是推荐使用 Hive 的一个高效替代方案:left semi join,这里就不展开了

2.15 使用 vectorization 技术(知道有这么回事就好了)

在计算类似 scan, filter, aggregation 的时候, vectorization 技术以设置批处理的增量大小为 1024 行单次来达到比单条记录单次获得更高的效率

set hive.vectorized.execution.enabled=true ;
set hive.vectorized.execution.reduce.enabled=true;

Hive 中真的很多只要把开关打开就可以自己帮你工作的东西,hhh

2.16 多重模式

多重插入就是一次读取,多次插入,有些场景是从一张表读取数据后,要多次利用,这时可以使用 multi insert 语法。但是 multi insert语法有一些限制。

1、一般情况下,单个SQL中最多可以写128路输出,超过128路,则报语法错误。

2、在一个multi insert中:对于分区表,同一个目标分区不允许出现多次。对于未分区表,该表不能出现多次。

3、对于同一张分区表的不同分区,不能同时有insert overwrite和insert into操作,否则报错返回

还有 Multi-Group by 这些,有兴趣的可以去了解下

2.17 压缩

2.17.1 map 输出压缩
set mapreduce.map.output.compress=true;
set mapreduce.map.output.compress.codec=org.apache.hadoop.io.compress.SnappyCodec;
2.17.2 中间数据压缩

中间数据压缩就是对 hive 查询的多个 Job 之间的数据进行压缩。最好是选择一个节省 CPU 耗时的压缩方式。可以采用 snappy 压缩算法,该算法的压缩和解压效率都非常高。

set hive.exec.compress.intermediate=true;
set hive.intermediate.compression.codec=org.apache.hadoop.io.compress.SnappyCodec;
set hive.intermediate.compression.type=BLOCK;
2.17.3 结果数据压缩

最终的结果数据也可以进行压缩,可以选择一个可以减少数据的大小和数据的磁盘读写时间;注:常用的 gzip,snappy 压缩算法是不支持并行处理的,如果数据源是 gzip/snappy压缩文件大文件,这样如果有个 mapper 来处理这个文件,会严重影响查询效率。所以如果结果数据需要作为其他查询任务的数据源,可以选择支持 splitable 的 LZO 算法,这样既能对结果文件进行压缩,还可以并行的处理,这样就可以大大的提高 job 执行的速度了。

set hive.exec.compress.output=true;
set mapreduce.output.fileoutputformat.compress=true;
set mapreduce.output.fileoutputformat.compress.codec=org.apache.hadoop.io.compress.G
zipCodec;
set mapreduce.output.fileoutputformat.compress.type=BLOCK;
2.17.4 Hadoop集群支持的压缩算法
org.apache.hadoop.io.compress.DefaultCodec
org.apache.hadoop.io.compress.GzipCodec
org.apache.hadoop.io.compress.BZip2Codec
org.apache.hadoop.io.compress.DeflateCodec
org.apache.hadoop.io.compress.SnappyCodec
org.apache.hadoop.io.compress.Lz4Codec
com.hadoop.compression.lzo.LzoCodec
com.hadoop.compression.lzo.LzopCodec

三、Hive 架构层面

3.1 启用本地抓取

Hive 的某些 SQL 语句需要转换成 MapReduce 的操作,某些 SQL 语句就不需要转换成 MapReduce 操作,但是需要注意,理论上来说,所有的 SQL 语句都需要转换成 MapReduce 操作

只不过 Hive 在转换 SQL 语句的过程中会做部分优化,使某些简单的操作不再需要转换成 MapReduce,例如:

1、只是 select * 的时候
2、where 条件针对分区字段进行筛选过滤时
3、带有 limit 分支语句时

Hive 从 HDFS 中读取数据,有两种方式:启用MapReduce读取 和 直接抓取。直接抓取数据比 MapReduce 方式读取数据要快的多,但是只有少数操作可以使用直接抓取方式。可以通过 hive.fetch.task.conversion 参数来配置在什么情况下采用直接抓取方式

minimal:只有 select * 、在分区字段上 where 过滤、有 limit 这三种场景下才启用直接抓取方式。
more:在 select、where 筛选、limit 时,都启用直接抓取方式

set hive.fetch.task.conversion = minimal/more;

3.2 本地执行优化

Hive 在集群上查询时,默认是在集群上多台机器上运行,需要多个机器进行协调运行,这种方式很好的解决了大数据量的查询问题。

但是在 Hive 查询处理的数据量比较小的时候,其实没有必要启动分布式模式去执行,因为以分布式方式执行设计到跨网络传输、多节点协调等,并且消耗资源。对于小数据集,可以通过本地模式,在单台机器上处理所有任务,执行时间明显被缩短。

启动本地模式涉及到三个参数

## 打开hive自动判断是否启动本地模式的开关
set hive.exec.mode.local.auto=true;

## map任务数最大值,不启用本地模式的task最大个数
set hive.exec.mode.local.auto.input.files.max=4;

## map输入文件最大大小,不启动本地模式的最大输入文件大小
set hive.exec.mode.local.auto.inputbytes.max=134217728;

其实就是task小于4而且数据小于128M,那我就可以帮你执行本地模式跑

3.3 JVM重用

Hive 语句最终会转换为一系列的 MapReduce 任务,每一个MapReduce 任务是由一系列的 MapTask和 ReduceTask 组成的,默认情况下,MapReduce 中一个 MapTask 或者 ReduceTask 就会启动一个JVM 进程,一个 Task 执行完毕后,JVM 进程就会退出。这样如果任务花费时间很短,又要多次启动JVM 的情况下,JVM 的启动时间会变成一个比较大的消耗,这时,可以通过重用 JVM 来解决。

set mapred.job.reuse.jvm.num.tasks=5;

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

3.4 并行执行

有的查询语句,Hive 会将其转化为一个或多个阶段,包括:MapReduce 阶段、抽样阶段、合并阶段、limit 阶段等。默认情况下,一次只执行一个阶段。但是,如果某些阶段不是互相依赖,是可以并行执行的。多阶段并行是比较耗系统资源的。

一个 Hive SQL 语句可能会转为多个 MapReduce Job,每一个 job 就是一个 stage,这些 Job 顺序执行,这个在 cli 的运行日志中也可以看到。但是有时候这些任务之间并不是是相互依赖的,如果集群资源允许的话,可以让多个并不相互依赖 stage 并发执行,这样就节约了时间,提高了执行速度,但是如果集群资源匮乏时,启用并行化反倒是会导致各个 Job 相互抢占资源而导致整体执行性能的下降。启用并行化:

## 可以开启并发执行。
set hive.exec.parallel=true;

## 同一个sql允许最大并行度,默认为8。
set hive.exec.parallel.thread.number=16;

3.5 推测执行

在分布式集群环境下,因为程序Bug(包括Hadoop本身的bug),负载不均衡或者资源分布不均等原因,会造成同一个作业的多个任务之间运行速度不一致,有些任务的运行速度可能明显慢于其他任务(比如一个作业的某个任务进度只有50%,而其他所有任务已经运行完毕),则这些任务会拖慢作业的整体执行进度。为了避免这种情况发生,Hadoop 采用了推测执行(Speculative Execution)机制,它根据一定的法则推测出“拖后腿”的任务,并为这样的任务启动一个备份任务,让该任务与原始任务同时处理同一份数据,并最终选用最先成功运行完成任务的计算结果作为最终结果

# 启动mapper阶段的推测执行机制
set mapreduce.map.speculative=true;

# 启动reducer阶段的推测执行机制
set mapreduce.reduce.speculative=true;

如果用户对于运行时的偏差非常敏感的话,那么可以将这些功能关闭掉。如果用户因为输入数据量很大而需要执行长时间的MapTask或者ReduceTask的话,那么启动推测执行造成的浪费是非常巨大大。

3.6 Hive 严格模式

所谓严格模式,就是强制不允许用户执行有风险的 HiveQL 语句,一旦执行会直接失败。但是Hive中为了提高SQL语句的执行效率,可以设置严格模式,充分利用Hive的某些特点。

## 设置Hive的严格模式
set hive.mapred.mode=strict;

set hive.exec.dynamic.partition.mode=nostrict;

注意:当设置严格模式之后,会有如下限制:

1、对于分区表,必须添加 where 对于分区字段的条件过滤
    select * from student_ptn where age > 25
    
2、order by 语句必须包含 limit 输出限制
    select * from student order by age limit 100;

3、限制执行笛卡尔积的查询
    select a.*, b.* from a, b;

4、在hive的动态分区模式下,如果为严格模式,则必须需要一个分区列式静态分区

3.6 数据倾斜

数据倾斜可以参考之前的 Spark 文章中提到的内容 一文带你理清Spark Core调优的方方面面

Finally

年久失修的公众号大家可以关注一下,之后会逐渐恢复更新。希望我们都能有所收获

9859b743b6a7c4ae06ada69557647c48.jpeg        

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值