分享

Hive读取不到Flume正在写入的HDFS临时文件的解决办法

desehawk 2015-1-15 21:41:19 发表于 问题解答 [显示全部楼层] 回帖奖励 阅读模式 关闭右栏 4 65215

问题导读

1.本文的应用场景是什么?
2.Hive读取不到Flume正在写入的HDFS临时文件,该如何解决?






实际工作遇到如下场景:应用服务器收集到的日志信息,通过Flume写入到HDFS指定目录,而Hive将其映射到表,进行离线统计。
计划
计划方式处理:
Hive的表创建为外部分区表,例如:
  1. USE mydb;
  2. CREATE EXTERNAL TABLE mytable
  3. (
  4.   c1 String,
  5.   c2 INT,
  6.   c3 INT,
  7.   create_time String
  8. )
  9. PARTITIONED BY (dt STRING);
复制代码


然后创建分区,如:
  1. ALTER TABLE mytable ADD PARTITION (dt = ’2013-09-25′) LOCATION ‘/data/mytable/2013-09-25/’;
  2. ALTER TABLE mytable ADD PARTITION (dt = ’2013-09-26′) LOCATION ‘/data/mytable/2013-09-26/’;
  3. ALTER TABLE mytable ADD PARTITION (dt = ’2013-09-27′) LOCATION ‘/data/mytable/2013-09-27/’;
复制代码


即Hive的表按天进行分区。指定到相应目录。
而Flume中配置将数据保存到HDFS中,即HDFS sink。计划每天一个文件,进行日切。如2013-09-25对应的文件就保存在:
  1. hdfs://<hive.metastore.warehouse.dir>/data/mytable/2013-09-25/FlumeData.xxx
复制代码


这样,只要文件生成,就能直接通过操作Hive的mytable表来对文件进行统计了。
业务上要求统计工作是按照小时进行,考虑到按照小时进行分区过于细化,而且会导致过多的文件给NameNode造成内存压力,所以如上Hive层面按天进行划分。
统计执行时首先指定天分区,然后根据create_time(mm:hh:ss)指定统计时间段,如:
  1. SELECT c1,
  2.             SUM(c2),
  3.             SUM(c3)
  4. FROM mytable
  5. WHERE dt = ’2013-09-25′
  6.      AND create_time BETWEEN ’22:00:00′ AND ’22:59:59′
  7. GROUP BY c1
  8. ;
复制代码


但是,但是,计划始终赶不到遇到的变化!
在实践的过程中遇到如下两个问题:
1.对于正在写入的文件,通过hadoop fs -ls 命令查看,其大小始终是0,即使通过hadoop fs -cat可以看到实际已经有内容存在!通过hive处理的话也看不到其中的数据。
2.Flume正在写入的文件,默认会有.tmp后缀。如果Hive在执行过程中,Flume切换文件,即将xxx.tmp重命名为xxx,这时Hive会报错如file not found xxx.tmp。
了解一番后大致知道了缘由,记录如下:
针对问题1
首先了解HDFS的特点:
HDFS中所有文件都是由块BLOCK组成,默认块大小为64MB。在我们的测试中由于数据量小,始终在写入文件的第一个BLOCK。而HDFS与一般的POSIX要求的文件系统不太一样,其文件数据的可见性是这样的:
  • 如果创建了文件,这个文件可以立即可见;
  • 写入文件的数据则不被保证可见了,哪怕是执行了刷新操作(flush/sync)。只有数据量大于1个BLOCK时,第一个BLOCK的数据才会被看到,后续的BLOCK也同样的特性。正在写入的BLOCK始终不会被其他用户看到!
  • HDFS中的sync()保证数据持久化到了datanode上,然后可以被其他用户看到。
针对HDFS的特点,可以解释问题1中的现象,正在写入无法查看。但是使用Hive统计时Flume还在写入那个BLOCK(数据量小的时候),那岂不是统计不到信息?
解决方案:
每天再按小时切分文件——这样虽然每天文件较多,但是能够保证统计时数据可见!Flume上的配置项为hdfs.rollInterval。
如果文件数多,那么还可以考虑对以前的每天的小时文件合并为每天一个文件!


针对问题2
原因比较明显,Hive处理前获取了对应分区下的所有文件信息,其中包含xxx.tmp文件,而传递给MapReduce处理时,由于Flume进行了切换,导致原来的xxx.tmp变成了xxx,新的.tmp名称又变成了yyy.tmp,这样自然找不到xxx.tmp了。
解决方案:
解决这个问题想法之一是想控制Hive的处理时机,但是显然不是那么好控制。
进一步了解到HDFS的Java API读取HDFS文件时,会忽略以”.”和”_”开头的文件!类似于Linux中默认.xx是隐藏的一样,应用程序读取HDFS文件时默认也不读取.xxx和_xxx这样名称的文件!
这样就产生了针对问题2的处理方案一)配置Flume,针对正在写入的文件,以.号开头。涉及Flume配置项hdfs.inUsePrefix。
也有网友给出了处理方案二):让应用程序也看不到.tmp结尾的文件!方法是继承PathFilter自定义自己的文件筛选类,然后在Hive中设置使用这个类。具体如下(转自此文
  1. package com.twitter.util;
  2. import java.io.IOException;
  3. import java.util.ArrayList;
  4. import java.util.List;
  5. import org.apache.hadoop.fs.Path;
  6. import org.apache.hadoop.fs.PathFilter;
  7. public class FileFilterExcludeTmpFiles implements PathFilter {
  8.     public boolean accept(Path p) {
  9.         String name = p.getName();
  10.         return !name.startsWith(“_”) && !name.startsWith(“.”) && !name.endsWith(“.tmp”);
  11.     }
  12. }
复制代码


然后在hive-site.xml中加入:
  1. <property>
  2.     <name>hive.aux.jars.path</name>
  3.     <value>file:///usr/lib/hadoop/hive-serdes-1.0-SNAPSHOT.jar,file:///usr/lib/hadoop/TwitterUtil.jar</value>
  4. </property>
  5. <property>
  6.     <name>mapred.input.pathFilter.class</name>
  7.     <value>com.twitter.util.FileFilterExcludeTmpFiles</value>
  8. </property>
复制代码


Done!

已有(5)人评论

跳转到指定楼层
roywang1024 发表于 2015-1-16 10:58:43
楼主,关于第一个问题,除了按小时切,还有没有其他方法?
直接文件流是可以读到内容的,就是hdfs没有刷新

点评

这是hadoop机制,不好改  发表于 2015-1-16 14:06
回复

使用道具 举报

roywang1024 发表于 2015-1-16 15:26:46
roywang1024 发表于 2015-1-16 10:58
楼主,关于第一个问题,除了按小时切,还有没有其他方法?
直接文件流是可以读到内容的,就是hdfs没有刷新

查了一圈下来,我估计是这样的,flush和sync之后,数据刷到了datanode,但是namenode没有更新hive可能是根据namenode去校验文件有没有更新,所以没有变化了
打算直接用mapreduce统计,之前试过直接打开文件是可以读到最新数据的
回复

使用道具 举报

willgo 发表于 2015-12-3 13:57:54
楼主  问题2  根据你方法  添加FileFilterExcludeTmpFiles过滤后    .tmp文件确实没有读取了   但是发现会引发另一个奇怪的问题     导致hive的某些内置函数不可用   现在发现配置后hive的  row_number () over ()   报错   报的是什么数组越界的错。。。。    有解么?~.~
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条