数仓面试之Hive小文件过多问题

一、小文件产生的原因

        hive 中的小文件肯定是向 hive 表中导入数据时产生,所以,一般而言,有多少种导入方式,就有多少种原因,解决时,需要具体问题具体分析。我们先看下向 hive 中导入数据的几种方式吧。

1、直接向表中插入数据

-- 方式一
insert into table emp_test values(7791,'zhangsan','临时工',7790,'1998-06-08',3000,1,1);
insert into table emp_test values(7792,'lisi','临时工',7790,'1998-07',3000,1,1);


-- 方式二
insert into table emp_test values(7793,'wangwu','临时工',7790,'1998-01-08',3000,1,1),(7794,'mazi','临时工',7790,'1998-08-08',3000,1,1)

        当我们以方式一插入两条数据时,会产生两个文件,当我们以方式二插入两条数据时,只会产生一个小时文件,如图所示:红色区域是方式一产生的小文件,蓝色区域是方式二产生的。

        结论:这种方式每次插入时都会产生一个文件,多次插入少量数据就会出现多个小文件,因为此处的insert语句没有reduce阶段,所以一个insert语句,就是一个小文件。但是这种方式生产环境很少使用,可以说基本没有使用的。

2、通过load方式加载数据

-- 导入文件
load data local inpath '/xgh/emp/A.txt' into table emp_test

-- 导入文件夹
load data local inpath '/xgh/emp/' into table emp_test

        小文件如图所示:

        结论:使用 load 方式可以导入文件或文件夹,当导入一个文件时,hive表就有一个文件,当导入文件夹时,hive表的文件数量为文件夹下所有文件的数量

3、通过查询方式加载数据

insert overwrite table A  select s_id,c_name,s_score from B;

        这种方式是生产环境中常用的,也是最容易产生小文件的方式 insert 导入数据时会启动 MR 任务,MR中 reduce 有多少个就输出多少个文件。所以,

        文件数量=ReduceTask数量*分区数

也有很多简单任务没有reduce,只有map阶段,则

        文件数量=MapTask数量*分区数

        每执行一次 insert 时hive中至少产生一个文件,因为 insert 导入时至少会有一个MapTask。

扩展:

  hive中,map和reduce的数量是如何划分的?为什么有些hive语句没有reduce?

一般来说,Hive中的Map阶段和Reduce阶段是根据以下规则划分的:

  1. Map阶段:Map阶段处理数据并生成中间结果。对于每一个执行中的Map任务,Hive会为每个分片(Split)生成一个或多个Map任务(默认情况下,Hive会以HDFS块的大小作为Map任务的输入分片大小)。这些Map任务会对输入数据进行本地处理,并将结果写入本地磁盘。
  2. Reduce阶段:Reduce阶段对Map阶段的中间结果进行汇总和聚合。在Map阶段之后,Hive会根据查询语句中的聚合函数(如GROUP BY、SUM、COUNT等)将Map阶段的输出进行合并。如果有聚合函数,则至少会有一个Reduce阶段。Hive会根据集群资源、数据量、数据分布和查询需求等因素来自动调整reduce任务的数量的,当然,也可以简单粗暴的设置reduce数量应该是多少,mapred.reduce.tasks,默认值是-1.

有些Hive语句没有Reduce阶段的原因可能有以下几种情况:

  1. 查询语句中没有聚合函数:如果查询语句中没有使用任何聚合函数,那么Hive会将所有的Map任务输出合并成一个结果集,而不需要额外的Reduce阶段。
  2. 数据量较小:如果输入数据量较小,Hive可能会选择在Map阶段直接进行聚合操作,而不需要额外的Reduce阶段。这是因为对于较小的数据集,直接在Map阶段进行聚合可以减少数据传输的开销。
  3. 配置问题:用户可以在Hive的配置文件中指定Map和Reduce阶段的数量。如果配置了较少的Reduce阶段或者没有配置Reduce阶段,那么Hive可能只会执行Map阶段而没有Reduce阶段。

二、小文件过多产生的影响

  1. 首先对底层存储HDFS来说,HDFS本身就不适合存储大量小文件,小文件过多会导致namenode元数据特别大, 占用太多内存,严重影响HDFS的性能

  2. 对 hive 来说,在进行查询时,每个小文件都会当成一个块,启动一个Map任务来完成,而一个Map任务启动和初始化的时间远远大于逻辑处理的时间,就会造成很大的资源浪费。而且,同时可执行的Map数量是受限的。

扩展:

为什么说HDFS不适合存储大量的小文件?

  • 命名节点(NameNode)的存储限制:HDFS中的namenode负责管理文件系统的命名空间和元数据。它需要将文件系统的所有信息加载到内存中并进行管理。当存储大量小文件时,命名节点需要为每个文件维护元数据,包括文件名、权限、时间戳等信息。这会导致命名节点的内存消耗过大,造成性能问题甚至崩溃。
  • 存储空间的浪费:在HDFS中,文件是以块(block)形式进行存储的,默认块大小为128MB或更大。当存储小文件时,较小的文件也会被分配到一个完整的块中,导致存储空间的浪费。如果有大量小文件,则会消耗过多的存储空间。
  • 数据定位和访问的开销:当需要访问特定的小文件时,HDFS需要在各个数据节点上进行定位和读取。对于大量小文件,这将导致访问开销的增加,降低整体的读取性能。

三、怎么解决小文件过多

1. 使用 hive 自带的 concatenate 命令,自动合并小文件

使用方法:

-- 对于非分区表
alter table A concatenate;

-- 对于分区表
alter table B partition(day=2023-10-23) concatenate;

举例:可以看到,未执行 alter table emp_test concatenate 之前,有两个小文件,执行后,只剩下一个小文件。

注意: 
1、concatenate 命令只支持 RCFILE 和 ORC 文件类型。 
2、使用concatenate命令合并小文件时不能指定合并后的文件数量,但可以多次执行该命令。 
3、当多次使用concatenate后文件数量不在变化,这个跟参数 mapreduce.input.fileinputformat.split.minsize=256mb 的设置有关,可设定每个文件的最小size。

扩展:

什么是ORC和RCFILE文件类型?

  • RCFILE(Record Columnar File)是Hive中的一种列式存储格式,它将数据按照列的方式进行存储。这种存储方式具有较高的压缩比率和查询效率,适合于处理大量数据并进行复杂分析的场景。
  • ORC(Optimized Row Columnar)也是一种列式存储格式,在Hive中引入了更多的优化,使得它在性能和存储效率方面都有所提升。ORC格式在处理大规模数据和高性能查询时非常有用。

如果Hive中的数据存储类型是默认的text,则可以使用 Hadoop getmerge 命令先合并数据到本地,再通过put上传数据回去。

2. 调整参数减少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 = 256*1000*1000;   -- 256M

-- 当输出文件的平均大小小于该值时,启动一个独立的MapReduce任务进行文件merge
set hive.merge.smallfiles.avgsize=16000000;   -- 16M 
  • 启用压缩

-- hive的查询结果输出是否进行压缩
set hive.exec.compress.output=true;

-- MapReduce Job的结果输出是否使用压缩
set mapreduce.output.fileoutputformat.compress=true;

3. 减少Reduce的数量

/*
reduce 的个数决定了输出的文件的个数,所以可以调整reduce的个数控制hive表的文件数量,
hive中的分区函数 distribute by 正好是控制MR中partition分区的,
然后通过设置reduce的数量,结合分区函数让数据均衡的进入每个reduce即可。
*/

-- 设置reduce的数量有两种方式,第一种是直接设置reduce个数
set mapreduce.job.reduces=10;

-- 第二种是设置每个reduce的大小,Hive会根据数据总大小猜测确定一个reduce个数
set hive.exec.reducers.bytes.per.reducer=5120000000; -- 默认是1G,设置为5G

-- 执行以下语句,将数据均衡的分配到reduce中
set mapreduce.job.reduces=10;
insert overwrite table A partition(dt)
select * from B
distribute by rand();

/*
解释:如设置reduce数量为10,则使用 rand(), 随机生成一个数 x % 10 ,
这样数据就会随机进入 reduce 中,防止出现有的文件过大或过小
*/

4. 使用hadoop的archive将小文件归档

Hadoop Archive简称HAR,是一个高效地将小文件放入HDFS块中的文件存档工具,它能够将多个小文件打包成一个HAR文件,这样在减少namenode内存使用的同时,仍然允许对文件进行透明的访问

-- 用来控制归档是否可用
set hive.archive.enabled=true;

-- 通知Hive在创建归档时是否可以设置父目录
set hive.archive.har.parentdir.settable=true;

-- 控制需要归档文件的大小
set har.partfile.size=1099511627776;

-- 使用以下命令进行归档
ALTER TABLE A ARCHIVE PARTITION(dt='2020-12-24', hr='12');

-- 对已归档的分区恢复为原文件
ALTER TABLE A UNARCHIVE PARTITION(dt='2020-12-24', hr='12');

注意:  
归档的分区可以查看不能 insert overwrite,必须先 unarchive.

ARCHIVE命令是Hive 2.0版本及更高版本中引入的

总结

如果是新集群,没有历史遗留问题的话,建议hive使用 orc 文件格式,以及启用 lzo 压缩。
这样小文件过多可以使用hive自带命令 concatenate 快速合并。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值