计算Job执行优化

一、Explain

1.1.功能

HiveQL是一种类SQL的语言,从编程语言规范来说是一种声明式语言,用户会根据查询需求提交声明式的HQL查询,而Hive会根据底层计算引擎将其转化成Mapreduce/Tez/Spark的 job。大多数情况下,用户不需要了解Hive内部是如何工作的,不过,当用户对于Hive

具有越来越多的经验后,尤其是需要在做性能优化的场景下,就要学习下Hive背后的理论知识以及底层的一些实现细节,会让用户更加高效地使用Hive。explain命令就可以帮助用户了解一条HQL语句在底层的实现过程,explain会解析HQL语句,将整个HQL语句的实现步骤、依赖关系、实现过程都会进行解析返回,可以帮助更好的了解一条HQL语句在底层是如何实现数据的查询及处理的过程,这样可以辅助用户对Hive进行优化。

官网:https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Explain

1.2.语法

常用语法命令如下:

EXPLAIN [FORMATTED|EXTENDED|DEPENDENCY|AUTHORIZATION|] query

说明:

  • FORMATTED:对执行计划进行格式化,返回JSON格式的执行计划
  • EXTENDED:提供一些额外的信息,比如文件的路径信息
  • DEPENDENCY:以JSON格式返回查询所依赖的表和分区的列表
  • AUTHORIZATION:列出需要被授权的条目,包括输入与输出

1.3 组成

解析后的执行计划一般由三个部分构成,分别是:

  • The Abstract Syntax Tree for the query
    • 抽象语法树:Hive使用Antlr解析生成器,可以自动地将HQL生成为抽象语法树
  • The dependencies between the different stages of the plan
    • Stage依赖关系:会列出运行查询所有的依赖以及stage的数量
  • The description of each of the stages
    • Stage内容:包含了非常重要的信息,比如运行时的operator和sort orders等具体的信息

1.4.示例1:过滤

执行SQL:

explain select count(*) from tb_orc_bloom
where track_time > '2017-08-10 13:00:01' and track_time < '2017-08-10 13:00:06'
and ip = '10.4.6.47' ;

组成说明:

解释:

1.5.示例2:分组排序

准备表,然后加载数据:

-- 创建普通表,因为.txt文件格式保存的数据只能加载到普通表中
CREATE TABLE orders_text (
    orderId bigint COMMENT ' 订单 id',
    orderNo string COMMENT ' 订单编号 ',
    shopId bigint COMMENT ' 门店 id',
    userId bigint COMMENT ' 用户 id',
    orderStatus tinyint COMMENT ' 订单状态 -3: 用户拒收 -2: 未付款的订单 -1 :用户取消 0: 待发货 1: 配送中 2: 用户确认收货 ',
    goodsMoney double COMMENT ' 商品金额 ',
    deliverMoney double COMMENT ' 运费 ',
    totalMoney double COMMENT ' 订单金额(包括运费) ',
    realTotalMoney double COMMENT ' 实际订单金额(折扣后金额) ',
    payType tinyint COMMENT ' 支付方式 ,0: 未知 ;1: 支付宝, 2 :微信 ;3 、现金; 4 、其他 ',
    isPay tinyint COMMENT ' 是否支付 0: 未支付 1: 已支付 ',
    userName string COMMENT ' 收件人姓名 ',
    userAddress string COMMENT ' 收件人地址 ',
    userPhone string COMMENT ' 收件人电话 ',
    createTime timestamp COMMENT ' 下单时间 ',
    payTime timestamp COMMENT ' 支付时间 ',
    totalPayFee int COMMENT ' 总支付金额 '
) 
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t';

-- 导入数据
LOAD DATA LOCAL INPATH '/tmp/hivedata/hive_orders.txt' INTO TABLE orders_text;


-- 创建订单表,采用orc存储格式,使用SNAPPY压缩方式
CREATE TABLE orders (
    orderId bigint COMMENT ' 订单 id',
    orderNo string COMMENT ' 订单编号 ',
    shopId bigint COMMENT ' 门店 id',
    userId bigint COMMENT ' 用户 id',
    orderStatus tinyint COMMENT ' 订单状态 -3: 用户拒收 -2: 未付款的订单 -1 :用户取消 0: 待发货 1: 配送中 2: 用户确认收货 ',
    goodsMoney double COMMENT ' 商品金额 ',
    deliverMoney double COMMENT ' 运费 ',
    totalMoney double COMMENT ' 订单金额(包括运费) ',
    realTotalMoney double COMMENT ' 实际订单金额(折扣后金额) ',
    payType tinyint COMMENT ' 支付方式 ,0: 未知 ;1: 支付宝, 2 :微信 ;3 、现金; 4 、其他 ',
    isPay tinyint COMMENT ' 是否支付 0: 未支付 1: 已支付 ',
    userName string COMMENT ' 收件人姓名 ',
    userAddress string COMMENT ' 收件人地址 ',
    userPhone string COMMENT ' 收件人电话 ',
    createTime timestamp COMMENT ' 下单时间 ',
    payTime timestamp COMMENT ' 支付时间 ',
    totalPayFee int COMMENT ' 总支付金额 '
) 
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
STORED AS orc tblproperties ("orc.compress"="SNAPPY");

-- 加载数据
INSERT INTO TABLE orders SELECT * FROM orders_text;

-- 查看数据
SELECT * FROM orders;

统计每个用户(userid)消费总金额超过 1000 的订单数量,并按订单数量降序排列。

explain SELECT userid,COUNT(*) num FROM orders WHERE totalMoney>1000
GROUP BY userid ORDER BY num DESC;

二、 MapReduce属性优化

2.1.本地模式

使用Hive的过程中,有一些数据量不大的表也会转换为MapReduce处理,提交到集群时,需要申请资源,等待资源分配,启动JVM进程,再运行Task,一系列的过程比较繁琐,本身数据量并不大,提交到YARN运行返回会导致性能较差的问题。
Hive为了解决这个问题,延用了MapReduce中的设计,提供本地计算模式,允许程序不提交给YARN,直接在本地运行,以便于提高小数据量程序的性能。

  • 配置
-- 开启本地模式
set hive.exec.mode.local.auto = true;

限制条件

Hive为了避免大数据量的计算也使用本地模式导致性能差的问题,所以对本地模式做了以下限制,如果以下任意一个条件不满足,那么即使开启了本地模式,将依旧会提交给YARN集群运行。

  • 处理的数据量不超过128M
  • MapTask的个数不超过4个
  • ReduceTask的个数不超过1个

2.2 JVM重用

  JVM正常指代一个Java进程,Hadoop默认使用派生的JVM来执行map-reducer,如果一个MapReduce程序中有100个Map,10个Reduce,Hadoop默认会为每个Task启动一个JVM来运行,那么就会启动100个JVM来运行MapTask,在JVM启动时内存开销大,尤其是Job大数据量情况,如果单个Task数据量比较小,也会申请JVM资源,这就导致了资源紧张及浪费的情况。
  为了解决上述问题,MapReduce中提供了JVM重用机制来解决,JVM重用可以使得JVM实例在同一个job中重新使用N次,当一个Task运行结束以后,JVM不会进行释放,而是继续供下一个Task运行,直到运行了N个Task以后,就会释放,N的值可以在Hadoop的mapred-site.xml文件中进行配置,通常在10-20之间。

配置

-- Hadoop3之前的配置,在mapred-site.xml中添加以下参数
-- Hadoop3中已不再支持该选项
mapreduce.job.jvm.numtasks=10

2.3.并行执行

Hive在实现HQL计算运行时,会解析为多个Stage,有时候Stage彼此之间有依赖关系,只能挨个执行,但是在一些别的场景下,很多的Stage之间是没有依赖关系的,例如Union语句,Join语句等等,这些Stage没有依赖关系,但是Hive依旧默认挨个执行每个Stage,这样会导致性能非常差,我们可以通过修改参数,开启并行执行,当多个Stage之间没有依赖关系时,允许多个Stage并行执行,提高性能。
配置:

-- 开启Stage并行化,默认为false
SET hive.exec.parallel=true;
-- 指定并行化线程数,默认为8
SET hive.exec.parallel.thread.number=16;

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

三、Join优化

3.1.Hive中的Join方案

表的Join是数据分析处理过程中必不可少的操作,Hive同样支持Join的语法,Hive Join的底层还是通过MapReduce来实现的,Hive实现Join时,为了提高MapReduce的性能,提供了多种Join方案来实现,例如适合小表Join大表的Map Join,大表Join大表的Reduce Join,以及大表Join的优化方案Bucket Join等。

3.2 Map Join

⑴.应用场景:

  • 适合于小表join大表或者小表Join小表

⑵.原理:

  • 将小的那份数据给每个MapTask的内存都放一份完整的数据,大的数据每个部分都可以与小数据的完整数据进行join
  • 底层不需要经过shuffle,需要占用内存空间存放小的数据文件

⑶.使用

  • 尽量使用Map Join来实现Join过程
  • Hive中默认自动开启了Map Join
-- 默认已经开启了Map Join
hive.auto.convert.join=true

⑷.Hive中判断哪张表是小表及限制

  • LEFT OUTER JOIN的左表必须是大表
  • RIGHT OUTER JOIN的右表必须是大表
  • INNER JOIN左表或右表均可以作为大表
  • FULL OUTER JOIN不能使用MAPJOIN
  • MAPJOIN支持小表为子查询
  • 使用MAPJOIN时需要引用小表或是子查询时,需要引用别名
  • 在MAPJOIN中,可以使用不等值连接或者使用OR连接多个条件
  • 在MAPJOIN中最多支持指定6张小表,否则报语法错误

⑸.Hive中小表的大小限制

-- 2.0版本之前的控制属性
hive.mapjoin.smalltable.filesize=25M
-- 2.0版本开始由以下参数控制
hive.auto.convert.join.noconditionaltask.size=512000000

3.3.Reduce Join

⑴.应用场景

  • 适合于大表Join大表

⑵.原理

  • 将两张表的数据在shuffle阶段利用shuffle的分组来将数据按照关联字段进行合并
  • 必须经过shuffle,利用Shuffle过程中的分组来实现关联

⑶.使用

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

3.4 Bucket Join

⑴.应用场景

适合于大表Join大表

⑵.原理

将两张表按照相同的规则将数据划分,根据对应的规则的数据进行join,减少了比较次数,提高了性能

⑶.使用

Bucket Join

语法:clustered by colName
参数

-- 开启分桶join
set hive.optimize.bucketmapjoin = true;

要求

  • 分桶字段 = Join字段 ,桶的个数相等或者成倍数
Sort Merge Bucket Join(SMB):基于有序的数据Join

语法:clustered by colName sorted by (colName)
参数

-- 开启分桶SMB join
set hive.optimize.bucketmapjoin = true;
set hive.auto.convert.sortmerge.join=true;
set hive.optimize.bucketmapjoin.sortedmerge = true;
set hive.auto.convert.sortmerge.join.noconditionaltask=true;

要求

  • 分桶字段 = Join字段 = 排序字段 ,桶的个数相等或者成倍数

四、优化器

4.1.关联优化

在使用Hive的过程中经常会遇到一些特殊的问题,例如当一个程序中如果有一些操作彼此之间有关联性,是可以放在一个MapReduce中实现的,但是Hive会不智能的选择,Hive会使用两个MapReduce来完成这两个操作。

例如:当我们执行以下SQL语句:

select …… from table group by id order by id desc;

该SQL语句转换为MapReduce时,我们可以有两种方案来实现:

方案一

  • 第一个MapReduce做group by,经过shuffle阶段对id做分组
  • 第二个MapReduce对第一个MapReduce的结果做order by,经过shuffle阶段对id进行排序

方案二

  • 因为都是对id处理,可以使用一个MapReduce的shuffle既可以做分组也可以排序

在这种场景下,Hive会默认选择用第一种方案来实现,这样会导致性能相对较差,我们可以在Hive中开启关联优化,对有关联关系的操作进行解析时,可以尽量放在同一个MapReduce中实现。

配置

set hive.optimize.correlation=true;

4.2 CBO优化器引擎

在使用MySQL或者Hive等工具时,我们经常会遇到一个问题,默认的优化器在底层解析一些聚合统计类的处理的时候,底层解析的方案有时候不是最佳的方案。

例如:当前有一张表【共1000条数据】,id构建了索引,id =100值有900条,我们现在的需求是查询所有id = 100的数据,所以SQL语句为:select * from table where id = 100;

由于id这一列构建了索引,索引默认的优化器引擎RBO,会选择先从索引中查询id = 100的值所在的位置,再根据索引记录位置去读取对应的数据,但是这并不是最佳的执行方案。有id=100的值有900条,占了总数据的90%,这时候是没有必要检索索引以后再检索

数据的,可以直接检索数据返回,这样的效率会更高,更节省资源,这种方式就是CBO优化器引擎会选择的方案。

使用Hive时,Hive中也支持RBO与CBO这两种引擎,默认使用的是RBO优化器引擎。

⑴.RBO

  • rule basic optimise:基于规则的优化器
  • 根据设定好的规则来对程序进行优化

⑵.CBO

  • cost basic optimise:基于代价的优化器
  • 根据不同场景所需要付出的代价来合适选择优化的方案
  • 对数据的分布的信息【数值出现的次数,条数,分布】来综合判断用哪种处理的方案是最佳方案

很明显CBO引擎更加智能,所以在使用Hive时,我们可以配置底层的优化器引擎为CBO引擎。

配置

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;

要求

  • 要想使用CBO引擎,必须构建数据的元数据【表行数、列的信息、分区的信息……】
  • 提前获取这些信息,CBO才能基于代价选择合适的处理计划
  • 所以CBO引擎一般搭配analyze分析优化器一起使用

五、谓词下推(PPD)

5.1.基本思想

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

也提升了任务的性能。

5.2.基本规则

开启参数

-- 默认自动开启谓词下推
hive.optimize.ppd=true;

不同Join场景下的Where谓词下推测试

试验结论

说明

  • Inner Join和Full outer Join,条件写在on后面,还是where后面,性能上面没有区别
  • Left outer Join时 ,右侧的表写在on后面,左侧的表写在where后面,性能上有提高
  • Right outer Join时,左侧的表写在on后面、右侧的表写在where后面,性能上有提高
  • 如果SQL语句中出现不确定结果的函数,也无法实现下推

六、Hive其他优化

6.1.Fetch抓取机制

Hive中对某些情况的查询可以不必使用MapReduce计算。例如:SELECT * FROM employees; 在这种情况下,Hive可以简单地读取employee对应的存储目录下的文件,然后输出查询结果到控制台。

在hive-default.xml.template文件中 hive.fetch.task.conversion 默认是more,老版本hive默认是 minimal,该属性修改为more以后,在全局查找、字段查找、limit查找等都不走mapreduce。

  • 把hive.fetch.task.conversion设置成none,然后执行查询语句,都会执行mapreduce程序。
set hive.fetch.task.conversion=none;
select * from log_text;
select ip from log_text;
select ip from log_text limit 3;
  • 把hive.fetch.task.conversion设置成more,然后执行查询语句,如下查询方式都不会执行mapreduce程序。
set hive.fetch.task.conversion=more;
select * from log_text;
select ip from log_text;
select ip from log_text limit 3;

6.2.推测执行计划

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

执行一个MR任务的同时,又开启了一个相同的MR任务共同执行(底层),其会根据Map阶段的执行结果选择一个最优的,然后再根据Reduce阶段执行结果选择一个最优的。

  • Reduce 3s
  • Reduce 7s

hadoop中默认两个阶段都开启了推测执行机制。

hive本身也提供了配置项来控制reduce-side的推测执行:

<property>
    <name>hive.mapred.reduce.tasks.speculative.execution</name>
    <value>true</value>
</property>

关于调优推测执行机制,还很难给一个具体的建议。如果用户对于运行时的偏差非常敏感的话,那么可以将这些功能关闭掉。如果用户因为输入数据量很大而需要执行长时间的map或者Reduce task的话,那么启动推测执行造成的浪费是非常巨大。

posted @ 2024-09-29 17:56  酒剑仙*  阅读(7)  评论(0编辑  收藏  举报