Hadoop關(guān)于處理大量小文件的問(wèn)題和解決方法
小文件指的是那些size比HDFS的block size(默認(rèn)64M)小的多的文件。如果在HDFS中存儲(chǔ)小文件,那么在HDFS中肯定會(huì)含有許許多多這樣的小文件(不然就不會(huì)用hadoop了)。而HDFS的問(wèn)題在于無(wú)法很有效的處理大量小文件。
任何一個(gè)文件,目錄和block,在HDFS中都會(huì)被表示為一個(gè)object存儲(chǔ)在namenode的內(nèi)存中,沒(méi)一個(gè)object占用150 bytes的內(nèi)存空間。所以,如果有10million個(gè)文件,沒(méi)一個(gè)文件對(duì)應(yīng)一個(gè)block,那么就將要消耗namenode 3G的內(nèi)存來(lái)保存這些block的信息。如果規(guī)模再大一些,那么將會(huì)超出現(xiàn)階段計(jì)算機(jī)硬件所能滿(mǎn)足的極限。
不僅如此,HDFS并不是為了有效的處理大量小文件而存在的。它主要是為了流式的訪問(wèn)大文件而設(shè)計(jì)的。對(duì)小文件的讀取通常會(huì)造成大量從datanode到datanode的seeks和hopping來(lái)retrieve文件,而這樣是非常的低效的一種訪問(wèn)方式。
大量小文件在mapreduce中的問(wèn)題
Map tasks通常是每次處理一個(gè)block的input(默認(rèn)使用FileInputFormat)。如果文件非常的小,并且擁有大量的這種小文件,那么每一個(gè)map task都僅僅處理了非常小的input數(shù)據(jù),并且會(huì)產(chǎn)生大量的map tasks,每一個(gè)map task都會(huì)消耗一定量的bookkeeping的資源。比較一個(gè)1GB的文件,默認(rèn)block size為64M,和1Gb的文件,沒(méi)一個(gè)文件100KB,那么后者沒(méi)一個(gè)小文件使用一個(gè)map task,那么job的時(shí)間將會(huì)十倍甚至百倍慢于前者。
hadoop中有一些特性可以用來(lái)減輕這種問(wèn)題:可以在一個(gè)JVM中允許task reuse,以支持在一個(gè)JVM中運(yùn)行多個(gè)map task,以此來(lái)減少一些JVM的啟動(dòng)消耗(通過(guò)設(shè)置mapred.job.reuse.jvm.num.tasks屬性,默認(rèn)為1,-1為無(wú)限制)。另一種方法為使用MultiFileInputSplit,它可以使得一個(gè)map中能夠處理多個(gè)split。
為什么會(huì)產(chǎn)生大量的小文件?
至少有兩種情況下會(huì)產(chǎn)生大量的小文件
1.這些小文件都是一個(gè)大的邏輯文件的pieces。由于HDFS僅僅在不久前才剛剛支持對(duì)文件的append,因此以前用來(lái)向unbounde files(例如log文件)添加內(nèi)容的方式都是通過(guò)將這些數(shù)據(jù)用許多chunks的方式寫(xiě)入HDFS中。
2.文件本身就是很小。例如許許多多的小圖片文件。每一個(gè)圖片都是一個(gè)獨(dú)立的文件。并且沒(méi)有一種很有效的方法來(lái)將這些文件合并為一個(gè)大的文件
這兩種情況需要有不同的解決方式。對(duì)于***種情況,文件是由許許多多的records組成的,那么可以通過(guò)件邪行的調(diào)用HDFS的sync()方法(和 append方法結(jié)合使用)來(lái)解決?;蛘?,可以通過(guò)些一個(gè)程序來(lái)專(zhuān)門(mén)合并這些小文件(see Nathan Marz’s post about a tool called the Consolidator which does exactly this)。
對(duì)于第二種情況,就需要某種形式的容器來(lái)通過(guò)某種方式來(lái)group這些file。hadoop提供了一些選擇:
HAR files
Hadoop Archives (HAR files)是在0.18.0版本中引入的,它的出現(xiàn)就是為了緩解大量小文件消耗namenode內(nèi)存的問(wèn)題。HAR文件是通過(guò)在HDFS上構(gòu)建一個(gè)層次化的文件系統(tǒng)來(lái)工作。一個(gè)HAR文件是通過(guò)hadoop的archive命令來(lái)創(chuàng)建,而這個(gè)命令實(shí) 際上也是運(yùn)行了一個(gè)MapReduce任務(wù)來(lái)將小文件打包成HAR。對(duì)于client端來(lái)說(shuō),使用HAR文件沒(méi)有任何影響。所有的原始文件都 visible && accessible(using har://URL)。但在HDFS端它內(nèi)部的文件數(shù)減少了。
通過(guò)HAR來(lái)讀取一個(gè)文件并不會(huì)比直接從HDFS中讀取文件高效,而且實(shí)際上可能還會(huì)稍微低效一點(diǎn),因?yàn)閷?duì)每一個(gè)HAR文件的訪問(wèn)都需要完成兩層 index文件的讀取和文件本身數(shù)據(jù)的讀取(見(jiàn)上圖)。并且盡管HAR文件可以被用來(lái)作為MapReduce job的input,但是并沒(méi)有特殊的方法來(lái)使maps將HAR文件中打包的文件當(dāng)作一個(gè)HDFS文件處理。可以考慮通過(guò)創(chuàng)建一種input format,利用HAR文件的優(yōu)勢(shì)來(lái)提高M(jìn)apReduce的效率,但是目前還沒(méi)有人作這種input format。需要注意的是:MultiFileInputSplit,即使在HADOOP-4565的改進(jìn)(choose files in a split that are node local),但始終還是需要seek per small file。
Sequence Files
通常對(duì)于“the small files problem”的回應(yīng)會(huì)是:使用SequenceFile。這種方法是說(shuō),使用filename作為key,并且file contents作為value。實(shí)踐中這種方式非常管用?;氐?0000個(gè)100KB的文件,可以寫(xiě)一個(gè)程序來(lái)將這些小文件寫(xiě)入到一個(gè)單獨(dú)的 SequenceFile中去,然后就可以在一個(gè)streaming fashion(directly or using mapreduce)中來(lái)使用這個(gè)sequenceFile。不僅如此,SequenceFiles也是splittable的,所以mapreduce 可以break them into chunks,并且分別的被獨(dú)立的處理。和HAR不同的是,這種方式還支持壓縮。block的壓縮在許多情況下都是***的選擇,因?yàn)樗鼘⒍鄠€(gè) records壓縮到一起,而不是一個(gè)record一個(gè)壓縮。
將已有的許多小文件轉(zhuǎn)換成一個(gè)SequenceFiles可能會(huì)比較慢。但是,完全有可能通過(guò)并行的方式來(lái)創(chuàng)建一個(gè)一系列的SequenceFiles。 (Stuart Sierra has written a very useful post about converting a tar file into a SequenceFile—tools like this are very useful)。更進(jìn)一步,如果有可能***設(shè)計(jì)自己的數(shù)據(jù)pipeline來(lái)將數(shù)據(jù)直接寫(xiě)入一個(gè)SequenceFile。