天天看點

[Hadoop]大量小檔案問題及解決方案

1. HDFS上的小檔案問題 http://gitlab.corp.qunar.com/jifeng.si/learningnotes/blob/master/IT/%E5%A4%A7%E6%95%B0%E6%8D%AE/Hadoop/%5BHadoop%5D%E5%A4%A7%E9%87%8F%E5%B0%8F%E6%96%87%E4%BB%B6%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88.md#1-hdfs

小檔案是指檔案大小明顯小于HDFS上塊(block)大小(預設64MB)的檔案。如果存儲小檔案,必定會有大量這樣的小檔案,否則你也不會使用Hadoop(If you’re storing small files, then you probably have lots of them (otherwise you wouldn’t turn to Hadoop)),這樣的檔案給hadoop的擴充性和性能帶來嚴重問題。當一個檔案的大小小于HDFS的塊大小(預設64MB),就将認定為小檔案否則就是大檔案。為了檢測輸入檔案的大小,可以浏覽Hadoop DFS 首頁 http://machinename:50070/dfshealth.jsp ,并點選Browse filesystem(浏覽檔案系統)。

首先,在HDFS中,任何一個檔案,目錄或者block在NameNode節點的記憶體中均以一個對象表示(中繼資料)(Every file, directory and block in HDFS is represented as an object in the namenode’s memory),而這受到NameNode實體記憶體容量的限制。每個中繼資料對象約占150byte,是以如果有1千萬個小檔案,每個檔案占用一個block,則NameNode大約需要2G空間。如果存儲1億個檔案,則NameNode需要20G空間,這毫無疑問1億個小檔案是不可取的。

其次,處理小檔案并非Hadoop的設計目标,HDFS的設計目标是流式通路大資料集(TB級别)。因而,在HDFS中存儲大量小檔案是很低效的。通路大量小檔案經常會導緻大量的尋找,以及不斷的從一個DatanNde跳到另一個DataNode去檢索小檔案(Reading through small files normally causes lots of seeks and lots of hopping from datanode to datanode to retrieve each small file),這都不是一個很有效的通路模式,嚴重影響性能。

最後,處理大量小檔案速度遠遠小于處理同等大小的大檔案的速度。每一個小檔案要占用一個slot,而task啟動将耗費大量時間甚至大部分時間都耗費在啟動task和釋放task上。

2. MapReduce上的小檔案問題 http://gitlab.corp.qunar.com/jifeng.si/learningnotes/blob/master/IT/%E5%A4%A7%E6%95%B0%E6%8D%AE/Hadoop/%5BHadoop%5D%E5%A4%A7%E9%87%8F%E5%B0%8F%E6%96%87%E4%BB%B6%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88.md#2-mapreduce

Map任務(task)一般一次處理一個塊大小的輸入(input)(預設使用FileInputFormat)。如果檔案非常小,并且擁有大量的這種小檔案,那麼每一個map task都僅僅處理非常小的input資料,是以會産生大量的map tasks,每一個map task都會額外增加bookkeeping開銷(each of which imposes extra bookkeeping overhead)。一個1GB的檔案,拆分成16個塊大小檔案(預設block size為64M),相對于拆分成10000個100KB的小檔案,後者每一個小檔案啟動一個map task,那麼job的時間将會十倍甚至百倍慢于前者。

Hadoop中有一些特性可以用來減輕bookkeeping開銷:可以在一個JVM中允許task JVM重用,以支援在一個JVM中運作多個map task,以此來減少JVM的啟動開銷(通過設定mapred.job.reuse.jvm.num.tasks屬性,預設為1,-1表示無限制)。(譯者注:如果有大量小檔案,每個小檔案都要啟動一個map task,則必相應的啟動JVM,這提供的一個解決方案就是重用task 的JVM,以此減少JVM啟動開銷);另 一種方法是使用MultiFileInputSplit,它可以使得一個map中能夠處理多個split。

3. 為什麼會産生大量的小檔案 http://gitlab.corp.qunar.com/jifeng.si/learningnotes/blob/master/IT/%E5%A4%A7%E6%95%B0%E6%8D%AE/Hadoop/%5BHadoop%5D%E5%A4%A7%E9%87%8F%E5%B0%8F%E6%96%87%E4%BB%B6%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88.md#3

至少有兩種場景下會産生大量的小檔案:

(1)這些小檔案都是一個大邏輯檔案的一部分。由于HDFS在2.x版本開始支援對檔案的append,是以在此之前儲存無邊界檔案(例如,log檔案)(譯者注:持續産生的檔案,例如日志每天都會生成)一種常用的方式就是将這些資料以塊的形式寫入HDFS中(a very common pattern for saving unbounded files (e.g. log files) is to write them in chunks into HDFS)。

(2)檔案本身就是很小。設想一下,我們有一個很大的圖檔語料庫,每一個圖檔都是一個獨一的檔案,并且沒有一種很好的方法來将這些檔案合并為一個大的檔案。

4. 解決方案 http://gitlab.corp.qunar.com/jifeng.si/learningnotes/blob/master/IT/%E5%A4%A7%E6%95%B0%E6%8D%AE/Hadoop/%5BHadoop%5D%E5%A4%A7%E9%87%8F%E5%B0%8F%E6%96%87%E4%BB%B6%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88.md#4

這兩種情況需要有不同的解決方 式。

4.1 第一種情況 http://gitlab.corp.qunar.com/jifeng.si/learningnotes/blob/master/IT/%E5%A4%A7%E6%95%B0%E6%8D%AE/Hadoop/%5BHadoop%5D%E5%A4%A7%E9%87%8F%E5%B0%8F%E6%96%87%E4%BB%B6%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88.md#4-1

對于第一種情況,檔案是許多記錄(Records)組成的,那麼可以通過調用HDFS的sync()方法(和append方法結合使用),每隔一定時間生成一個大檔案。或者,可以通過寫一個程式來來合并這些小檔案(可以看一下Nathan Marz關于Consolidator一種小工具的文章)。

4.2 第二種情況 http://gitlab.corp.qunar.com/jifeng.si/learningnotes/blob/master/IT/%E5%A4%A7%E6%95%B0%E6%8D%AE/Hadoop/%5BHadoop%5D%E5%A4%A7%E9%87%8F%E5%B0%8F%E6%96%87%E4%BB%B6%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88.md#4-2

對于第二種情況,就需要某種形式的容器通過某種方式來對這些檔案進行分組。Hadoop提供了一些選擇:

4.2.1 HAR File http://gitlab.corp.qunar.com/jifeng.si/learningnotes/blob/master/IT/%E5%A4%A7%E6%95%B0%E6%8D%AE/Hadoop/%5BHadoop%5D%E5%A4%A7%E9%87%8F%E5%B0%8F%E6%96%87%E4%BB%B6%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88.md#4-2-1-har-file

Hadoop Archives (HAR files)是在0.18.0版本中引入到HDFS中的,它的出現就是為了緩解大量小檔案消耗NameNode記憶體的問題。HAR檔案是通過在HDFS上建構一個分層檔案系統來工作。HAR檔案通過hadoop archive指令來建立,而這個指令實 際上是運作了一個MapReduce作業來将小檔案打包成少量的HDFS檔案(譯者注:将小檔案進行合并幾個大檔案)。對于client端來說,使用HAR檔案沒有任何的改變:所有的原始檔案都可見以及可通路(隻是使用har://URL,而不是hdfs://URL),但是在HDFS中中檔案數卻減少了。

讀取HAR中的檔案不如讀取HDFS中的檔案更有效,并且實際上可能較慢,因為每個HAR檔案通路需要讀取兩個索引檔案以及還要讀取資料檔案本身(如下圖)。盡管HAR檔案可以用作MapReduce的輸入,但是沒有特殊的魔法允許MapReduce直接操作HAR在HDFS塊上的所有檔案(although HAR files can be used as input to MapReduce, there is no special magic that allows maps to operate over all the files in the HAR co-resident on a HDFS block)。 可以考慮通過建立一種input format,充分利用HAR檔案的局部性優勢,但是目前還沒有這種input format。需要注意的是:MultiFileInputSplit,即使在HADOOP-4565(

https://issues.apache.org/jira/browse/HADOOP-4565)的改進,但始終還是需要每個小檔案的尋找。我們非常有興趣看到這個與SequenceFile進行對比。

 在目前看來,HARs可能最好僅用于存儲文檔(At the current time HARs are probably best used purely for archival purposes.)。

4.2.2 SequenceFile http://gitlab.corp.qunar.com/jifeng.si/learningnotes/blob/master/IT/%E5%A4%A7%E6%95%B0%E6%8D%AE/Hadoop/%5BHadoop%5D%E5%A4%A7%E9%87%8F%E5%B0%8F%E6%96%87%E4%BB%B6%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88.md#4-2-2-sequencefile

通常對于"小檔案問題"的回應會是:使用序列檔案(SequenceFile)。這種方法的思路是,使用檔案名(filename)作為key,并且檔案内容(file contents)作為value,如下圖。在實踐中這種方式非常有效。我們回到10,000個100KB小檔案問題上,你可以編寫一個程式将它們放入一個單一的SequenceFile,然後你可以流式處理它們(直接處理或使用MapReduce)操作SequenceFile。這樣同時會帶來兩個優勢:(1)SequenceFiles是可拆分的,是以MapReduce可以将它們分成塊并獨立地對每個塊進行操作;(2)它們同時支援壓縮,不像HAR。 在大多數情況下,塊壓縮是最好的選擇,因為它将壓縮幾個記錄為一個塊,而不是一個記錄壓縮一個塊。(Block compression is the best option in most cases, since it compresses blocks of several records (rather than per record))。

将現有資料轉換為SequenceFile可能很慢。 但是,完全可以并行建立SequenceFile的集合。(It can be slow to convert existing data into Sequence Files. However, it is perfectly possible to create a collection of Sequence Files in parallel.)Stuart Sierra寫了一篇關于将tar檔案轉換為SequenceFile的文章(

https://stuartsierra.com/2008/04/24/a-million-little-files

 ),像這樣的工具是非常有用的,我們應該多看看。展望未來,最好設計資料管道,将源資料直接寫入SequenceFile(如果可能),而不是作為中間步驟寫入小檔案。

與HAR檔案不同,沒有辦法列出SequenceFile中的所有鍵,是以不能讀取整個檔案。Map File,就像對鍵進行排序的SequenceFile,隻維護了部分索引,是以他們也不能列出所有的鍵,如下圖。

SequenceFile是以Java為中心的。 TFile(

https://issues.apache.org/jira/browse/HADOOP-4565

 )設計為跨平台,并且可以替代SequenceFile,不過現在還不可用。

4.2.3 HBase http://gitlab.corp.qunar.com/jifeng.si/learningnotes/blob/master/IT/%E5%A4%A7%E6%95%B0%E6%8D%AE/Hadoop/%5BHadoop%5D%E5%A4%A7%E9%87%8F%E5%B0%8F%E6%96%87%E4%BB%B6%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88.md#4-2-3-hbase

如果你生産很多小檔案,那麼根據通路模式,不同類型的存儲可能更合适(If you are producing lots of small files, then, depending on the access pattern, a different type of storage might be more appropriate)。HBase以Map Files(帶索引的SequenceFile)方式存儲資料,如果您需要随機通路來執行MapReduce式流式分析,這是一個不錯的選擇( HBase stores data in MapFiles (indexed SequenceFiles), and is a good choice if you need to do MapReduce style streaming analyses with the occasional random look up)。如果延遲是一個問題,那麼還有很多其他選擇 - 參見Richard Jones對鍵值存儲的調查(

http://www.metabrew.com/article/anti-rdbms-a-list-of-distributed-key-value-stores/

)。

原文:

http://blog.cloudera.com/blog/2009/02/the-small-files-problem/