一、问题描述

为了支撑相应的业务需求,本次生产环境通过Hive SQL来完成动态插入分区表数据的脚本开发。但是,动态分区的插入往往会伴随产生大量的小文件的发生。而小文件产生过多的影响主要分为以下两种情况:
(1) 从Hive的角度看,小文件会开很多map,一个map开一个JVM去执行,所以这些任务的初始化,启动,执行会浪费大量的资源,严重影响性能。
(2)在HDFS中,每个小文件对象约占150byte,如果小文件过多会占用大量内存。这样NameNode内存容量严重制约了集群的扩展。
所以,我们必须要解决上述小文件过多的情况,可以从输入、中间过程和输出来入手进行优化。
二、知识概括

简要描述动态分区
所谓的动态分区,就是对分区表insert数据时候,数据库自动会根据分区字段的值,将数据插入到相应的分区中,Hive也提供了类似的机制,即动态分区。

·在PARTITION (month,day)中指定分区字段名即可;
·在SELECT子句的最后两个字段,必须对应前面PARTITION (month,day)中指定的分区字段,包括顺序。

[注意] hive使用动态分区,需要进行相应的配置。
2. 动态分区相关配置项介绍

● hive.exec.dynamic.partition
  ○ 默认值:false
  ○ 是否开启动态分区功能,默认为关闭状态
  ○ 使用动态分区,必须将该参数设置为true
● hive.exec.dynamic.partition.mode
  ○ 默认值为:strict
  ○ 动态分区的模式,默认为:strict,表示必须指定至少一个分区为静态分区。nonstrict模式表示允许所有的分区字段都可以动态分区。
  ○ 一般需要设置为nonstrict
● hive.exec.max.dynamic.partitions.pernode
  ○ 默认值为:100
  ○ 在每个执行的MR任务的节点上,最大可以创建多少个动态分区
  ○ 该参数根据实际数据情况来定
  ○ 比如:源数据中包含了一年的数据,即day字段有365个值,那么该参数就需要设置成大于365,如果使用默认值100,则会报错。
● hive.exec.max.dynamic.partitions
  ○ 在所有执行MR的节点上,最大一共可以创建多少个动态分区
  ○ 一般默认值足够,除非你的数据量非常大,需要创建的文件数大于100000,可以根据实际情况来调整
● hive.exec.max.created.files
  ○ 默认值为:100000
  ○ 整个MR job中,最大可以创建多少个hdfs文件
  ○ 一般默认值足够,除非你的数据量非常大,需要创建的文件数大于100000,可以根据实际情况来调整
● hive.error.on.empty.partition
  ○ 默认值为:false
  ○ 当有空分区生成时,是否抛出异常
  ○ 一般不需要设置

三、案例实操

1.本次优化的过程,所设置的参数主要涉及动态分区的参数配置以及减少map和reduce数量。

4. 调整参数减少Map数量
● 设置map输入合并小文件的相关参数:
#执行Map前进行小文件合并
#CombineHiveInputFormat底层是 Hadoop的 CombineFileInputFormat 方法
#此方法是在mapper中将多个文件合成一个split作为输入
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
#每个Map最大输入大小(这个值决定了合并后文件的数量)
set mapred.max.split.size=256000000;   -- 256M
#一个节点上split的至少的大小(这个值决定了多个DataNode上的文件是否需要合并)
set mapred.min.split.size.per.node=100000000;  -- 100M
#一个交换机下split的至少的大小(这个值决定了多个交换机上的文件是否需要合并)
set mapred.min.split.size.per.rack=100000000;  -- 100M
● 设置map输出和reduce输出进行合并的相关参数:
#设置map端输出进行合并,默认为true
set hive.merge.mapfiles = true;
#设置reduce端输出进行合并,默认为false
set hive.merge.mapredfiles = true;
#设置合并文件的大小
set hive.merge.size.per.task = 256000000;   -- 256M
#当输出文件的平均大小小于该值时,启动一个独立的MapReduce任务进行文件merge
set hive.merge.smallfiles.avgsize=16000000;   -- 16M 
 
5. 减少Reduce的数量
#reduce 的个数决定了输出的文件的个数,所以可以调整reduce的个数控制hive表的文件数量,
#hive中的分区函数 distribute by 正好是控制MR中partition分区的,
#然后通过设置reduce的数量,结合分区函数让数据均衡的进入每个reduce即可。
#第二种是设置每个reduce的大小,Hive会根据数据总大小猜测确定一个reduce个数
set hive.exec.reducers.bytes.per.reducer=10240000000; -- 默认是1G,设置为10G
#执行以下语句,将数据均衡的分配到reduce中
set mapreduce.job.reduces=10;
insert into table A partition(dt)
select * from B
distribute by cast(rand()*100 as int);
解释:如设置reduce数量为10,则使用 rand(), 随机生成一个数 x % 10 ,
这样数据就会随机进入 reduce 中,防止出现有的文件过大或过小
 
6. 动态分区参数设置
set hive.exec.dynamic.partition = true;
set hive.exec.dynamic.partition.mode = nostrick;
set hive.exec.max.dynamic.partitions.pernode=5000;
set hive.exec.max.dynamic.partitions = 10000;
set hive.exec.max.created.files =1000000;

四、结果对比

未处理小文件前,小文件数量很多,截图如下:

hive spark 动态分区 hive动态分区缺点_大数据

整体文件数如下图所示:

hive spark 动态分区 hive动态分区缺点_大数据_02


处理小文件后,文件大小如下图所示:

hive spark 动态分区 hive动态分区缺点_大数据_03


整体文件数如下图所示:

hive spark 动态分区 hive动态分区缺点_hive spark 动态分区_04


五、总结

以上就是我本次对由动态分区插入产生大量的小文件的优化过程,采用动态分区参数设置和减少map和reduce数量并且加上使用distribute by cast(rand()*N as int) 随机分发的策略很好的解决集群小文件问题,起到了优化的作用。