Hive Distribute by 应用之动态分区小文件过多问题优化

1 问题解决

解决办法

2 由以上问题引出的问题

3 思考

4 小结

0 问题现象及原因分析


现象:

[Error 20004]: Fatal error occurred when node tried to create 
too many dynamic partitions. The maximum number of dynamic 
partitions is controlled by hive.exec.max.dynamic.partitions and 
hive.exec.max.dynamic.partitions.pernode. Maximum was setto: 100


原因:

Hive对其创建的动态分区数量实施限制。默认值为每个节点100个动态分区,所有节点的总(默认)限制为1000个动态分区。但是,这可以调整。

1 问题解决


解决办法

set hive.exec.dynamic.partition=true;
set hive.exec.max.dynamic.partitions=2048;
set hive.exec.max.dynamic.partitions.pernode=256;


用以上设置后不能保证正常,有时候还需要设置reduce数来配合动态分区使用

set mapred.reduce.tasks=10;


这几个参数需要满足一下条件:

dynamic.partitions / dynamic.partitions.pernode <=mapred.reduce.tasks


比如上面的例子:2048 / 256 = 8,如果mapred.reduce.tasks小于8就会报错,而hive默认reduce数是跟具数据量来动态调整的,所以有时候需要手动调整。

2 由以上问题引出的问题


虽然上述方案能解决报错问题,但是同样引出新的问题,动态分区会产生大量小文件的问题,我们知道hive默认创建文件的上限是100000个(hive.exec.max.created.files),假如我输入的数据量比较大,比如1T数据,此时启动2000个Mapper任务去读取,那么我开启动态分区后,每个任务下有100个分区,将产生2000*100=200000,这样也会超出hive文件创建文件的上限,产生过多的小文件。如下SQL

insert overwrite table test partition(dt)
select * 
from table


那么由动态分区差生的小文件应该如何避免和优化呢?

Hive中distribute by就是用来解决数据分发问题的,他按照后面指定的key将数据分发到哪些reduce中去【HASH的方式,类似于spark中的repartition】,我们利用distribute by dt 可以将同一分区的数据直接发到同一reduce中,这样就产生了100个文件,由原来的200000个文件现在降到为100个,解决了文件数过多的问题。SQL如下:

insert overwrite table test partition(dt)
select * 
from table
distribute by dt


经过上述改造后运行良好,没有再出现任何问题,但同样又引来了一个新的问题,因为这100个分区的数据是分布不均匀的,有的redcue数据很多有几百个G,有的只有几兆,这样导致reduce会卡在99%,HQL运行很慢。因此我们采用随机数,随机分配给reduce来解决该问题,这样可以使每个Reduce处理的数据大体一致。

(1)通过设定每个Reduce处理的数据量来控制最终生成的文件数。

假定一个redcue我们设定10G数据量,则对于1T的数据总共会起102个左右的Reduces,修改的SQL如下:

set hive.exec.reducers.bytes.per.reducer=10240000000;
 
insert overwrite table test partition(dt)
select * 
from table
 
distribute by rand()


(2)通过控制rand()函数来控制最终生成多少个文件【推荐】

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

insert overwrite table test partition(dt)
select * 
from table
 
distribute by cast(rand()*100 as int);


注意这里的技巧:通过 distribute by cast( rand() * N as int)来控制落地文件数,或随机数的具体范围。

3 思考


为什么Hive底层会限制动态分区的数量呢?

动态分区因为会在短时间内创建大量的分区,可能会占用大量的资源

1 内存方面:在Insert场景下,每个动态目录分区写入器(File Writer)至少会打开一个文件,特别是对于parquert或者orc格式的文件,在写入的时候会首先写到缓冲区中,而这些缓冲区是按照分区来维护的,在运行的时候需要的内存量会随着分区数增加而增加。所以经常导致OOM的mapper或者reduce,可能是由于打开的文件写入器的数量。如常见的错误:Error: GC overhead limit exceeded,针对该问题,对应的解决方案如下:

可开启hive.optimize.sort.dynamic.partition参数
增加mapper端的内存,设置mapreduce.map.memory.mb和mapreduce.map.java.opts
2 文件句柄:如果分区数过多,那么每个分区都会打开对应的文件句柄写入数据,可能会导致系统文件句柄占用过多,影响系统其他应用运行。因此hive又提出了一个hive.exec.max.created.files参数来控制创建文件数量(默认是100000)

生成动态分区的几个参数说明

hive.exec.dynamic.partition

默认值:false

是否开启动态分区功能,默认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

默认值:1000

在所有执行MR的节点上,最大一共可以创建多少个动态分区。

同上参数解释。

hive.exec.max.created.files

默认值:100000

整个MR Job中,最大可以创建多少个HDFS文件。

一般默认值足够了,除非你的数据量非常大,需要创建的文件数大于100000,可根据实际情况加以调整。

mapreduce.map.memory.mb

map任务的物理内存分配值,常见设置为1GB,2GB,4GB等。

mapreduce.map.java.opts

map任务的Java堆栈大小设置,一般设置为小于等于上面那个值的75%,这样可以保证map任务有足够的堆栈外内存空间。

mapreduce.input.fileinputformat.split.maxsize

mapreduce.input.fileinputformat.split.minsize

这个两个参数联合起来用,主要是为了方便控制mapreduce的map数量。比如我设置为1073741824,就是为了让每个map处理1GB的文件。

hive.optimize.sort.dynamic.partition
默认值:false

启用hive.optimize.sort.dynamic.partition,将其设置为true。通过这个优化,这个只有map任务的mapreduce会引入reduce过程,这样动态分区的那个字段比如日期在传到reducer时会被排序。由于分区字段是排序的,因此每个reducer只需要保持一个文件写入器(file writer)随时处于打开状态,在收到来自特定分区的所有行后,关闭记录写入器(record writer),从而减小内存压力。这种优化方式在写parquet文件时使用的内存要相对少一些,但代价是要对分区字段进行排序。

第二种方式就是增加每个mapper的内存分配,即增大mapreduce.map.memory.mb和mapreduce.map.java.opts,这样所有文件写入器(filewriter)缓冲区对应的内存会更充沛。
 
 

将查询分解为几个较小的查询,以减少每个查询创建的分区数量。这样可以让每个mapper打开较少的文件写入器(file writer)。


4 小结


本文分析了一种由动态分区产生小文件的或是集群中小文件过多的一种解决方案,采用distribute by cast(rand()*N as int)这一方式能很好的解决集群小文件问题,起到了优化作用。对于使用SparkSQL的用户来说,SparkSQL提供了repartition算子来解决这一问题,在这里其实repartition和distribute by的作用一致,在Spark 2.4.0版中提供了Hive中类似的Hint语法,可以通过如下方式解决

--提示名称不区分大小写

INSERT ... SELECT /*+REPARTITION(n)*/ ...
Repartition Hint可以增加或减少分区数量,它执行数据的完全shuffle,并确保数据平均分配。

repartition增加了一个新的stage,因此它不会影响现有阶段的并行性。

repartition,常用的情况是:上游数据分区数据分布不均匀,才会对RDD/DataFrame等数据集进行重分区,将数据重新分配均匀,

假设原来有N个分区,现在repartition(M)的参数传为M,

当 N < M ,则会根据HashPartitioner (key的hashCode % M)进行数据的重新划分

而 N  远大于 M ,那么还是建议走repartition,这样所有的executor都会运作起来,效率更高,如果还是走coalesce,假定参数是1,那么即使原本申请了10个executor,那么最后执行的也只会有1个executor。
 

  • 3
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 关于hive on spark的distribute by和group by使用: distribute by是在map端对数据进行分区,可以将数据按照指定的列进行分区,使得相同的key值被分到同一个分区中,从而提高reduce端的并行度。而group by是在reduce端对数据进行分组,将相同key值的数据聚合在一起,进行统计计算。 小文件合并问题: 在hive on spark中,由于spark是基于Hadoop的,因此也存在小文件合并的问题。小文件会导致HDFS的NameNode负载过重,影响系统性能。可以通过设置合适的HDFS块大小、使用Hadoop的SequenceFile格式、使用Hive的合并小文件机制等方式来解决小文件合并问题。 ### 回答2: Hive on Spark是将Hive与Spark引擎结合使用的一种解决方案,可以利用Spark的高性能计算能力加速大规模数据处理。在Hive on Spark中,分布式查询是实现高效计算的关键,而其中distribute by和group by语句的使用是非常重要的。 distribute by和group by的区别在于:distribute by指定了分区的方式,将数据按照指定的字段进行分区,而group by则是根据指定的字段对数据进行分组。因此,distribute by语句的执行是在Map阶段完成的,而group by语句的执行是在Reduce阶段完成的。 在使用distribute by和group by时,需要根据数据量和计算需求来选择合适的字段作为分区和分组的标准。一般来说,选择具有高可靠性的字段作为分区和分组的标准,可以避免数据倾斜和计算异常的情况。 另外,在使用Hive on Spark时,小文件的合并问题也需要注意。由于Hadoop文件系统的存储管理机制,大量小文件会占用大量的存储空间,影响数据的查询和计算性能。因此,需要对小文件进行合并处理,合并成较大的文件,以提高数据访问效率。 在Hive on Spark中,可以使用Hadoop的工具对小文件进行合并,例如可以使用Hadoop的SequenceFile格式将多个小文件合并成一个大文件,或者使用Hive提供的Merge语句对具有相似表结构的小文件进行合并。另外,也可以通过调整Hadoop的Block Size参数和SequenceFile中的Record Size参数来控制文件大小和数据压缩比,进一步提高数据存储和访问的效率。 总之,distribute by和group by语句的使用和小文件的合并问题Hive on Spark中都是非常重要的,合理选择分区和分组的标准和对小文件进行合并处理,可以有效提高查询和计算性能,从而实现大规模数据处理的高效率和高可靠性。 ### 回答3: Hive on Spark是一种在Spark上运行的Hive查询引擎,它使用了Spark的分布式计算能力,实现了高性能的数据处理和分析。在使用Hive on Spark时,distribute by和group by是两个常用的关键词,用于实现数据的分组聚合和排序等操作。 Distribute by和Group by的使用 一般情况下,在使用distribute by和group by时,都需要指定一个列名作为排序的关键字。其中,distribute by用于将数据按照指定列进行分发,以便进行并行处理;group by则用于将相同的键值聚合在一起,计算聚合函数的结果。 在使用distribute by时,需要注意数据分发的均衡性,避免数据倾斜导致某些节点的处理压力过大。可以通过调整数据分区的大小或者使用多个distribute by键值来解决这个问题。 在使用group by时,需要注意聚合函数的选择和性能优化。一般建议尽量选择简单的聚合函数,避免复杂的计算逻辑导致性能下降。同时,可以根据数据的分布情况和聚合结果的大小,选择合适的分组数和数据分区大小,以提高查询效率。 小文件合并问题 在使用Hive on Spark时,小文件合并是一个比较常见的性能优化问题。由于Hadoop和Spark都是针对大文件而设计的分布式计算框架,当遇到大量小文件时,会造成系统的性能下降和资源浪费。 一般来说,可以采用两种方式来解决小文件合并问题。一种是使用Hadoop的SequenceFile和CombineFileInputFormat等机制来优化文件的读写性能,把多个小文件合并成一个大文件进行处理。另一种是使用Hive on Spark中提供的压缩和归档工具来实现小文件的合并和压缩,减少存储空间和读写延迟。 总的来说,Hive on Spark作为一种高性能的数据处理引擎,可以通过合理使用distribute by和group by等功能,以及解决小文件合并问题,提高数据分析和处理的效率和可靠性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值