一. Hive性能调优综述
Hive的一般学习者和培训者在谈性能优化的时候一般都会从语法和参数这些雕虫小技的角度谈优化,而不会革命性的优化Hive的性能,产生这种现象的原因有:
-
- 历史原因和思维定势:大家学习SQL的时候一般都是单机Database,这个时候性能优化技巧确实主要是SQL语法和参数调优。
-
- Hive的核心性能问题往往是产生在超过规模的数据集,例如说100亿条级别的数据集,以及每天处理成千上万个Hive作业的情况下产生的。
要从根本上解决实际企业中Hive真正的性能优化问题,必须考虑到底什么是Hive性能的限制,按照优先级来描述如下:
-
1.战略性架构 解决海量数据下大量Job过于频繁的IO问题,而这个问题实质上涉及了架构方面的分表,数据复用以及分区表等调优的方式 补充: (1)海量的数据中有些数据是高频使用的数据,而有些数据是很少使用的,如果能够分离成不同的表,会极大的提高效率;很多作业可能会有共同点,抽离出来先进行计算并保留计算结果,后面的作业都可以复用;同时,底层的基础功能也可以先计算,在上层应用的时候直接拿数据结果,而不是每次都重复计算。 (2)合理使用静态分区表和动态分区表,可以避免数据全局扫描及计算资源更合理的利用 (3)数据倾斜的一站式解决方案
-
- 引擎和物理层面,很多内容都是普通Hive使用者不知道的 从Hive语法和Job内部角度进行优化,这要求MapReduce以及Hive如何被翻译成为MapReduce要非常精通
-
- 一些关键的参数
归根结底,Hive的性能优化主要考虑的是如何最大化和最优化的使用CPU、Memory以及IO。
二、Hive性能调优之企业级Mapper和Reducer调优深度细节解密
1.Hive背后的Mapper调优
(1) Mapper数过大,会产生大量小文件,由于Mapper是基于JVM虚拟机的,过多的Mapper的创建和初始化以及关闭虚拟机都会消耗大量的硬件资源。(jvm复用不稳定) Mapper数太小,并发度过小,Job执行时间过长,无法充分利用引擎的分布式硬件资源。 (2) Mapper数量的决定因素:
- 输入文件数量
- 输入文件的大小
- 配置参数 默认情况下: 例如一个文件800MB,Block-Size:128MB,那么Mapper数量为6,(800/128=6.25 向上取整为7),6个Mapper处理的数据是128MB,1个Mapper处理的数据是32MB; 再例如:一个目录下有三个文件分别大小为5MB,18MB,280MB,此时会产生5个Mapper,处理的数据分别是5MB,18MB,128MB,128MB, 22MB;(数据倾斜)
减少Mapper个数的方法: 合并小文件,这种小文件有可能是直接来自于数据源的小文件,也可能是Reducer产生的小文件; 设定合并器:
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
set hive.merge.mapfiles=true; //mapper输出文件合并启用
set hive.merge.mapredfiles=true; //reducer输出文件合并启用
set hive.merge.size.per.task=256000000; //合并文件大小
set mapred.max.split.size=256000000;
set mapred.min.split.size.per.node=128000000; //每个节点切分大小,判断小文件的标准,小于256MB的为小文件需要合并
**增加Mapper个数的方法: ** 一般情况下,需要增加Mapper个数的场景:数据源为上一个Job的Reducer,可以通过控制Hive SQL中上一个Job的Reducer个数控制的(set mapred.reduce.tasks=12)。例如在Join操作的时候会把多个表分解为多个Job。 (修改InputSplit,通常不要改)
set mapred.reduce.tasks=2;
set hive.merge.mapfiles=true;
set hive.merge.mapredfiles=true;
set hive.merge.size.per.task=256000000;
例如有5个300MB的文件,按照上面的配置会产生10个Mapper,5个Mapper处理的都是256MB的数据,另外5个Mapper处理的都是44MB的数据,问题是:大的Mapper会数据倾斜(拖慢整个Map任务),解决方法:设置** set mapred.map.tasks=6**, (300*5/256=6)此时根据MapReduce的运行机制,会划分6个Mapper的,每个Mapper的处理数据大小是250MB(计算公式: min(1500m/6, 256M))。
2. Hive背后的Reducer调优
- (1) Reducer数目过大的话,会产生很多小文件,每个Reducer都会产生一个文件,如果这些小文件是下一个Job的输入,则会需要对小文件进行合并。同样启动、初始化和销毁Reducer的JVM虚拟机也需要消耗大量的硬件。
Reducer数目过小的话,Reduce的时间会比较长,也可能会出现数据倾斜。
- (2)控制Reducer的个数
set hive.exec.reducers.byte.per.reducer=1G //每个Reducer处理的数据量
set hive.exec.reducers.max=1009
实际计算Reducer个数=min(1009, Reducer的数据输入总量/1G) set mapred.reduce.tasks=10; 默认值1,如果说当前的Reducer的结果很大,且被接下来多个Job使用其结果,如何设置参数呢?一般都需要调大该参数。
什么情况下只有一个Reducer? 如果不进行Group By但却需要order by,当然如果最后Reducer的数据小于默认1G的话,也会只有一个Reducer。
select /*+mapjoin(c)*/ coalesce(ncount,0) new_num, coalesce(acount,0) act_num, coalesce(cum_num,0)+coalesce(ncount,0) cum_num, coalesce(icount,0) init_num, '2016-12-27' dt, c.appid
from
(
select appid, dt, cum_num
from day_user
where datediff('2016-12-27', dt)=1
) c left join
(
select appid, dt, count(appid) acount, sum(init_num) icount
from active_user
where dt='2016-12-27'
group by appid, dt
) a on a.appid=c.appid
left outer join
(
select appid, dt, count(appid) ncount
from new_user
where dt='2016-12-27'
group by appid, dt
) n on n.appid=c.appid;
set mapred.reduce.tasks=2;
set hive.merge.mapfiles=true;
set hive.merge.mapredfiles=true;
select coalesce(ncount,0) new_num, coalesce(acount,0) act_num, coalesce(cum_num,0)+coalesce(ncount,0) cum_num, coalesce(icount,0) init_num, '2016-12-27' dt, c.appid
from
(
select appid, dt, cum_num
from day_user
where datediff('2016-12-27', dt)=1
) c left join
(
select appid, dt, count(appid) acount, sum(init_num) icount
from active_user
where dt='2016-12-27'
group by appid, dt
) a on a.appid=c.appid
left outer join
(
select appid, dt, count(appid) ncount
from new_user
where dt='2016-12-27'
group by appid, dt
) n on n.appid=c.appid;
三、Hive性能调优之企业级Join、MapJoin、Group By、Count和数据倾斜
-
- Hive在分布式运行的时候最害怕的是数据倾斜,这是由于分布式系统的特性决定的,因为分布式系统之所以很快是由于作业平均分配给了不同的节点,不同节点同心协力,从而达到更快处理完作业的目的。 顺便说明一下,处理数据倾斜的能力是Hadoop和Spark工程师最核心的竞争力之一。
- 2.Hive中数据倾斜的原因: 数据在分布式节点上分布不平衡;join时某些key可能特别大(常见null值);group by时某个值可能特别多;count(distinct key...)时有可能出现数据倾斜,因为其内部处理会进行group by操作。
- 3. join,我们希望join时key是分散的,如果一个key的数据量特别大,有可能会出现数据倾斜和OOM。一个核心点是: 小表join大表,可以在reduce阶段左侧的小表会加载进内存,降低OOM的风险。
-
- 大表join大表的情况:数据倾斜,例如null值,解决办法一般是要打散null值,例如说使用随机数等,如果数据倾斜比较严重,采用这种方式可以提升至少一倍的速度。
-
- mapJoin:小表join(超)大表的时候,可以采用mapjoin的方式把小表全部加载到mapper端的内存中。 /+MAPJOIN(table_name)/
-
- 小表join(超)大表的时候,不会自动进行mapJoin,需要设置:
set hive.auto.convert.join=true; //hive在进行join的时候会判断左表的大小来决定是否进行mapJoin
set hive.mapjoin.smalltable.filesize=128000000 //hive在进行join的时候会判断左表的大小来决定是否进行mapJoin的大小阈值 字节数
set hive.mapjoin.cache.numrows=1000000 //hive在进行join的时候会判断左表的大小来决定是否进行mapJoin的大小阈值--数据行数
上述参数可以根据实际的硬件机器的内存进行调整,对性能有至关重要的影响,因为没有了shuffle 对于mapJoin我们能够使用mapper端JVM中多大的内存呢?
set hive.mapjoin.followby.gby.localtask.max.memory.usage=0.55 //百分比
set hive.mapjoin.localtask.max.memory.usage=0.9 //百分比
-
- group by,我们可以设置在Mapper端进行部分聚合,最后在reducer端进行全局聚合。
set hive.map.aggr=true; //默认开启,
set hive.groupby.mapaggr.checkinterval=1000000; //在Map端进行聚合操作的条目数
//防止数据倾斜
set hive.groupby.skewindata=true; //会产生Mapper-Reducer-Reducer的结构
生成查询计划时,实际上会生成两个Job,第一个Job会通过自己的算法打散倾斜的key并进行聚合操作且保留结果,第二个Job会完成全部的GroupBy操作,相当于Mapper-Reducer-Reducer的结构。(第一个会把Mapper的输出随机分布到Reducer中,每个Reducer做部分聚合并且保存结果,这样导致相同的Groupby Key分配到不同的Reducer上,一定程度上避免数据倾斜,接下来另外一个Job根据前一个Job预处理数据的结果再进行Group By到Reducer中)
默认情况下: set hive.groupby.skewindata=true; 参数后
-
- count(distinct ) 如果某个值特别多,容易产生数据倾斜。 解决思路: 在查询语句中,例如对null值进行过滤,在结果上加1。 count(uid) (uid中去掉了值为null)的记录,所以在最后的结果 cnt 的基础上加1, 即cnt+1
-
- 笛卡尔积: join时没有on条件或者on条件无效,此时会使用Reducer进行笛卡尔积的操作。 on条件中不支持UDAF(User- Defined Aggregation Function)
-
- 数据倾斜 skew join:
select l.* from logs l join users u on l.user_id=u.user_id; (未登录用户user_id为-1,造成数据倾斜)
解决办法: 1). 采用系统自带的优化方式: 首先把user_id=-1的值先保存到HDFS上,然后专门启动一个MapJoin来进行user_id=-1的计算 自动开启此功能:
set hive.optimize.skewjoin=true;
set hive.skewjoin.key=100000; //某个key超过这个值,开启这个功能
2). 从业务的视角去进行优化来改写sql语句 核心思路就是把倾斜的key和不倾斜的字段分离开;
select a.* from (select * from logs where user_id = -1) a
join (select * from users where user_id = -1) b on a.user_id = b.user_id
union all
select a.* from logs a join users b on a.user_id > 0 and a.user_id = b.user_id;
3.关于Hive的Job并行问题 Hive的Job默认情况下是串行的,我们可以通过并行Hive应用程序中的多个Job来达到提升效率的目的。 设置
set hive.exec.parallel=true;
set hive.exec.parallel.thread.number=32;
减少Job的数目: 例如用户访问日志分析,想看news和business不同页面的用户访问量 未优化的HQL:
select count(*) from
(select distinct user_id from logs where pageCategory = 'news') a
join
(select distinct user_id from logs where pageCategory = 'business') b
on a.user_id = b.user_id;
上述SQL语句一共4个job(两个子查询,一个join,一个count)
explain
select count(*) from
(select distinct devid from user_session where evt_type = 'appinit') a
join
(select distinct devid from user_session where evt_type = 'appclose') b
on a.devid= b.devid;
select count(*)
from logs
group by user_id
having (count(case when pageCategory = 'news' then 1 end) > 0) and
(count(case when pageCategory = 'business' then 1 end) >0);
Explain
STAGE DEPENDENCIES:
Stage-1 is a root stage
Stage-7 depends on stages: Stage-1, Stage-4 , consists of Stage-8, Stage-9, Stage-2
Stage-8 has a backup stage: Stage-2
Stage-5 depends on stages: Stage-8
Stage-3 depends on stages: Stage-2, Stage-5, Stage-6
Stage-9 has a backup stage: Stage-2
Stage-6 depends on stages: Stage-9
Stage-2
Stage-4 is a root stage
Stage-0 depends on stages: Stage-3
STAGE PLANS:
Stage: Stage-1
Map Reduce Map Operator Tree: TableScan alias: user_session Statistics: Num rows: 5194 Data size: 175404 Basic stats: PARTIAL Column stats: NONE Filter Operator predicate: ((evt_type = 'appinit') and devid is not null) (type: boolean) Statistics: Num rows: 1299 Data size: 43867 Basic stats: COMPLETE Column stats: NONE Select Operator expressions: devid (type: string) outputColumnNames: devid Statistics: Num rows: 1299 Data size: 43867 Basic stats: COMPLETE Column stats: NONE Group By Operator keys: devid (type: string) mode: hash outputColumnNames: _col0 Statistics: Num rows: 1299 Data size: 43867 Basic stats: COMPLETE Column stats: NONE Reduce Output Operator key expressions: _col0 (type: string) sort order: + Map-reduce partition columns: _col0 (type: string) Statistics: Num rows: 1299 Data size: 43867 Basic stats: COMPLETE Column stats: NONE Reduce Operator Tree: Group By Operator keys: KEY._col0 (type: string) mode: mergepartial outputColumnNames: _col0 Statistics: Num rows: 649 Data size: 21916 Basic stats: COMPLETE Column stats: NONE File Output Operator compressed: false table: input format: org.apache.hadoop.mapred.SequenceFileInputFormat output format: org.apache.hadoop.hive.ql.io.HiveSequenceFileOutputFormat serde: org.apache.hadoop.hive.serde2.lazybinary.LazyBinarySerDe
Stage: Stage-7
Conditional Operator
Stage: Stage-8
Map Reduce Local Work Alias -> Map Local Tables: $INTNAME1 Fetch Operator limit: -1 Alias -> Map Local Operator Tree: $INTNAME1 TableScan HashTable Sink Operator keys: 0 _col0 (type: string) 1 _col0 (type: string)
Stage: Stage-5
Map Reduce Map Operator Tree: TableScan Map Join Operator condition map: Inner Join 0 to 1 keys: 0 _col0 (type: string) 1 _col0 (type: string) Statistics: Num rows: 713 Data size: 24107 Basic stats: COMPLETE Column stats: NONE Group By Operator aggregations: count() mode: hash outputColumnNames: _col0 Statistics: Num rows: 1 Data size: 8 Basic stats: COMPLETE Column stats: NONE File Output Operator compressed: false table: input format: org.apache.hadoop.mapred.SequenceFileInputFormat output format: org.apache.hadoop.hive.ql.io.HiveSequenceFileOutputFormat serde: org.apache.hadoop.hive.serde2.lazybinary.LazyBinarySerDe Local Work: Map Reduce Local Work
Stage: Stage-3
Map Reduce Map Operator Tree: TableScan Reduce Output Operator sort order: Statistics: Num rows: 1 Data size: 8 Basic stats: COMPLETE Column stats: NONE value expressions: _col0 (type: bigint) Reduce Operator Tree: Group By Operator aggregations: count(VALUE._col0) mode: mergepartial outputColumnNames: _col0 Statistics: Num rows: 1 Data size: 8 Basic stats: COMPLETE Column stats: NONE File Output Operator compressed: false Statistics: Num rows: 1 Data size: 8 Basic stats: COMPLETE Column stats: NONE table: input format: org.apache.hadoop.mapred.TextInputFormat output format: org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat serde: org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe
Stage: Stage-9
Map Reduce Local Work Alias -> Map Local Tables: $INTNAME Fetch Operator limit: -1 Alias -> Map Local Operator Tree: $INTNAME TableScan HashTable Sink Operator keys: 0 _col0 (type: string) 1 _col0 (type: string)
Stage: Stage-6
Map Reduce Map Operator Tree: TableScan Map Join Operator condition map: Inner Join 0 to 1 keys: 0 _col0 (type: string) 1 _col0 (type: string) Statistics: Num rows: 713 Data size: 24107 Basic stats: COMPLETE Column stats: NONE Group By Operator aggregations: count() mode: hash outputColumnNames: _col0 Statistics: Num rows: 1 Data size: 8 Basic stats: COMPLETE Column stats: NONE File Output Operator compressed: false table: input format: org.apache.hadoop.mapred.SequenceFileInputFormat output format: org.apache.hadoop.hive.ql.io.HiveSequenceFileOutputFormat serde: org.apache.hadoop.hive.serde2.lazybinary.LazyBinarySerDe Local Work: Map Reduce Local Work
Stage: Stage-2
Map Reduce Map Operator Tree: TableScan Reduce Output Operator key expressions: _col0 (type: string) sort order: + Map-reduce partition columns: _col0 (type: string) Statistics: Num rows: 649 Data size: 21916 Basic stats: COMPLETE Column stats: NONE TableScan Reduce Output Operator key expressions: _col0 (type: string) sort order: + Map-reduce partition columns: _col0 (type: string) Statistics: Num rows: 649 Data size: 21916 Basic stats: COMPLETE Column stats: NONE Reduce Operator Tree: Join Operator condition map: Inner Join 0 to 1 keys: 0 _col0 (type: string) 1 _col0 (type: string) Statistics: Num rows: 713 Data size: 24107 Basic stats: COMPLETE Column stats: NONE Group By Operator aggregations: count() mode: hash outputColumnNames: _col0 Statistics: Num rows: 1 Data size: 8 Basic stats: COMPLETE Column stats: NONE File Output Operator compressed: false table: input format: org.apache.hadoop.mapred.SequenceFileInputFormat output format: org.apache.hadoop.hive.ql.io.HiveSequenceFileOutputFormat serde: org.apache.hadoop.hive.serde2.lazybinary.LazyBinarySerDe
Stage: Stage-4
Map Reduce Map Operator Tree: TableScan alias: user_session Statistics: Num rows: 5194 Data size: 175404 Basic stats: PARTIAL Column stats: NONE Filter Operator predicate: ((evt_type = 'appclose') and devid is not null) (type: boolean) Statistics: Num rows: 1299 Data size: 43867 Basic stats: COMPLETE Column stats: NONE Select Operator expressions: devid (type: string) outputColumnNames: devid Statistics: Num rows: 1299 Data size: 43867 Basic stats: COMPLETE Column stats: NONE Group By Operator keys: devid (type: string) mode: hash outputColumnNames: _col0 Statistics: Num rows: 1299 Data size: 43867 Basic stats: COMPLETE Column stats: NONE Reduce Output Operator key expressions: _col0 (type: string) sort order: + Map-reduce partition columns: _col0 (type: string) Statistics: Num rows: 1299 Data size: 43867 Basic stats: COMPLETE Column stats: NONE Reduce Operator Tree: Group By Operator keys: KEY._col0 (type: string) mode: mergepartial outputColumnNames: _col0 Statistics: Num rows: 649 Data size: 21916 Basic stats: COMPLETE Column stats: NONE File Output Operator compressed: false table: input format: org.apache.hadoop.mapred.SequenceFileInputFormat output format: org.apache.hadoop.hive.ql.io.HiveSequenceFileOutputFormat serde: org.apache.hadoop.hive.serde2.lazybinary.LazyBinarySerDe
Stage: Stage-0
Fetch Operator limit: -1 Processor Tree: ListSink
Time taken: 0.876 seconds, Fetched: 229 row(s)
两个作业:(explain执行计划)
explain
select count(*) from user_session
group by devid
having (count(case when evt_type = 'appinit' then 1 end) > 0) and count(case when evt_type= 'appclose' then 1 end) >0);
Explain
STAGE DEPENDENCIES:
Stage-1 is a root stage
Stage-0 depends on stages: Stage-1
STAGE PLANS:
Stage: Stage-1
Map Reduce Map Operator Tree: TableScan alias: user_session Statistics: Num rows: 5194 Data size: 175404 Basic stats: PARTIAL Column stats: NONE Select Operator expressions: devid (type: string), evt_type (type: string) outputColumnNames: devid, evt_type Statistics: Num rows: 5194 Data size: 175404 Basic stats: PARTIAL Column stats: NONE Group By Operator aggregations: count(), count(CASE WHEN ((evt_type = 'appinit')) THEN (1) END), count(CASE WHEN ((evt_type = 'appclose')) THEN (1) END) keys: devid (type: string) mode: hash outputColumnNames: _col0, _col1, _col2, _col3 Statistics: Num rows: 5194 Data size: 175404 Basic stats: COMPLETE Column stats: NONE Reduce Output Operator key expressions: _col0 (type: string) sort order: + Map-reduce partition columns: _col0 (type: string) Statistics: Num rows: 5194 Data size: 175404 Basic stats: COMPLETE Column stats: NONE value expressions: _col1 (type: bigint), _col2 (type: bigint), _col3 (type: bigint) Reduce Operator Tree: Group By Operator aggregations: count(VALUE._col0), count(VALUE._col1), count(VALUE._col2) keys: KEY._col0 (type: string) mode: mergepartial outputColumnNames: _col0, _col1, _col2, _col3 Statistics: Num rows: 2597 Data size: 87702 Basic stats: COMPLETE Column stats: NONE Filter Operator predicate: ((_col2 > 0) and (_col3 > 0)) (type: boolean) Statistics: Num rows: 288 Data size: 9725 Basic stats: COMPLETE Column stats: NONE Select Operator expressions: _col1 (type: bigint) outputColumnNames: _col0 Statistics: Num rows: 288 Data size: 9725 Basic stats: COMPLETE Column stats: NONE File Output Operator compressed: false Statistics: Num rows: 288 Data size: 9725 Basic stats: COMPLETE Column stats: NONE table: input format: org.apache.hadoop.mapred.TextInputFormat output format: org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat serde: org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe
Stage: Stage-0
Fetch Operator limit: -1 Processor Tree: ListSink
Time taken: 0.297 seconds, Fetched: 55 row(s)
- Multi-Group by 和Multi-Insert是Hive特有语法,可以在同一个查询语句中使用多个不相交的insert语句,只需扫描一遍全表。
from test
insert overwrite table test_by_a
select a, count(e) group by a
insert overwrite table test_by_b
select b,count(f) group by b
insert overwrite table test_by_c
select c,count(g) group by c;
- 利用union all特性将多个MapReduce作业合并 未优化的HQL:
select * from
(select * from t1 group by c1,c2,c3
union all
select * from t2 group by c1,c2,c3) t3
group by c1,c2,c3;
优化后的HQL: //t1,t2表结构完全一致
select * from
(select * from t1
union all
select * from t2)t3
group by c1,c2,c3;
- 全排序Order by 默认情况下:归并排序
优化:通过shuffle将Map的数据进行排序,Reduce任务对自己的数据进行排序,输出刚好全局有序。 关键点:如何生成数据分区。 (1)改变Shuffle规则,使Shuffle过程按照分区进行,而不是按照key值进行
set hive.mapred.partitioner=org.apache.hadoop.mapred.lib.TotalOrderPartitioner;
set total.order.partitioner.path=/tmp/range_key; //指定分区文件的路径
(2) 创建分区表来保存分区文件
create external table range_keys(id int)
row format serde 'org.apache.hadoop.hive.serde2.binarysortable.BinarySortableSerDe' stored as inputformat 'org.apache.hadoop.mapred.TextInputFomat' outputformat 'org.apache.hadoop.hive.ql.io.HiveNullValue SequenceFileOutputFormat' location '/tmp/range_key';
(3) 利用hive的桶特性进行抽样,生成分区文件
insert overwrite table range_keys
select distinct id
from source t_sale sampletable (bucket 100 out of 1000 on rand()) s
sort by id;
四、Hive性能调优之Hadoop引擎调优分析
- 对Hive底层的引擎对MapReduce进行调优,主要分为Mapper端和Reducer端的调优;而Mapper和Reducer都是JVM,所以JVM视角来看待调优是一个很棒的视角,因为可以使用以前对JVM和Java调优的经验。
从JVM的角度来讲的话 Mapper端: (1) Mapper要读取数据、处理数据和写数据到磁盘,里面运行的MapTask在写磁盘前会产生中间数据,此时会有Buffer,由于MapReduce特性也会有Buffer对数据进行排序,Mapper中MapOutputBuffer的大小默认100M(版本相关),这个参数可以通过io.sort.mb参数来调整,根据不同的硬件尤其是内存的大小来调整,调大的话,会减少磁盘spill的次数,此时如果内存足够的话,一般都会显著提升性能。Spill一般会在Buffer大小的80%(可调)左右开始进行Spill,可以通过io.sort.spill.percent进行调整,MapTask在计算的时候会不断地产生很多spill文件,在MapTask结束前,会对这些spill文件进行合并,这个过程就是merge的过程,其中io.sort.factor默认值是10,代表进行merge的时候最多能够同时merge多少spill文件,如果有100个spill文件的话,此时无法一次完成整个merge过程,这个时候需要调大io.sort.factor参数来减少merge的次数, 减少磁盘的操作。 (2) Combiner存在的时候,此时会根据Combiner定义的函数对map的结果进行合并,什么时候进行Combine操作是由min.num.spill.for.combine的参数决定,默认值为3,也就是说,spill的文件数在默认情况下有3个的时候就会进行combine操作(线程)。最终减少磁盘数据,减少Reducer的网络IO。 (3) 压缩,对spill、merge文件都可以压缩,中间结果非常大IO成为瓶颈的时候,使用压缩可以很大程度的提高性能。可以通过mapred.compress.map.output设置为true,数据会被压缩后写入磁盘,读数据读到是压缩后的数据,会消耗CPU。在实际经验中Hive在Hadoop运行的瓶颈一般都是IO而不是CPU,压缩 一般可以10倍的减少IO操作。压缩算法Gzip、Lzo、Bzip2、Lzma等,其中lzo是一种比较平衡的选择。通过mapred.map.output.compression.codec参数设置。
io.sort.record.percent参数Map任务输出Record边界的百分比,一般很少调
Reducer端: (1) Reducer: copy(shuffle)-sort-reduce,Mapper端会根据Reducer的数目把把Mapper的输出结果分为若干部分(shuffle),在Hadoop中当第一个Mapper完成的时候就开始了Reducer过程,其实就是shuffle,核心是Reducer去Mapper端下载属于自己的数据部分,由于Mapper有很多,所以对具体的一个Reducer而言会到很多Mapper下载数据自己的数据,那到底同时到多少个Mapper下载数据呢?可以通过mapred.reduce.paralle.copies进行调整,默认并行度为5,即每个Reducer可以同时从5个Mapper下载数据,在Reducer内存和网络都比较好的情况下,可以调大该参数。 (2) Shuffle的时候Reducer从Mapper下载属于自己的数据的过程中Mapper有可能出错,此时Reducer对于这个问题的Mapper下载线程会等一段时间后再去下载,尝试一定次数后会放弃并从其他的地方进行下载,可以通过mapred.reduce.copy.backoff参数进行调整,一般情况下都会调大这个参数,这是企业级最佳实践。 (3) Reducer端把数据下载本地后需要进行merge,io.sort.factor配置会调整merge,一般情况下都会调大,通过mapred.job.shuffle.input.buffer.percent来设置,默认reducer会使用heap的70%来再内存中缓存数据,例如:heap的大小为10G,那么会使用7G的大小来缓存下载的数据,但是会通过mapred.job.shuffle.merge.percent来设置百分比,默认是66% (4) Reducer端把自己需要的所有数据下载完后会进行排序(很快,边下载边merge边排序),然后进行reduce用户自定义业务逻辑的回调,可以用参数mapred.job.reduce.input.buffer.percent来设置reduce的缓存大小,默认情况是没有缓存的!!!所以默认情况下都是从磁盘读取数据,所以如果内存够大,务必设置该参数让reduce直接从缓存读数据,这样做就有点Spark Cache的感觉。