hadoop解決小文件思路

1.什麼是小文件

小文件一般是指明顯小於Hadoop的block size的文件。Hadoop的block size一般是64MB,128MB或者256MB,現在一般趨向於設置的越來越大。後文要討論的內容會基於128MB,這也是CDH中的默認值。爲了方便後面的討論,Fayson這裏假定如果文件大小小於block size的75%,則定義爲小文件。但小文件不僅是指文件比較小,如果Hadoop集羣中的大量文件略大於block size,同樣也會存在小文件問題。

比如,假設block size是128MB,但加載到Hadoop的所有文件都是136MB,就會存在大量8MB的block。處理這種“小塊”問題你可以調大block size來解決,但解決小文件問題卻要複雜的多。

2.小文件是怎麼來的

一個Hadoop集羣中存在小文件問題是很正常的,可能的原因如下:

1. 現在我們越來越多的將Hadoop用於(準)實時計算,在做數據抽取時處理的頻率可能是每小時,每天,每週等,每次可能就只生成一個不到10MB的文件。

2.數據源有大量小文件,未做處理直接拷貝到Hadoop集羣。

3.MapReduce作業的配置未設置合理的reducer或者未做限制,每個reduce都會生成一個獨立的文件。另外如果數據傾斜,導致大量的數據都shuffle到一個reduce,然後其他的reduce都會處理較小的數據量並輸出小文件。

3.爲什麼Hadoop會有小文件問題

Hadoop的小文件問題主要是會對NameNode內存管理和MapReduce性能造成影響。Hadoop中的每個目錄、文件和block都會以對象的形式保存在NameNode的內存中。根據經驗每個對象在內存中大概佔用150個字節。如果HDFS中保存2000萬個文件,每個文件都在同一個文件夾中,而且每個文件都只有一個block,則NameNode需要6GB內存。如果只有這麼點文件,這當然也沒有什麼問題,但是隨着數據量的增長集羣的擴容,最終會達到單臺NameNode可以處理的文件(block)數量的上限。基於同樣的假設,如果HDFS中保存的數據文件增長到10億個,則NameNode需要300GB內存。以下Fayson帶大家看看300GB內存的NameNode會有什麼影響:

1.當NameNode重啓時,它都需要從本地磁盤讀取每個文件的元數據,意味着你要讀取300GB數據到內存中,不可避免導致NameNode啓動時間較長。

2.一般來說,NameNode會不斷跟蹤並檢查每個數據塊的存儲位置。這是通過DataNode的定時心跳上報其數據塊來實現的。數據節點需要上報的block越多,則也會消耗越多的網絡帶寬/時延。即使節點之間是高速網絡(萬兆/光纖),但不可避免的會帶來一些不好的影響。

3.NameNode本身使用300G內存,相當於JVM你需要配置300GB的heap,對於JVM來說本來就存在穩定性的風險,比如GC時間較長。

所以優化和解決小文件問題基本是必須的。如果可以減少集羣上的小文件數,則可以減少NameNode的內存佔用,啓動時間以及網絡影響。

4.MapReduce的性能問題

如果集羣中有大量小文件,會降低MapReduce的處理性能,無論是Hive,Pig還是Java MapReduce,當然其實其他計算引擎比如Spark,Impala也會受到影響。第一個原因是大量小文件意味着大量的隨機磁盤IO。磁盤IO通常是MapReduce性能的最大瓶頸之一,在HDFS中對於相同數量的數據,一次大的順序讀取往往優於幾次隨機讀取的性能。如果可以將數據存儲在較少,而更大的一些block中,可以降低磁盤IO的性能影響。

性能下降的第二個原因有點複雜,需要了解MapReduce如何處理文件和資源調度。當MapReduce任務啓動時,每個數據block會被分配爲一個map任務。HDFS中的每個文件至少是一個block。如果你有10000個文件,而且每個文件10MB,那麼這個MapReduce作業會被分配爲10000個map任務。一般來說每個Hadoop的map任務會運行在自己的JVM中,所以會帶來10000個JVM的啓動和關閉的資源開銷。

集羣的資源是有限的,爲了方便計算,假設我們在YARN的配置中爲每個NodeManager配置20個vcore,那麼爲了同時運行10000個mapper,你需要500臺節點。大多數Hadoop集羣都小於這個規模,所以一般情況下大量map任務可能只能排隊等待ResourceManager來分配資源。如果你只有10臺機器,那麼總共只有200個vcore,這個排隊的隊列會較大,相應的處理時間也會變的較長。另外,往往你的集羣中可能不止這一個作業。

如果10000個10MB文件換成800個128MB的文件,那麼你就只需要800個map任務。相當於減少了一個數量級的JVM維護時間,同時也優化了磁盤IO。儘管一個單獨的map任務處理一個128MB的文件比一個10MB的文件時間要慢,但是整個作業的總運行時間肯定可以降低一個數量級。

5.解決NameNode的內存問題

上面的內容提到過每個block的元數據都需要加載到NameNode的內存中,這導致一個Hadoop集羣在NameNode中存儲的對象是有上限的,並且對象太多會帶來啓動時間較長以及網絡延遲的問題。常見的有兩種解決方案,減少集羣的NameNode中的對象數量,或者以某種方式讓NameNode使用更多的“內存”但不會導致較長的啓動時間,這就是Hadoop Archive(HAR)文件和NameNode聯邦。

5.1.Hadoop Archive Files

Hadoop archive files通過將許多小文件打包到更大的HAR文件中來緩解NameNode內存問題,類似於Linux上的TAR文件。這樣可以讓NameNode只處理單個HAR文件,而不是數十個或數百個小文件。可以使用har://前綴而不是hdfs://來訪問HAR文件中的文件。HAR文件是基於HDFS中已有的文件創建的。因此,HAR文件不僅可以合併從數據源抽取到HDFS中的數據,也可以合併通過正常的MapReduce處理創建的數據。HAR文件可以獨立的用於解決小文件問題,除了HDFS,沒有其他的依賴。

雖然HAR文件減少了NameNode中小文件對內存的佔用,但訪問HAR文件內容性能可能會更低。HAR文件仍然隨機存儲在磁盤上,並且讀取HAR內的文件需要訪問兩個索引 - 一個用於NameNode找到HAR文件本身,一個用於在HAR文件內找到小文件的位置。在HAR中讀取文件實際上可能比讀取存儲在HDFS上的相同文件慢。MapReduce作業的性能同樣會受到影響,因爲它仍舊會爲每個HAR文件中的每個文件啓動一個map任務。

所以這裏我們需要有一個權衡(trade-off),HAR文件可以解決NameNode內存問題,但同時會降低讀取性能。如果你的小文件主要用於存檔,並且不經常訪問,那麼HAR文件是一個很好的解決方案。如果小文件經常要被讀取或者處理,那麼可能需要重新考慮解決方案。

5.2.NameNode聯邦

NameNode聯邦允許你在一個集羣中擁有多個NameNode,每個NameNode都存儲元數據對象的子集。這樣可以讓所有的元數據對象都不止存儲在單個機器上,也消除了單個節點的內存限制,因爲你可以擴容。這聽上去是一個很美麗的方案,但其實它也有侷限性。

NameNode聯邦隔離了元數據對象 - 僅僅只有某一個NameNode知道某一個特定的元數據對象在哪裏,意思就是說如果你想找到某個文件,你必須知道它是保存在哪個NameNode上的。如果你的集羣中有多個租戶和/或隔離的應用程序,那使用NameNode聯邦是挺不錯的,你可以通過租戶或者應用程序來隔離元數據對象。但是,如果要在所有的應用程序之間共享數據,則該方法其實也並不是完美的。

由於NameNode聯邦並不會改變集羣中對象或者塊的數量,所以它並沒有解決MapReduce的性能問題。相反,聯邦會增加Hadoop集羣安裝和維護的複雜度。所以我們說聯邦可以解決小文件問題,倒不如說它提供了一種辦法讓你“隱藏”小文件。

6.解決MapReduce性能問題

根據之前討論的內容,MapReduce性能問題主要是由隨機磁盤IO和啓動/管理太多的map任務組合引起的。解決方案似乎很明顯 - 合併小文件,然而這個事往往說起來容易做起來難。以下討論一下幾種解決方案:

注:雖然本章名爲解決MapReduce的性能問題,但其實同樣也是爲了解決NameNode的壓力,以及解決其他計算引擎比如Impala/Spark的性能問題。

1.修改數據抽取方法/間隔

2.批量文件合併

3.Sequence文件

4.HBase

5.S3DistCp (如果使用Amazon EMR)

6.使用CombineFileInputFormat

7.通過Hive合併小文件

8.使用Hadoop的追加特性

6.1.修改數據抽取方法/間隔

解決小文件問題的最簡單方法就是在生成階段就進行杜絕。如果是由數據源產生大量小文件並直接拷貝到Hadoop,可以調研瞭解數據源是否能生成一些大文件,或者從數據源到HDFS的數據抽取過程中進行數據處理合併小文件。如果每小時只抽取10MB的數據,考慮是否改爲每天一次,這樣創建1個240MB的文件而不是24個10MB的文件。但是,你可能無法控制數據源的改動配合或業務對數據抽取間隔的需求,這樣小文件問題無法避免,這時可能需要考慮其他的解決方案。

6.2.批量文件合併

當產生小文件是不可避免時,文件合併是常見的解決方案。使用這種方法,你可以定期運行一個MapReduce任務,讀取某一個文件夾中的所有小文件,並將它們重寫爲較少數量的大文件。比如一個文件夾中有1000個文件,你可以在一個MapReduce任務中指定reduce的數量爲5,這樣1000個輸入文件會被合併爲5個文件。隨後進行一些簡單的HDFS文件/文件夾操作(將新文件覆蓋回原目錄),則可以將NameNode的內存使用減少到200分之1,並且可以提高以後MapReduce或其他計算引擎對同一數據處理的性能。

舉例如果使用Pig,只需要2行包括load和store語句即可以實現。比如合併文本文件:

在Hive或Java MapReduce中實現同樣比較容易。這些MapReduce作業運行同樣需要集羣資源,所以建議調度在生產系統非繁忙時間段執行。但是,應該定期執行這種合併的MapReduce作業,因爲小文件隨時或者幾乎每天都可能產生。但這個合併程序需要有額外的邏輯來判斷存在大量小文件的目錄,或者你自己是知道哪些目錄是存在大量小文件的。因爲假如某個目錄只有3個文件,運行合併作業遠不如合併一個500個文件的文件夾的性能優勢提升明顯。

檢查所有文件夾並確認哪些文件夾中的小文件需要合併,目前主要是通過自定義的腳本或程序,當然一些商業工具也能做,比如Pentaho可以迭代HDFS中的一組文件夾,找到最小合併要求的文件夾。這裏還推薦另外一個開源工具File Crush

https://github.com/edwardcapriolo/filecrush/
File Crush沒有專業支持,所以無法保證它可以與Hadoop的後續版本一直保持兼容。

批量合併文件的方法無法保留原始文件名,如果原始文件名對於你瞭解數據來源非常重要,則批量合併文件的方法也不適用。但一般來說,我們一般只會設計HDFS的各級目錄的文件名,而不會細化到每個文件的名字,所以理論來說這種方法問題也不大。

6.3.Sequence文件

當需要維護原始文件名時,常見的方法是使用Sequence文件。 在此解決方案中,文件名作爲key保存在sequence文件中,然後文件內容會作爲value保存。下圖給出將一些小文件存儲爲sequence文件的示例:

如果一個sequence文件包含10000個小文件,則同時會包含10000個key在一個文件中。sequence文件支持塊壓縮,並且是可被拆分的。這樣MapReduce作業在處理這個sequence文件時,只需要爲每個128MB的block啓動一個map任務,而不是每個小文件啓動一個map任務。當你在同時抽取數百個或者數千個小文件,並且需要保留原始文件名時,這是非常不錯的方案。

但是,如果你一次僅抽取少量的小文件到HDFS,則sequence文件的方法也不太可行,因爲sequence文件是不可變的,無法追加。比如3個10MB文件將產生1個30MB的sequence文件,根據本文前面的定義,這仍然是一個小文件。另外一個問題是如果需要檢索sequence文件中的文件名列表則需要遍歷整個文件。

另外一個問題是Hive並不能較好的處理由該方法合併出來的sequence文件。Hive將value中的所有數據視爲單行。這樣會導致Hive查看這些數據不方便,因爲以前小文件中的一行的所有數據也是Hive中的單行,即相當於只有一個字段。同時,Hive沒辦法訪問這種sequence的key,即文件名。當然你可以自定義Hive serde來實現,不過這個超過了本文需要討論的範圍。

6.4.HBase

解決小文件問題,除了HDFS存儲外,當然還可以考慮HBase列式存儲。使用HBase可以將數據抽取過程從生成大量小HDFS文件更改爲以逐條記錄寫入到HBase表。如果你對數據訪問的需求主要是隨機查找或者叫點查,則HBase是最好的選擇。HBase在架構上就是爲快速插入,存儲大量數據,單個記錄的快速查找以及流式數據處理而設計的。但如果你對數據訪問的需求主要是全表掃描,則HBase不是最適合的。

可以基於HBase的表的數據創建Hive表,但是查詢這種Hive表對於不同的查詢類型性能會不一樣。當查詢單行或者範圍查找時,Hive on HBase會表現不錯,但是如果是全表掃描則效率比較低下,大多數分析查詢比如帶group by的語句都是全表掃描。

使用HBase,可以較好的應對實時數據寫入以及實時查詢的場景。但是如何分配和平衡HBase與集羣上其他的組件的資源使用,以及HBase本身運維都會帶來額外的運維管理成本。另外,HBase的性能主要取決於你的數據訪問方式,所以在選擇HBase解決小文件問題之前,應該進行仔細調研和設計。

6.5.S3DistCp (如果使用Amazon EMR)

此解決方案僅適用於Amazon EMR的用戶,當然你在AWS中使用CDH也一樣。Amazon EMR集羣一般設計爲短期存儲,而在S3中持久化保存數據。即使使用S3,依舊存在小文件問題,所以這時需要選擇S3DistCp。

S3DistCp是由Amazon提供的一個工具,用於分佈式將S3中的數據拷貝到臨時的HDFS或其他S3 bucket。這個工具可以通過配置groupBy和targetSize參數來將文件合併到一起。如果S3中存儲了數千個EMR需要處理的小文件時,這個工具是一個不錯的選擇。S3DistCp通過連接許多小文件並導入到HDFS中,據報道,該方式的性能也非常優秀。

S3DistCp這個工具跟之前文章提到的批量合併文件的方法其實是類似的,只是說Amazon給你提供了一個現成的工具。

6.6.使用CombineFileInputFormat

CombineFileInputFormat是Hadoop提供的抽象類,它在MapReduce讀取時合併小文件。合併的文件不會持久化到磁盤,它是在一個map任務中合併讀取到的這些小文件。好處是MapReduce可以不用爲每個小文件啓動一個map任務,而且因爲是自帶的實現類,你不用額外將小文件先提前合併。這解決了MapReduce作業啓動太多map任務的問題,但是因爲作業仍然在讀取多個小文件,隨機磁盤IO依舊是一個問題。另外,CombineFileInputFormat大多數情況下都不會考慮data locality,往往會通過網絡從其他節點拉取數據。

爲了實現這個,需要爲不同的文件類型編寫Java代碼擴展CombineFileInputFormat類。這樣實現一個自定義的類後,就可以配置最大的split大小,然後單個map任務會讀取小文件並進行合併直到滿足這個大小。以下有一個示例參考:

http://www.idryman.org/blog/2013/09/22/process-small-files-on-hadoop-using-combinefileinputformat-1/
當然如果是Hive作業有簡單的方式,直接配置以下參數即可:

hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat
set mapreduce.input.fileinputformat.split.maxsize=1073741824  
set mapreduce.input.fileinputformat.split.minsize=1073741824
以上是以Hive的單個map作業合併小文件到1GB爲示例。

注意以上無論是MapReduce代碼實現方式還是Hive,因爲合併的文件並不會持久化保存到磁盤,因此CombineFileInputFormat方式並不會緩解NameNode內存管理問題。只是提高MapReduce或者Hive作業的性能。

6.7.通過Hive合併小文件

如果你在使用Hive時因爲“create table as”或“insert overwrite”語句輸出了小文件,你可以通過設置一些參數來緩解。通過設置這些參數。Hive會在本身的SQL作業執行完畢後會單獨起一個MapReduce任務來合併輸出的小文件。

注意這個設置僅對Hive創建的文件生效,比如你使用Sqoop導數到Hive表,或者直接抽數到HDFS等,該方法都不會起作用。涉及的配置參數如下:

6.8.使用Hadoop的追加特性

有些人可能會問,爲什麼不使用Hadoop自帶的Append特性來解決小文件問題,即當第一次輸出是小文件時,後面的輸出可以繼續追加這些小文件,讓小文件變成大文件,這聽上去是個不錯的建議,但其實做起來挺難的,因爲Hadoop生態系統裏的工具都不支持包括Flume,Sqoop,Pig,Hive,Spark,Impala和Java MapReduce。比如MapReduce任務有一個規定,輸出結果目錄必須是在之前不存在的。所以MapReduce作業肯定無法使用Append特性,由於Sqoop,Pig和Hive都使用了MapReduce,所以這些工具也不支持Append。Flume不支持Append主要是因爲它假設經過一段時間比如幾秒,多少字節,多少事件數或者不活動的秒數,Flume就會關閉文件而不再打開它。

如果你想使用Append來解決小文件問題,則你需要自己編寫特定的程序來追加到現有的文件。另外,當集羣中其他應用程序如果正在讀取或處理這些需要追加的文件,你就不能使用自定義的MapReduce或者Spark程序來追加這些文件了。所以如果要使用這種方法,你最好還是謹慎考慮。
--------------------- 
 
原文:https://blog.csdn.net/LINBE_blazers/article/details/82861981 
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章