数据仓库Hive
Hive 优化
Hive的存储层依托于HDFS,Hive的计算层依托于MapReduce,一般Hive的执行效率主要取决于SQL语句的执行效率,因此Hive优化的核心思想是MapReduce的优化
Fetch抓取
理论上来说,所有的SQL语句都需要转换成MapReduce操作,只不过Hive在转换SQL语句的过程中会做部分优化,使某些简单的操作不再需要转换成MapReduce。Fetch抓取是指,Hive中对某些情况的查询可以不必使用MapReduce计算,例如针对单表的查询
启用MapReduce Job是会消耗系统开销的。对于这个问题,从 Hive0.10.0 版本开始,对于简单的不需要聚合的类似 select <col> from <table> limit n
语句,不需要起MapReduce job,直接通过Fetch task获取数据
在Hive中hive.fetch.task.conversion
参数控制是否通过Fetch task获取数据,此值默认是more,针对单表的全局查找、字段查找、limit查找都不执行MapReduce。比如:select * from user_table
在这种情况下,Hive可以简单地读取user_table对应的存储目录下的文件,然后输出查询结果到控制台
如果将hive.fetch.task.conversion
修改成none,然后执行查询语句,则都会执行MapReduce程序
-- 不会执行MapReduce
select * from person2;
set hive.fetch.task.conversion = none;
-- 执行如下查询,会使用MapReduce处理
select * from person2;
-- 恢复初始配置
set hive.fetch.task.conversion=more;
本地模式运行
类似于MapReduce的操作,Hive的运行也分为本地模式和集群模式。0.7版本后Hive开始支持任务执行选择本地模式(local mode)。大多数的Hadoop job是需要hadoop提供的完整的可扩展性来处理大数据的。不过,有时hive的输入数据量是非常小的。在这种情况下,为查询触发执行任务的时间消耗可能会比实际job的执行时间要多的多。对于大多数这种情况,hive可以通过本地模式在单台机器上处理所有的任务。对于小数据集,执行时间会明显被缩短
在开发和测试阶段为了加快查询速度我们可以使用Hive本地模式来查询SQL,以及验证SQL语句是否正确
-- 执行如下SQL,MR会在Yarn上运行
select count(*) from person2;
-- 查看本地模式默认值,默认为false
set hive.exec.mode.local.auto;
-- 设置本地默认为true
set hive.exec.mode.local.auto=true;
-- 执行如下SQL,此时MR会在本地运行
select count(*) from person2;
-- 恢复初始配置
set hive.exec.mode.local.auto=false;
Hive本地模式执行的SQL不能在Yarn的WebUI中看到,因为没有在Yarn上执行。对于小数据集hive通过本地模式在单机上处理任务,执行时间可以明显被缩短。本地模式有下面两个限制
设置local mr的最大输入数据量,当输入数据量小于这个值时采用local mr的方式,默认134217728也就是128M。若大于该配置仍会以集群方式来运行,hive.exec.mode.local.auto.inputbytes.max=134217728
设置local mr的最大输入文件个数,当输入文件个数小于这个值时采用local mr方式,默认是4,若大于该配置仍会以集群方式来运行,hive.exec.mode.local.auto.input.files.max=4
并行模式
hive.exec.parallel参数控制在同一个sql中的不同的job是否可以同时运行,默认为false。有时候一个SQL中包含很多子SQL,这些子SQL执行时没有依赖关系,可以并行执行,可以设置参数hive.exec.parallel=true来让这些子SQL并行执行
-- 执行如下SQL ,两个关联子SQL没有关系,可以看出执行时,两个子JOB按照顺序执行
select a.id,a.age1,b.age2 from (select id,sum(age) as age1 from student_info group by id) a,(select id,sum(age) as age2 from student_info group by id) b where a.id = b.id;
-- 设置并行执行
set hive.exec.parallel=true;
-- 再次执行如下SQL ,观察SQL执行,两个子JOB并行执行
select a.id,a.age1,b.age2 from (select id,sum(age) as age1 from student_info group by id) a,(select id,sum(age) as age2 from student_info group by id) b where a.id = b.id;
-- 恢复初始配置
set hive.exec.parallel=false;
在资源充足的时候hive.exec.parallel会让那些存在并发job的sql运行得更快,但同时消耗更多的资源
Hive的并行度并不是无限增加的,在一次SQL执行中,可以通过hive.exec.parallel.thread.number
参数来设置并行job的最大数量,该参数默认为8
严格模式与非严格模式
hive提供了一个严格模式,可以防止用户执行那些可能产生意想不到的不好的效果的查询。即某些查询在严格模式下无法执行。通过设置hive.mapred.mode的值为strict,可以禁止三种类型的查询
1.查询一个分区表时
如果在一个分区表执行hive,除非where语句中包含分区字段过滤条件来显示数据范围,否则不允许执行。换句话说,就是用户不允许扫描所有的分区。进行这个限制的原因是,通常分区表都拥有非常大的数据集,而且数据增加迅速。 如果没有进行分区限制的查询可能会消耗令人不可接受的巨大资源来处理这个表
select * from person30;
1 zs 2021-10-01
2 ls 2021-10-01
3 ww 2021-10-02
4 ml 2021-10-02
-- 设置严格模式
set hive.mapred.mode=strict;
-- 再次查询,不指定分区会报错
select * from person30;
FAILED: SemanticException [Error 10056]: Queries against partitioned tables without a partition filter are disabled for safety reasons...
-- 必须在where中指定分区列
select * from person30 where log_dt= '2021-10-01';
1 zs 2021-10-01
2 ls 2021-10-01
2.带有order by的查询
对于使用了order by的查询,要求必须有limit语句。因为order by为了执行排序过程会将所有的结果分发到同一个reducer中进行处理,强烈要求用户增加这个limit语句可以防止reducer额外执行很长一段时间
-- 开启严格模式之后,查询中加入limit才可以正常执行
select * from person2 order by id;
FAILED: SemanticException 1:31 Order by-s without limit are disabled for safety reasons. ...
select * from person2 order by id limit 10;
1 小明1 ["lol","book","movie"] {"beijing":"xisanqi","shanghai":"pudong"}
3.限制笛卡尔积的查询
Hive中如果出现笛卡尔积查询,如果表足够大,那么这个查询就会出现不可控的情况,所以开启严格模式会限制使用笛卡尔积查询
select * from person2 a join person2 b;
FAILED: SemanticException Cartesian products are disabled for safety reasons. ...
-- 恢复初始配置
set hive.mapred.mode=nostrict;
注意:这里的静态模式(hive.mapred.mode)与动态分区中的静态模式(hive.exec.dynamic.partition.mode)不是一个概念
Hive排序
用Hive编写SQL时经常会使用到排序,Hive中支持的排序方式有4种
-
order by
-
sort by
-
distribute by
-
cluster by
准备数据(employ_info.txt)
1,1001,Jack01,5000
1,1002,Jack02,5001
1,1003,Jack03,5002
1,1004,Jack04,5003
1,1005,Jack05,5004
1,1006,Jack06,5005
1,1007,Jack07,5006
1,1008,Jack08,5007
1,1009,Jack09,5008
1,1010,Jack10,5009
1,1011,Jack11,5010
1,1012,Jack12,5011
2,1013,Maria01,7500
2,1014,Maria02,7501
2,1015,Maria03,7502
2,1016,Maria04,7503
2,1017,Maria05,7504
2,1018,Maria06,7505
2,1019,Maria07,7506
2,1020,Maria08,7507
2,1021,Maria09,7508
3,1022,Lucy01,8540
3,1023,Lucy02,8541
3,1024,Lucy03,8542
3,1025,Lucy04,8543
3,1026,Lucy05,8544
3,1027,Lucy06,8545
3,1028,Lucy07,8546
3,1029,Lucy08,8547
3,1030,Lucy09,8548
3,1031,Lucy10,8549
3,1032,Lucy11,8550
3,1033,Lucy12,8551
4,1034,Jimmy01,10000
4,1035,Jimmy02,10001
4,1036,Jimmy03,10002
4,1037,Jimmy04,10003
4,1038,Jimmy05,10004
4,1039,Jimmy06,10005
4,1040,Jimmy07,6300
4,1041,Jimmy08,6301
4,1042,Jimmy09,6302
4,1043,Jimmy10,6303
4,1044,Jimmy11,6304
4,1045,Jimmy12,6305
5,1046,Ace01,6306
5,1047,Ace02,9700
5,1048,Ace03,9701
5,1049,Ace04,9702
5,1050,Ace05,9703
5,1051,Ace06,9704
5,1052,Ace07,9705
-- 创建表并导入数据
create table employ_info(dept_id int,employ_id int,employ_name string,employ_salary double) row format delimited fields terminated by ',';
load data local inpath '/home/bigdata/employ_info.txt' into table employ_info;
order by
order by 会对输入做全局排序,为了保证数据全局有序,所以只有一个reducer,也正因为只有一个reducer,所以当输入的数据规模较大时会导致计算时间较长。生产环境中一般不会使用order by
如果设置了严格模式参数set hive.mapred.mode=strict,order by 后必须跟上limit操作,防止返回数据量过大无法输出结果
select * from employ_info order by employ_id limit 2;
1 1001 Jack01 5000.0
1 1002 Jack02 5001.0
sort by
sort by作用是对每个reducer内部进行排序,即部分排序,对全局结果集来说不是排序
sort by不是全局排序,其在数据进入reducer前完成排序。因此,如果用sort by进行排序,并且设置mapred.reduce.tasks>1(通过在Hive中 set mapred.reduce.tasks=xx 来设置),则sort by只保证每个reducer的输出有序,不保证全局有序
注意:可以用limit子句大大减少数据量,使用limit n后,传输到每个reduce端的数据记录数就减少到n*(map个数)。否则由于数据过大可能出不了结果
-- mapred.reduce.tasks默认是1,不修改这个配置,sort by的执行效果与order by是一样的,下面的查询结果employ_id全局有序
select * from employ_info sort by employ_id desc;
-- 设置reduce task个数
set mapred.reduce.tasks=3;
-- 执行如下语句,结果employ_id不是全局有序
select * from employ_info sort by employ_id desc;
-- 可以通过以下命令,将结果导入到文件中,会生成3个文件,可以看到每个文件中员工id都是降序排序
insert overwrite local directory '/home/bigdata/result' row format delimited fields terminated by '\t' select * from employ_info sort by employ_id desc;
sort by 配合limit使用,结果也是准确的,但是可以并行运行。而order by 不可以并行运行
distribute by
distribute by可以保证相同key的记录被划分到一个Reduce 中
一般使用distribute by 时,都会结合sort by 使用,将相同的key分到同一个reduce中后,再对当前key列或者其他列使用sort by 局部排序
使用distribute by时,必须通过 set mapred.reduce.tasks=xx 设置多个reduce task,否则看不到distribute by的效果
-- 设置2个reduce task
set mapred.reduce.tasks=2;
-- 执行如下查询,将同一个部门的用户按照薪资倒序排列,distribute by 相当于分组
select * from employ_info distribute by dept_id sort by employ_salary desc;
3 1033 Lucy12 8551.0
3 1032 Lucy11 8550.0
3 1031 Lucy10 8549.0
3 1030 Lucy09 8548.0
...
-- 将查询结果导入到文件中方便查看结果
insert overwrite local directory '/home/bigdata/result2' row format delimited fields terminated by '\t' select * from employ_info distribute by dept_id sort by employ_salary desc;
上面reduce task的数量是2,一共5个部门,一个reduce处理3个部门,另外一个reduce处理2个部门
# 查看/home/bigdata/result2
# 2个部门的数据
cat /home/bigdata/result2/000000_0
4 1039 Jimmy06 10005.0
4 1038 Jimmy05 10004.0
...
2 1021 Maria09 7508.0
2 1020 Maria08 7507.0
...
4 1045 Jimmy12 6305.0
4 1044 Jimmy11 6304.0
...
# 3个部门的数据
cat /home/bigdata/result2/000001_0
5 1052 Ace07 9705.0
5 1051 Ace06 9704.0
...
3 1033 Lucy12 8551.0
3 1032 Lucy11 8550.0
...
5 1046 Ace01 6306.0
1 1012 Jack12 5011.0
1 1011 Jack11 5010.0
...
cluster by
当distribute by 和sort by字段相同时,可以使用cluster by方式代替
cluster by = distribute by + sort by ,使用cluster by 时排序只能是升序排序,如果需要降序排序还需写成 distribute by col1 sort by col1 desc 方式
-- 以下两种写法等价
select * from employ_info cluster by dept_id;
select * from employ_info distribute by dept_id sort by dept_id;
分区剪裁和列剪裁
分区剪裁:只拿需要的分区、用什么分区过滤什么分区
列剪裁:只拿需要的列,用什么列就过滤什么列
在分区剪裁中,当使用外关联时,如果将副表的过滤条件写在Where后面,那么就会先全表关联,之后再过滤
select a.id,a.name,a.age,b.score from student_info as a join student_score as b on a.id = b.id where b.id < 3;
-- 以上SQL可以优化成下面的样子
select a.id,a.name,a.age,b.score from student_info as a join (select id,score from student_score where student_score.id < 3) b on a.id = b.id ;
推测执行
HQL底层会转换成MapReduce任务执行,MapReduce将作业分解成多个任务并行运行的机制,决定了作业运行的总体时间对运行缓慢的任务比较敏感。为了尽量避免运行缓慢的任务对作业运行时间“托后腿”的情况,需要启动作业的推测执行。
推测执行是根据一定的法则推测出“拖后腿”的任务,并为这样的任务启动一个备份任务,让该任务和原始任务同时处理“同一份”数据,并最终选择最先成功完成任务的计算结果作为最终结果
-- 推测执行设置如下,默认就是true,可以不用设置
set hive.mapred.reduce.tasks.speculative.execution=true;
HiveSQL优化
Hive在多个表的join操作时尽可能多的使用相同的连接键,这样在转换MR任务时会转换成少的MR的任务
Hive 小表 join 大表优化
在Hive中小表 join 大表可以采用map join 优化查询速度。那么什么是map join呢?除了map join 还有common join,下面分别解释common join和map join
common join
我们知道Hive编写SQL语句,Hive会将SQL解析成MapReduce任务。对于一个简单的关联查询,CommonJoin任务涉及Map阶段和Reduce阶段
Mapper 从连接表中读取数据并将连接的 key 和连接的 value 键值对输出到中间文件中。Hadoop 在所谓的 shuffle 阶段对这些键值对进行排序和合并
Reducer 将排序结果作为输入,并进行Join。Shuffle 阶段代价非常昂贵,因为它需要排序和合并。因此减少 Shuffle 和 Reduce 阶段的代价可以提高任务性能
map join
MapJoin 的目的是减少 Shuffle和 reducer阶段的代价,并仅在 Map 阶段进行Join。其原理如下:MapJoin 会把小表全部读入内存中,在map阶段直接拿另外一个表的数据和内存中表数据做匹配,由于在map是进行了join操作,省去了reduce 阶段,运行的效率就会高很多
map join的使用场景
并不是所有的场景都适合用MapJoin,它通常会用存在如下的一些情景:在两个要连接的表中,有一个很大,有一个很小,这个小表可以存放在内存中而不影响性能。这样我们就把小表文件复制到每一个Map任务的本地,再让Map把文件读取内存中待用
如何开启Hive map join
在Hive中可以通过参数hive.auto.convert.join
来控制是否使用map join ,在Hive0.7版本之后,hive.auto.convert.join
参数默认为true,Hive中开启了自动map join
也可以手动map join,必须先关闭自动map join(set hive.auto.convert.join=false)
-- 是否开启自动map join,默认为true,如果需要手动map join必须将该参数设置为false
set hive.auto.convert.join=true;
-- 是否忽略mapjoin hint,默认为true,如果需要手动map join必须将该参数设置为false
set hive.ignore.mapjoin.hint=true;
-- 在SQL语句中添加MapJoin标记(mapjoin hint),进行手动map join,自动map join必须处于关闭状态,该hint才有效
SELECT /*+ MAPJOIN(smallTable) */ smallTable.key, bigTable.value
FROM smallTable JOIN bigTable ON smallTable.key = bigTable.key;
-- 那么多大的表算小表,可以通过以下参数设置,默认25M,当表的大小小于该阈值时该表可以被全部加载到内存中,来进行自动或手动map join,当表的大小超过该阈值时,不管是自动还是手动的map join都不会执行(执行正常的join)
set hive.mapjoin.smalltable.filesize=25000000;
注意:为了自动map join,写SQL时要把小表放在左边,hive一般会把左表当成小表(不同的版本可能会不一样)
Hive 大表join大表优化
Hive 两张大表进行join时,效率低下,可以通过以下几点来进行优化
1.打散join关联key,增加并行度
如果两张表进行join,都比较大,没有所谓小表之分,效率低下,可能会出现某些key对应的记录过多,这时只是增加并行度不能解决问题,可以将关联key随机加前缀打散后进行关联,然后对关联结果再去前缀进行聚合一次,获取最终结果
2.空key过滤
有时join超时是因为某些key对应的数据太多,而相同key对应的数据都会发送到相同的reducer上,从而导致内存不够。此时我们应该仔细分析这些异常的key,很多情况下,这些key对应的数据是异常数据,我们需要在SQL语句中进行过滤
3.空key转换
有时虽然某个key为空对应的数据很多,但是相应的数据不是异常数据,必须要包含在join的结果中,此时我们可以为表a中key为空的字段赋一个随机的值,使得数据随机均匀地分不到不同的reducer上
Map side 预聚合
默认情况下,Map阶段相同key分发到一个reduce,当某个key的数据过大时就会发生数据倾斜,并不是所有的聚合都需要在reduce端完成,可以先在map端进行聚合,最后再在reduce端聚合,类似于MR的combine操作
-- 开启map端预聚合,默认就是true
set hive.map.aggr=true;
-- map端group by执行聚合时处理的多少行数据(默认:100000)
set hive.groupby.mapaggr.checkinterval=100000;
-- 进行聚合的最小比例,默认0.5(预先对100000条数据做聚合,若聚合之后的数据量/100000的值大于该配置0.5,则不会聚合)
set hive.map.aggr.hash.min.reduction=0.5;
-- hive在启用combiner时,会尝试拉取hive.groupby.mapaggr.checkinterval这个配置对应的数据量进行聚合,将聚合后的数据量除以聚合前的数据量,如果小于hive.map.aggr.hash.min.reduction这个配置则不会聚合(map端聚合也是消耗资源的,但是没有产生多大的收益,则关闭)
-- map端聚合使用的内存的最大值,默认0.5(即map端最大内存的50%)
set hive.map.aggr.hash.percentmemory=0.5
Map side 预聚合也可以用来解决数据倾斜的问题,例如当某个key对应的数据特别多时,在map端预聚合,可以大幅减轻reduce端的压力
还有一个配置hive.groupby.skewindata
,是否对GroupBy产生的数据倾斜做优化,也适用于distinct查询(可以看成GroupBy查询),默认为false。如果设置为true,生成的查询计划会有两个MRJob。第一个MRJob 中,Map的输出结果会随机分布到Reduce中(即job中分区是随机的,是通过给key增加一个随机数实现的,也可以手动通过rand()函数实现),每个Reduce做部分聚合操作,并输出结果,这样处理的结果是相同的GroupBy Key有可能被分发到不同的Reduce中,从而达到负载均衡的目的;第二个MRJob再根据预处理的数据结果按照GroupBy Key分发到Reduce中,即第二个MRJob中分区是按照GroupBy的Key分区的,这就保证了整体的GroupBy没有问题,相同的key分到了同一个Reduce中。可以结合"Map side 预聚合"一起使用,这样经过前面几个聚合的局部聚合(第一个MRJob的部分聚合和Map端的预聚合),这个时候的数据量已经大大减少了,在最后一个reduce里进行最后的整体聚合。对数据倾斜的优化也会带来额外的开销,在实际使用时,需要根据数据倾斜的实际情况来定
count(distinct )去重优化
由于COUNT DISTINCT操作需要用一个Reduce Task来完成,这个Reduce需要处理的数据量太大,就会导致整个Job很难完成。如果数据量小无所谓,数据量大时COUNT DISTINCT一般会先使用GROUP BY再COUNT的方式替换
select count(distinct dept_id) as cnt from employ_info;
-- 数据量大时,上面的sql应该转化为下面的形式
select count(dept_id) from (select dept_id from employ_info group by dept_id) tmp;
转换之后的SQL会多出一个job进行计数,但是当数据量过大时,转换之后的SQL速度提升很明显
避免笛卡尔积
尽量避免笛卡尔积,即避免join的时候不加on条件,或者无效的on条件。Hive只使用1个reduce来完成笛卡尔积,所以效率特别低
合理设置MR数量
合理设置MapTask数量
通常情况下,MapReduce作业会通过input的目录产生一个或者多个map任务,map task个数与input文件总个数、输入文件大小、设置的block块大小有关
-- 设置hadoop中的切片大小,计算公式是Math.max(minSize, Math.min(maxSize, blockSize)),最终的切片数量等于map的数量。map数量无法直接设置,只能通过设置切片大小来间接增加或减少map数量。注意每个文件的切片数量是单独计算的,不可以放在一起计算,这也是执行MR前需要合并小文件的原因
-- 最小切片大小,默认1B
set mapred.min.split.size=1;
-- 最大切片大小,默认256MB
set mapred.max.split.size=256000000;
-- 块大小,默认128Mb
set dfs.blocksize=134217728
问题一:是不是map task个数越多越好?
不是,如果一个任务有很多小文件,则每个小文件都会被当成一个split切片,用一个map任务来完成,执行业务逻辑运算的时间远远小于map任务的启动和初始化的时间,会造成很大的资源浪费。另外,同时可执行的map数也是受限的
如何优化:减少map的数量,比如通过合并小文件减少map数量,可以手动合并,也可以通过hive自动合并
-- 一个节点上split的最小值,默认为1B
set mapred.min.split.size.per.node=1;
-- 一个机架上split的最小值,默认为1B
set mapred.min.split.size.per.rack=1;
-- map输入格式化,可以在执行map前合并小文件
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
mapred.min.split.size.per.node
和mapred.min.split.size.per.rack
参数一般和配置切片大小的参数一起使用,用来自动合并map端输入的小文件。例如,这两个参数都配置成100000000(上面的其它参数都采用默认配置),则此时MR执行时切片的大小是256MB,当一个节点(node)和机架(rack)上的输入文件大小小于配置的值100MB时,则会进行合并,合并的上限是切片的大小256MB
问题二:如何减少输出小文件个数?
小文件数目多,容易在文件存储端造成压力,给hdfs造成压力,影响效率。可以在Hive中设置如下属性
-- 是否合并map输出文件,默认为true
set hive.merge.mapfiles=true;
-- 是否合并reduce输出文件,默认为false
set hive.merge.mapredfiles=false;
-- 合并文件的大小,默认为256000000
set hive.merge.size.per.task=256000000
-- 当一个作业的输出文件平均大小小于这个数字时,Hive将启动一个额外的MR作业,将输出文件合并为更大的文件。如果hive.merge.mapfiles为true,则对map作业输出执行此操作,如果hive.merge.mapredfiles为true,则对reduce输出执行此操作
set hive.merge.smallfiles.avgsize=16000000;
这里只能减少MR产生的中间文件和最终输出文件的数量,Map端的输入文件如果有很多的小文件(问题一),应该提前通过手动操作hdfs合并这些小文件、或者通过hive自动合并这些小文件
问题三 : 是不是保证每个map 处理接近128M 的文件块,就高枕无忧了?
不一定,比如一个128MB(或者接近该值)的文件,默认情况会用一个map去完成,但是这个文件可能只有很少的小字段,却有几千万的记录,如果map处理的逻辑比较复杂,用一个map任务去做,肯定比较耗时,如何解决?当然是增加map的数量,在Hive中可以通过设置分片(切分数据)大小来控制map task个数
合理设置ReduceTask数量
可以通过以下参数来设置Hive转换成MapReduce任务对应的ReduceTask数量
-- 强制指定reduce任务的数量,默认为-1
set mapred.reduce.tasks=5;
-- 设置每个reduce处理的数据量,默认为256000000(256MB)
set hive.exec.reducers.bytes.per.reducer=256000000;
-- 每个任务最大的reduce数,默认为1009
set hive.exec.reducers.max=1009;
在Hive中,如果没有指定reduce数量(mapred.reduce.tasks),计算ReduceTask数量公式是num = min(hive.exec.reducers.max, 总输入数据量/hive.exec.reducers.bytes.per.reducer)
总结:reduce数量并不是越多越好,过多的启动和初始化reduce也会消耗时间和资源;另外过多的reduce会生成很多个结果文件,同样产生了小文件的问题。在极端情况下,如果某个key的数据非常多,此时设置ReduceTask数量没有用(同一个key只能由一个reduce处理),需要通过手动或自动的方式将同一个key打散到不同的reduce处理,然后再将第一步处理的结果再执行一次MR得到最终的结果
jvm重用
JVM重用是Hadoop调优参数的内容,其对Hive的性能具有非常大的影响,特别是对于很难避免小文件的场景或task特别多的场景,这类场景大多数执行时间都很短。Hadoop的默认配置通常是使用派生JVM来执行map和reduce任务的。这时JVM的启动过程可能会造成相当大的开销,尤其是执行的job包含有成百上千个task任务的情况。JVM重用可以使得JVM实例在同一个job中重新使用N次。N的值可以在Hadoop的mapred-site.xml文件中进行永久设置
<property>
<name>mapreduce.job.jvm.numtasks</name>
<value>10</value>
<description>每个jvm要运行多少任务。如果设置为-1,则没有限制
</description>
</property>
也可以在hive中执行sql之前,临时设置set mapreduce.job.jvm.numtasks=10;
在hadoop2.x版本中可以查到该配置的默认值是1,在hadoop3.x版本中查不到默认值,不知道是不是使用方式变化了???
这个功能的一个缺点是,开启JVM重用将会一直占用使用到的task插槽,以便进行重用,直到任务完成后才能释放。如果某个“不平衡的”的job中有某几个reduce task执行的时间要比其他reduce task消耗的时间多得多的话,那么保留的插槽就会一直空闲着却无法被其他的job使用,直到所有的task都结束了才会释放
JVM重用,类似于数据库连接池的概念,数据库连接池中连接过多则浪费资源(资源无法释放),过少则会造成资源争抢(排队)。JVM重用,重用次数过少则会造成资源浪费(资源无法释放),重用次数过多则会造成资源争抢(排队)