Hive使用递归子目录作为partition

一般情况下,使用add partition语句为Hive表新增partition:

1
ALTER TABLE table_name ADD [IF NOT EXISTS] PARTITION (partition_column1='value1', partition_column2='value2',) [LOCATION 'location1'];

其中Location默认为最低层目录,如果该目录仍包含子目录,Hive就会报错。

Hive input not a file异常
然而在很多情况下,数据文件是以层级目录的方式组织的。典型的案例就是使用Flume写数据到HDFS,这时不得不为新的partition新建一个目录,并把数据文件手动拷到该路径下。更加糟糕的是,因为Flume经常使用日期或者日志内存动态创建子目录,有时你并不知道有哪些子目录。为了实现add partition,你可能还要读取目录下的文件夹列表。这显然给程序增加了不必要的复杂度,使程度变得冗长难以维护。

解决这个问题的方法是,为Hive设置以下两个参数:

1
2
hive.mapred.supports.subdirectories=true
mapreduce.input.fileinputformat.input.dir.recursive=ture(Hadoop 2.x) 或 mapred.input.dir.recursive=true(Hadoop 1.x)

讲完结论,我们再仔细研究下背后的原因。

Hive其实就是MapReduce的一个SQL接口,它只记录meta data,并不直接存储和计算数据。Add partition操作本质上是为MapReduce指定了一个新的输入目录,然后MapReduce会尝试在该目录在寻找输入文件。那么定位文件的方式是怎样呢?在JIRA上一个较早的issue HIVE-1083有提到

Basically, by default, a table’s file name pattern will be “*”.

也就是说,Hive只是简单地在location后面使用通配符来查找文件,当匹配到了文件夹,无法处理就报错了。那么既然是HDFS路径,使用HDFS支持的正则通配可以吗?很遗憾,Hive对location是进行了转义处理的,正则通配并不起作用。

这样一来,只好从MapReduce入手了。要过滤输入文件,第一个想到的必须是InputFormat。在编写MapReduce程序时,FileInputFormat有个函数,FileInputFormat.setInputDirRecursive(job, true),应该就是我们需要的。查一下FileInputFormat相关参数,在静态成员变量中找到了:

1
public static final String INPUT_DIR_RECURSIVE = "mapreduce.input.fileinputformat.input.dir.recursive";

于是,将该参数设为true。在Hive中使用时,再将hive.mapred.supports.subdirectories设为true,再执行查询就没问题了!

本文是原创文章,转载请注明:时间与精神的小屋 - Hive使用递归子目录作为partition