Hive 动态分区小文件过多问题优化

一、问题描述

为了支撑相应的业务需求,本次生产环境通过Hive SQL来完成动态插入分区表数据的脚本开发。但是,动态分区的插入往往会伴随产生大量的小文件的发生。而小文件产生过多的影响主要分为以下两种情况:

(1) 从Hive的角度看,小文件会开很多map,一个map开一个JVM去执行,所以这些任务的初始化,启动,执行会浪费大量的资源,严重影响性能。

(2)在HDFS中,每个小文件对象约占150byte,如果小文件过多会占用大量内存。这样NameNode内存容量严重制约了集群的扩展。
所以,我们必须要解决上述小文件过多的情况,可以从输入、中间过程和输出来入手进行优化。

二、知识概括

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

·在PARTITION (month,day)中指定分区字段名即可;

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

[注意] hive使用动态分区,需要进行相应的配置。

2. 动态分区相关配置项介绍

● hive.exec.dynamic.partition

  ○ 默认值:false

  ○ 是否开启动态分区功能,默认为关闭状态

  ○ 使用动态分区,必须将该参数设置为true

● hive.exec.dynamic.partition.mode

  ○ 默认值为:strict

  ○ 动态分能不能将数据均匀的分配呢?可以!我们可以使用DISTRIBUTE BY rand()控制在map端如何拆分数据给reduce端的,hive会根据distribute by后面列,对应reduce的个数进行分发,默认采用的是hash算法。rand()方法会生成一个0~1之间的随机数[rand(int param)返回一个固定的数值],通过随机数进行数据的划分,因为每次都随机的,所以每个reducer上的数据会很均匀。将数据随机分配给Reduce,这样可以使得每个Reduce处理的数据大体一致区的模式,默认为: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

  ○ 当有空分区生成时,是否抛出异常

  ○ 一般不需要设置

3.产生小文件的原因:

Spark程序以动态分区的方式写入Hive表时,会出现了大量的小文件,导致最后移动文件到hive表目录非常耗时,这是因为在Shuffle时Hive多个分区的数据随机落到Spark的多个Task中,此时Task与Hive分区数据的关系是多对多,即每个Task会包含多个分区的部分数据,每个Task中包含的每个分区的数据都很少,最终会导致Task写多个分区文件,每个分区文件都比较小。

为了减少小文件的数量,需要将数据按照分区字段进行Shuffle,将各个分区的数据尽量各自集中在一个Task,在Spark SQL中就是通过distribute by关键字来完成这个功能的。

当使用distribute by关键字在后出现了数据倾斜,即有的分区数据多,有的分区数据少,也会导致spark 作业整体耗时长。需要在distribute by后面增加随机数,例如:

insert overwrite table target partition(p1,p2)select * from sourcedistribute by p1, p2, cast(rand() * N as int)

N值可以在文件数量和倾斜度之间做权衡。

在Spark SQL中使用动态分区写入数据,需要同时使用distribute bysort by才能有效减少小文件数量。

4.解决办法:

 

    

distribute by:

少用动态分区,如果场景下必须使用时,那么记得在SQL语句最后添加上distribute by

假设现在有20个分区,我们可以将dt(分区键)相同的数据放到同一个Reduce处理,这样最多也就产生20个文件,dt相同的数据放到同一个Reduce可以使用DISTRIBUTE BY dt实现,所以修改之后的SQL如下:

insert overwrite table temp.wlb_tmp_smallfile partition(dt)

select * from process_table

DISTRIBUTE BY dt;

修改完之后的SQL运行良好,并没有出现上面的异常信息,但是这里也有个问题,这20个分区目录下每个都只有一个文件!!这样用计算框架(MR/Spark)读取计算时,Mapper/Task数量根据文件数而定,并发度上不去,直接导致了这个SQL运行的速度很慢 

如果想要具体最后落地生成多少个文件数,使用 distribute by cast( rand * N as int) 这里的N是指具体最后落地生成多少个文件数,那么最终就是每个分区目录下生成7个 文件大小基本一致的文件。修改的SQL如下

insert overwrite table temp.wlb_smallfile partition(dt)

select * from process_table

distribute by cast(rand()*7 as int);

合理调整Reduce端的读取的文件的大小:

-- 在 map only 的任务结束时合并小文件

set hive.merge.mapfiles = true;

-- 在 MapReduce 的任务结束时合并小文件

set hive.merge.mapredfiles = true;

-- 作业结束时合并文件的大小

set hive.merge.size.per.task = 256000000;

-- 每个Map最大输入大小(这个值决定了合并后文件的数量)

set mapred.max.split.size=256000000;  

-- 每个reducer的大小, 默认是1G,输入文件如果是10G,那么就会起10个reducer;

set hive.exec.reducers.bytes.per.reducer=1073741824;

set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;

参考文章:Hive Distribute by 应用之动态分区小文件过多问题优化_distribute by cast(rand() * 99 as int)-CSDN博客

  • 13
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值