Hadoop Archives对小文件的处理
1. 简介
我们知道大量的小文件会增加nameNode的压力,因为hadoop都是以block为单位存储数据,默认128M.再小的数据也会认为是一个块。Hadoop Archive是一个归档工具,Archive可以把多个文件归档成为一个文件。archive包含元数据(形式是_index和_masterindx)和数据(part-*)文件。_index文件包含了档案中的文件的文件名和位置信息。
创建这个文件是一个mapreduce的过程。
archive文件本占用与原文件相同的硬盘空间;
archive文件不支持压缩;
archive一旦创建就不能进行修改;
归档的文件大小其实没有变化,只是压缩了文件的元数据大小
2. 创建
hadoop archive -archiveName name -p [-r ] *
name:归档文件名 需以.harj结尾
-p:要归档文件的父目录
-r:父目录下的子目录 可给出多个子目录
最后:归档文件安放在哪里
1. 创建:
hadoop archive -archiveName devicehour01.har -p /hyl/20180122 devicehour01 /hyl/archiveTest
2. 查看:用普通的 fs -ls
hadoop fs -ls /hyl/archiveTest/devicehour01.har
Found 4 items
-rw-r--r-- 3 hyl supergroup 0 2018-03-21 16:57 /hyl/archiveTest/devicehour01.har/_SUCCESS
-rw-r--r-- 3 hyl supergroup 229145 2018-03-21 16:57 /hyl/archiveTest/devicehour01.har/_index
-rw-r--r-- 3 hyl supergroup 93 2018-03-21 16:57 /hyl/archiveTest/devicehour01.har/_masterindex
-rw-r--r-- 3 hyl supergroup 2529187762 2018-03-21 16:52 /hyl/archiveTest/devicehour01.har/part-0
3. 大小:fs -du -h 数据文件的大小与我们源文件的大小是一样的 源文件devicehour01也是2.4G
hadoop fs -du -h /hyl/archiveTest/devicehour01.har
0 0 /hyl/archiveTest/devicehour01.har/_SUCCESS
223.8 K 671.3 K /hyl/archiveTest/devicehour01.har/_index
93 279 /hyl/archiveTest/devicehour01.har/_masterindex
2.4 G 7.1 G /hyl/archiveTest/devicehour01.har/part-0
4. 归档工具只是只是一个文件层,所以hadoop fs shell命令同样适用,只是所提供的数据路径会发生变化。
- hadoop fs -ls har:///path这个命令可以看到原来归档子目录有哪些,如果当时有多个子目录,这里也会出现多个。
hadoop fs -ls har:///hyl/archiveTest/devicehour01.har
Found 1 items
drwxr-xr-x - hyl supergroup 0 2018-01-22 22:11 har:///hyl/archiveTest/devicehour01.har/devicehour01
该命令如果继续深入,如-ls har:///path/devicehour01 就可以看到原来在devicehour01下面的文件。
3. 读取
- 其实首先我们需要确定的是我们的归档有没有达到我们的目的,也就是减小block的数量,我们看以下测试:
sc.textFile("/hyl/20180122/devicehour01").getNumPartitions
res69: Int = 2136
sc.textFile("har:///hyl/archiveTest/devicehour01.har/devicehour01").getNumPartitions
结果与上面一样:说明啥问题???这个就能说明归档没有效果吗?
其实不然。这个结果只能说明从归档文件中依然可以获取原来文件的整个目录结构。
我们换一种方式来说明归档后的功效。我们利用 hdfs fsck来查看文件块的信息:
归档前:
hdfs fsck /hyl/20180122/devicehour01
Total size: 2529187762 B
Total dirs: 1
Total files: 2137
Total symlinks: 0
Total blocks (validated): 2136 (avg. block size 1184076 B)
Minimally replicated blocks: 2136 (100.0 %)
归档后:先看整个har文件
hdfs fsck /hyl/archiveTest/devicehour01.har
Total size: 2529417000 B
Total dirs: 1
Total files: 4
Total symlinks: 0
Total blocks (validated): 7 (avg. block size 361345285 B)
Minimally replicated blocks: 7 (100.0 %)
这里的问题是:归档后的文件也是2.4G,如果我们按照128M块大小算的话,应该是20个块左右,
但是现在显示的是7个块,且平均块的大小超过了128M,达到了300多M? 后面会解释这个问题
如果我们再看归档里面的那个文件呢。
但是,用hdfs fsck命令无法再往下走了呢
hdfs fsck har:///hyl/archiveTest/devicehour01.har/devicehour01
该命令无效,虽然我们用Hadoop命令可以解析这个.har下面的文件,
是对hdfs来说不行,我的理解是hadoop 遇到har:// 会根据两个元数据文件去解析那个数据文件。
- 接下来我们读取文件
sc.textFile("har:///hyl/archiveTest/devicehour01.har/devicehour01").take(10).foreach(println)
321cad9e4ed03d23bceb096c1d6e1460b
321cb30831df581174bcbb87724a91a80
没有问题!!!!
这里我们需要指定到归档文件.har的下一级,因为下面还有一个子目录别忘了。。。。。。
可能我们还需要试一下parquet文件;我们先归档一个parquet文件;
hadoop archive -archiveName md5_idfa_ids.har -p /hyl/idMappingAll/ md5_idfa_ids / /hyl/archiveTest
这个约100G的parquet文件 归档耗时约2分钟
这个md5_idfa_ids.har文件下的数据文件就远不止一个了
Found 52 items
-rw-r--r-- 3 hyl supergroup 0 2018-03-22 11:17 /hyl/archiveTest/md5_idfa_ids.har/_SUCCESS
-rw-r--r-- 3 hyl supergroup 2205305 2018-03-22 11:17 /hyl/archiveTest/md5_idfa_ids.har/_index
-rw-r--r-- 3 hyl supergroup 397 2018-03-22 11:17 /hyl/archiveTest/md5_idfa_ids.har/_masterindex
-rw-r--r-- 3 hyl supergroup 2178337118 2018-03-22 11:16 /hyl/archiveTest/md5_idfa_ids.har/part-0
-rw-r--r-- 3 hyl supergroup 2179177657 2018-03-22 11:16 /hyl/archiveTest/md5_idfa_ids.har/part-1
-rw-r--r-- 3 hyl supergroup 2178541621 2018-03-22 11:16 /hyl/archiveTest/md5_idfa_ids.har/part-10
-rw-r--r-- 3 hyl supergroup 2179088198 2018-03-22 11:17 /hyl/archiveTest/md5_idfa_ids.har/part-11
-rw-r--r-- 3 hyl supergroup 2178711339 2018-03-22 11:16 /hyl/archiveTest/md5_idfa_ids.har/part-12
.......
归档完成后,我们进行以下读取操作:
sqlContext.read.parquet("har:///hyl/archiveTest/md5_idfa_ids.har/md5_idfa_ids").printSchema
sqlContext.read.parquet("har:///hyl/archiveTest/md5_idfa_ids.har/md5_idfa_ids").show(5)
以上两个命令都是没有问题的 都能读出正确的内容
我们再来看一下这个文件归档前后快数量的一个变化
归档前:
Total size: 104862340282 B
Total dirs: 1
Total files: 10591
Total symlinks: 0
Total blocks (validated): 10590 (avg. block size 9902015 B)
Minimally replicated blocks: 10590 (100.0 %)
归档后:
Total size: 104864545984 B
Total dirs: 1
Total files: 52
Total symlinks: 0
Total blocks (validated): 243 (avg. block size 431541341 B)
Minimally replicated blocks: 243 (100.0 %)
读取一个HAR文件并不会比直接从HDFS中读文件高效,而且实际上可能还会稍微低效一点,因为对每一个HAR文件的访问都需要完成两层读取,index文件的读取和文件本身的读取
4. 反归档化
我们将文件进行归档了,能不能再将其反归档化,恢复成我们正常的文件呢?
未找到方案。(官网上没有看到这一块的介绍,归档后文件不可更改,以此推断没有办法直接转成以前的文件,可以读取出来再重新存储)
5. 总结
这个归档工具的确可以解决大量小的block块的问题,甚至可以将不同的文件目录放到同一个块里面,但是读取的效率会降低,因为会读两个文件,如果要采用的话,可以对历史文件数据进行归档,然后删除原文件,因为历史文件的访问频率可能会比较低。
6. 原理简析
- 这是一个mapreduce的任务,我们首先看一下他的Map的输入文件。
Map的输入文件是一个临时的senqueceFile。首先我们会生成这个senqueceFile:
会根据我们传入的文件拼接成一个完整的目录(因为可能回传入多个子目录) 然后会递归,遍历所有的文件 而这个senqueceFile的文件内容是 文件长度(文件目录为0)+文件路径+子目录
接下来这个senqueceFile交给Map任务。
通过读取SequenceFile文件,获得它的文件大小,根据目录获取文件中的内容。
map任务的个数:(totalSize/partSize)
totalSize为文件的总大小
partSize默认2 * 1024 * 1024 * 1024l 这并不是最终块的大小
在读取SequenceFile的时候,会根据读入的文件大小累计,直到大于一个partSize的时候,进行划分。也就是说每一个maptask读入的文件数据量不会超过一个partSize的量。
进入Map
map task的key是文件长度,value是文件地址等相关信息。
根据文件路径判断是否是文件。
如果是文件目录:
传给reduce的是:towrite=relPath.toString()) + " dir " + propStr + " 0 0 "
如果是文件:
写入文件:copyData(srcStatus.getPath(), input, partStream, reporter)
是往partStream这个流里面写,这个流里面是有块大小的限制的:
partStream = destFs.create(tmpOutput, false, conf.getInt("io.file.buffer.size", 4096), destFs.getDefaultReplication(tmpOutput), blockSize)
从这里我们可以看出它重新定义了块的大小:默认为512 * 1024 * 1024,可通过-D har.block.size 指定。
同时 towrite = relPath.toString()
+ " file " + partname + " " + startPos
+ " " + srcStatus.getLen() + " " + propStr + " ";
其中startPos是当前输出流的位置。
propStr=encodeName(fStatus.getModificationTime() + " "
+ fStatus.getPermission().toShort() + " "
+ encodeName(fStatus.getOwner()) + " "
+ encodeName(fStatus.getGroup()))
最后传给reduce:
new IntWritable(hash), new Text(towrite) :其中hash是文件路径的hash值。
这里就解释了前面说的文件块大于128M的原因。
4.reduce端:reduce端主要用于生成我们的index文件。索引文件有两个:masterIndex和index。
index:每行就是传过来的value的值。
masterIndex:每行记录startIndex + " " + endIndex + " " + startPos +" "+indexStream.getPos() + " \n"。
startIndex:开始时的路径的hash值(index文件中路径的索引,即map传过来的key)
endIndex:结束时的路径的hash值(index文件中路径的索引,即map传过来的key)
startPos:index文件开始时的hash值的流的位置;
indexStream.getPos():现在流的位置,即结束时hash值的末尾的位置
index记录每个map传过来的值信息(value),每记录numIndexes个,就向masterIndex写一行值,格式如上所示。numIndexes默认为1000。
那么这些index文件会被用于读取har文件:首先读取文件的时候我们回传入一个路径,以这个路径的hash值在masterIndex中去找。找到之后又在在index文件中去找,最后得到这个文件在数据文件中的起始位置和长度。