企業使用Hadoop的重大挑戰:如何在HDFS中組織和使用資料?

大資料頻道發表於2018-09-28

在上一章,我們研究瞭如何在MapReduce中使用不同的檔案格式,以及哪些格式適合儲存資料(往期文章請檢視文末連結)。一旦熟練掌握了資料格式的概念和使用法則,就該思考如何在HDFS中組織資料了。在設計Hadoop系統時,企業應該儘早瞭解如何訪問資料,以便最佳化將支援的重要用例,這一點非常重要。

本文作為《Hadoop從入門到精通》大型選題的第四章,主要講解影響企業資料決策的幾大因素,例如是否需要提供對資料的SQL訪問,哪些欄位用於查詢資料以及訪問時間SLA。同時,企業應該確保不使用大量小檔案對HDFS NameNode應用不必要的堆壓力,並且還需瞭解如何使用大量輸入資料集。

本章主要研究在HDFS中有效儲存和訪問大資料的方法。首先介紹在HDFS中佈局資料的方法,並介紹分割槽和組織資料的方式以減輕NameNode堆壓力。然後,我們將討論一些資料訪問模式,以幫助處理不同的資料及龐大的資料集。最後,我們將壓縮視為一種有效方式以最大化儲存和處理資料。當然,本文只是該章的第一節,完整章節還請持續關注本專題。

本章適用於對HDFS概念有基本瞭解,並且具有直接使用HDFS經驗的工程師或研究人員。

4.1 組織資料

組織資料是使用Hadoop最具挑戰性的方面之一。企業中存在來自不同部門和不同人員的壓力,例如資料科學家和叢集管理員,每個人對資料都有自己的要求。更重要的是,這些要求通常是在資料應用程式投入生產並且已經積累大量資料之後提出。

Hadoop中組織資料有多個維度。首先,我們需要學習如何在HDFS中組織資料,之後將面臨實際操作問題,例如對資料進行分割槽和壓縮,決定是否啟用Kerberos來保護叢集以及管理和傳遞資料更改。本章目標是關注組織資料過程中一些複雜問題,包括資料分割槽和壓縮,讓我們從在HDFS中構建資料開始吧。

4.1.1 目錄和檔案佈局

定義資料組織方式的叢集範圍標準是一項值得探究的工作,因為它可以更容易地發現資料位置,並且應用和管理可透過資料儲存解決的問題。因為我們在檔案系統可以表達的範圍內工作,所以安排資料的常用方法是建立與企業組織或功能結構一致的層級結構。例如,如果在分析團隊工作並且正在將新資料集引入叢集,那麼組織目錄的一種方法將如圖4.1所示。

圖4.1 HDFS目錄佈局示例

在學習之前,企業最好已經確定了一種資料格式,例如Avro,這可以隨時間推移改進模式。 透過在結構中貼上版本號,你可以靈活地轉移到任何一種新的資料格式,並使用目錄路徑傳達不同的檔案格式。

在目錄中放置版本號面臨的唯一挑戰是如何將更改有效傳達給資料使用者。如果確實有該問題,企業可以嘗試將HCatalog視為從客戶端抽象出的一種資料格式。

按日期和其他欄位進行分割槽

你可能會使用目錄結構來模擬企業不同部門的資料演變需求,但為什麼還需要按日期進一步分割槽?這是Hive早期使用的一種技術,可以幫助加快查詢速度。如果將所有資料放入一個目錄中,那麼每次訪問資料實際都在進行等效的Hadoop全表掃描。相反,根據對資料的訪問方式對資料進行分割槽更為明智。

因為我們事先很難確切知道如何訪問資料,但按資料生成日期對資料進行分段是合理的分割槽嘗試。如果資料沒有日期,那麼請與資料生產者討論新增日期,因為建立事件或記錄的時間是應始終捕獲的關鍵資料點。

4.1.2 資料層

在2012年的Strata演講中,Eric Sammer提出了對資料儲存進行分層的想法,這也很好地與Nathan Marz的Lambda架構的主要原則相關——永遠不會刪除或修改原始資料。

乍一看,這似乎沒有任何意義。因為你只需要提取資料來源的重要部分,其餘部分可以捨棄,畢竟保留全部原始資料有些浪費,特別是如果有部分未被積極使用,但你很難保證未來堅決不會從未使用的資料中提取到有價值的資訊。

我們的軟體偶爾也會出現錯誤。想象一下,如果你正在軟體中傳輸資料,獲得想要的結果之後,你決定丟棄源資料,但是你突然發現操作邏輯中存在錯誤,因為丟棄了源資料將無法返回並重新生成結果。

因此,建議企業根據以下層級考慮資料儲存:

  • 原始資料是第一層。這是從源捕獲的未更改資料,永遠不應修改此層資料,因為生成衍生物或聚合的邏輯可能存在錯誤,如果丟棄原始資料,則無法在發生錯誤時恢復。

  • 第二層:從原始資料建立派生資料。該層,你可以執行重複資料刪除和任何其他資料治理要求。

  • 第三層:彙總資料。這是根據派生資料計算出來的,可能會被輸入HBase系統或選擇的NoSQL系統,以便在生產和分析過程中實時訪問資料。

資料層也應該在目錄佈局中有所展示,以便使用者可以輕鬆區分這些層。

一旦確定了用於對資料進行分割槽的目錄佈局,下一步就是弄清楚如何將資料匯入這些分割槽。

4.1.3 分割槽

分割槽是獲取資料集並將其拆分為不同部分的過程。這些部分是分割槽,代表了對資料的有意義劃分。資料中的公共分割槽示例是時間,因為它允許查詢資料的人在特定的時間視窗內縮小範圍。4.1.2節將時間作為決定在HDFS中佈局資料的關鍵因素。

如果在HDFS中有一個大型資料集,需要對其進行分割槽,應該怎麼做呢?本節將介紹兩種可用於對資料進行分割槽的方法。

使用MultipleOutputs對資料進行分割槽

想象一下,你將股票價格資料流入HDFS,並且希望編寫MapReduce作業,以根據當天的股票報價對資料進行分割槽。為此,你需要在單個任務中寫入多個輸出檔案,讓我們來看看如何實現這一目標。

問題

需要對資料進行分割槽,但大多數輸出格式僅為每個任務建立一個輸出檔案。

解決方案

使用與MapReduce捆綁在一起的MultipleOutputs類。

討論

Hadoop中的MultipleOutputs類繞過了在Hadoop中生成輸出的正常通道。它提供了一個單獨的API來寫入分割槽輸出,並將輸出直接寫入HDFS中的任務嘗試目錄,這可以繼續提供給作業的Context物件的標準write方法來收集輸出,還可以使用MultipleOutputs來編寫分割槽輸出。當然,你也可以選擇僅使用MultipleOutputs類並忽略標準的基於上下文的輸出。

在此技術中,你將使用MultipleOutputs按報價日期對股票進行分割槽。第一步是設定MultipleOutputs以便在工作中使用。 在驅動程式中,你將指明輸出格式以及鍵和值型別:

為什麼需要在驅動程式中命名輸出?你可能想知道為什麼MultipleOutputs要求指定輸出名稱(前面示例中的分割槽)。這是因為MultipleOutputs支援兩種操作模式 - 靜態分割槽和動態分割槽。

如果提前知道分割槽名稱,靜態分割槽可以正常工作,這為每個分割槽指定不同輸出的格式提供了額外的靈活性(只需要對具有不同命名輸出的MultipleOutputs.addNamedOutput進行多次呼叫)。對於靜態分割槽,在呼叫addNamedOutput時指定的輸出名稱與在mapper或reducer中發出輸出時使用的名稱相同。

命名輸出主要針對於動態分割槽,因為在大多數情況下,操作者不會提前知道分割槽名稱。在這種情況下,仍然需要提供輸出名稱,當然這也可能被忽略,因為可以在mapper或reducer中動態指定分割槽名稱。

正如以下程式碼所示,map(或reduce)類將獲取MultipleOutputs例項的控制程式碼,然後使用write方法寫入分割槽輸出。請注意,第三個引數是分割槽名稱,即股票日期:

不要忘記close()方法! 在任務完成後呼叫MultipleOutputs上的close方法非常重要。否則,輸出可能會丟失資料,甚至造成檔案損壞。

正如你在以下輸出中所看到的,執行上一個示例會為單個mapper生成許多分割槽檔案。我們還可以看到原始map輸出檔案,該檔案為空,因為沒有使用Context物件發出任何記錄:

此示例使用了僅map作業,但在生產中,你可能希望限制建立分割槽的任務數。有兩種方法可以做到這一點:

  • 使用CombineFileInputFormat或自定義輸入格式來限制作業中的mapper數量。

  • 使用reducer明確指定合理數量的reducer。

總結

MultipleOutputs有很多值得關注的東西:它支援“舊”和“新”MapReduce API,並支援多種輸出格式類。但是,使用MultipleOutputs應該注意一些問題:

  • 在mapper中使用MultipleOutput時請記住,最終會得到NumberOfMappers * NumberOfPartition輸出檔案,根據經驗,這會降低具有大量兩個值的叢集!

  • 每個分割槽在任務期間都會產生HDFS檔案控制程式碼開銷。

  • 你經常會遇到大量小檔案,這些檔案會在分割槽程式的多次使用中累積。當然,這可以採用壓縮策略來緩解(有關詳細資訊,請參閱第4.1.4節)。

  • 儘管Avro附帶了AvroMultipleOutputs類,但由於程式碼效率低下,速度很慢。

除了MultipleOutputs方法之外,Hadoop還附帶了一個MultipleOutputFormat類,其功能類似於MultipleOutputs,主要缺陷是隻支援舊的MapReduce API,並且只有一種輸出格式可用於所有分割槽。

我們可以使用的另一種分割槽策略是MapReduce分割槽程式,它可以幫助減輕可能使用MultipleOutputs生成的大量檔案。

使用自定義MapReduce分割槽程式

另一種分割槽方法是使用MapReduce中內建的分割槽工具。預設情況下,MapReduce使用分割槽程式來計算每個map輸出鍵的雜湊,並對reducer的數量執行計數以確定應將記錄傳送到哪個reducer。我們可以編寫自定義分割槽程式,然後根據分割槽方案路由記錄來控制分割槽生成方式。

這種技術比以前的技術有額外的好處,因為通常會得到更少的輸出檔案,每個reducer只會建立一個輸出檔案,而MultipleOutputs則每個map或reduce任務都會產生N個輸出檔案,每個分割槽一個。

問題

對輸入資料進行分割槽。

解決方案

編寫自定義分割槽程式,將記錄分割槽到適當的reducer。

討論

自定義分割槽器可向MapReduce驅動程式公開輔助方法,允許定義從日期到分割槽的map,並將此map寫入作業配置。然後,當MapReduce載入分割槽器時,MapReduce呼叫setConf方法; 在分割槽器中,我們將讀取mapping到map中,隨後在分割槽時使用。

驅動程式程式碼需要設定自定義分割槽程式配置。此示例中的分割槽是日期,如果希望確保每個reducer對應唯一的日期。股票示例資料有10個唯一日期,因此可以使用10個reducer配置作業,還可以呼叫先前定義的分割槽幫助程式函式設定將每個唯一日期map到唯一reducer配置。

除了從輸入資料中提取股票日期並將其作為輸出key之外,mapper幾乎沒有其他功能:

執行前面示例的命令如下:

此作業將生成10個輸出檔案,每個檔案包含當天的股票資訊。

總結

使用MapReduce框架自然地對資料進行分割槽可以帶來以下幾個優點:

  • 對分割槽中的資料進行排序,因為shuffle將確保對流式傳輸到reducer的所有資料進行排序,這允許對資料使用最佳化的連線策略。

  • 可以對reducer中的資料進行重複資料刪除,這也是shuffle階段的一個好處。

使用這種技術需要注意的主要問題是資料偏差,如果希望確保儘可能地將負載分散到reducer上,資料會存在自然偏差,這可能是一個問題。例如,如果分割槽是天數,那麼大多數記錄可能是一天,而可能只有一些記錄可用於前一天或後一天。 在這種情況下,理想情況是將大多數Reducer分配到一天對記錄進行分割槽,然後可能在前一天或後一天分配一兩個記錄,也可以對輸入進行取樣,並根據樣本資料動態確定最佳reducer數量。

生成分割槽輸出後,下一個挑戰是處理分割槽產生的大量潛在小檔案。

4.1.4 資料壓縮

在HDFS中有小檔案是無法避免的,也許你正在使用類似於前面描述的分割槽技術,或者資料可能以小檔案大小有機地落在HDFS中。 無論哪種方式,都將暴露HDFS和MapReduce的一些弱點,比如:

  • Hadoop的NameNode將所有HDFS後設資料保留在記憶體中,以實現快速後設資料操作。雅虎估計每個檔案平均佔用記憶體600位元組的空間,轉換為10億個檔案的後設資料開銷,總計60 GB,所有檔案都需要儲存在NameNode記憶體中。即使是當今的中端伺服器RAM容量,這也代表著單個程式的大量記憶體。

  • 如果對MapReduce作業的輸入是大量檔案,則將執行的mapper數量(假設檔案是文字或可拆分的)將等於這些檔案佔用的塊數。如果執行一個輸入數千或數百萬個檔案的MapReduce作業,那麼將花費更多時間在核心層處理建立和銷燬map任務流程,而不是實際工作。

  • 最後,如果在具有排程程式的受控環境中執行,則可能會限制MapReduce作業可以使用的任務數。由於每個檔案(預設情況下)至少會生成一個map任務,因此這可能會導致作業被排程程式拒絕。

如果認為不會有這個問題,那就再想一想,檔案百分比小於HDFS塊大小嗎?小多少?50%?70%?還是90%?如果大資料專案突然需要擴充套件以處理大幾個數量級的資料集,該怎麼辦?這不是你首先使用Hadoop的原因嗎?擴充套件就要新增更多節點,企業肯定不接受重新設計Hadoop並處理遷移檔案。因此,在設計階段最好儘早思考和準備這些可能的問題。

本節介紹了一些可用於在HDFS中組合資料的技術。首先討論一個名為filecrush的實用程式,它可以將小檔案壓縮在一起以建立較少數量的較大檔案。

使用filecrush壓縮資料

壓縮是將小檔案組合在一起生成更大檔案的行為,這有助於緩解NameNode上的堆壓力。

就與Hadoop版本的相容性而言,filecrush實用程式僅適用於Hadoop版本1。但是compacter()以及Archive與Hadoop 版本2相容。

問題

希望壓縮小檔案以減少NameNode需要保留在記憶體中的後設資料

解決方案

使用filecrush實用程式。

討論

filecrush實用程式組合或壓縮多個小檔案以形成更大的檔案。該實用程式非常複雜,並能夠

  • 確定壓縮檔案的大小閾值(並透過關聯,保留足夠大的檔案)

  • 指定壓縮檔案的最大大小

  • 使用不同的輸入和輸出格式以及不同的輸入和輸出壓縮編解碼器(用於移動到不同的檔案格式或壓縮編解碼器)

  • 使用較新的壓縮檔案交換較小的檔案

我們在一個簡單示例中使用filecrush ,粉碎一個小文字檔案的目錄,並用gzipped SequenceFiles替換。

首先,在HDFS目錄人為建立10個輸入檔案:

執行filecrush,此示例使用新的大檔案替換小檔案,並將文字檔案轉換為壓縮的SequenceFile:

執行filecrush後會發現輸入目錄中的檔案已被單個SequenceFile替換:

還可以執行文字Hadoop命令來檢視SequenceFile的文字表示:

你會注意到原始的小檔案已全部移動到命令中指定的輸出目錄:

如果在沒有--clone選項的情況下執行filecrush,則輸入檔案將保持不變,並且已壓碎的檔案將被寫入輸出目錄。

輸入和輸出檔案大小閾值

filecrush如何確定檔案是否需要粉碎?透過檢視輸入目錄中的每個檔案,並將其與塊大小進行比較(在Hadoop 2中,可以在-Ddfs.block.size命令中指定大小)。如果檔案小於塊大小的75%,則會被壓縮。可以透過--threshold引數自定義此閾值,例如,如果需要將值提高到85%,則指定--threshold 0.85。

同樣,filecrush使用塊大小來確定輸出檔案大小。預設情況下,它不會建立佔用超過八個塊的輸出檔案,但可以使用--max-file-blocks引數進行自定義。

總結

Filecrush是一種將小檔案組合在一起的簡單快捷的方法。只要存在關聯的輸入格式和輸出格式類,就支援任何型別的輸入或輸出檔案。遺憾的是,它不能與Hadoop 2一起使用,並且在過去幾年中專案中沒有太多變化,因此可能企業不會考慮該程式。

因為filecrush需要輸入和輸出格式,所以如果正在處理二進位制資料並且需要一種將小二進位制檔案組合在一起的方法,那麼它顯然還有不足之處。

使用Avro儲存多個小型二進位制檔案

假設正在開發一個類似於Google影像的專案,可以在其中抓取網頁並從網站下載影像檔案,該專案是網際網路規模,因此下載了數百萬個檔案並將它們單獨儲存在HDFS中。你已經知道HDFS不能很好地處理大量小檔案,因此我們嘗試一下Avro。

問題

希望在HDFS中儲存大量二進位制檔案,並且無需遵守NameNode記憶體限制即可。

解決方案

在HDFS中處理小型二進位制檔案的最簡單方法是將其打包到一個更大的檔案中。此技術將讀取儲存在本地磁碟目錄中的所有檔案,並將它們儲存在HDFS的單個Avro檔案中。你還將瞭解如何使用MapReduce中的Avro檔案來處理原始檔案內容。

討論

圖4.2顯示了該技術的第一部分,可以建立HDFS中的Avro檔案。這樣做可以在HDFS中建立更少的檔案,這意味著儲存在NameNode記憶體中的資料更少,這也意味著可以儲存更多內容。

圖4.2 在Avro中儲存小檔案以允許儲存更多檔案

Avro是由Hadoop的建立者Doug Cutting發明的資料序列化和RPC庫。Avro具有強大的架構演進功能,其優勢已經超過競爭對手,比如SequenceFile等,第3章詳細介紹了這一內容,此處不再贅述。

請檢視以下Java程式碼,該程式碼將建立Avro檔案:

程式碼4.1讀取包含小檔案的目錄,並在HDFS中生成單個Avro檔案

要執行本章程式碼需要在主機上安裝Snappy和LZOP壓縮編解碼器。有關如何安裝和配置它們的詳細資訊,此處不作贅述。

當針對Hadoop的config目錄執行此指令碼時會發生什麼(將$ HADOOP_CONF_DIR替換為包含Hadoop配置檔案的目錄):

讓我們確保輸出檔案是HDFS:

為確保一切正常執行,還可以編寫程式碼以從HDFS讀取Avro檔案,併為每個檔案的內容輸出MD5雜湊值:

此程式碼比寫入更簡單。由於Avro將架構寫入每個Avro檔案,因此無需在反序列化過程中向Avro提供有關架構的資訊:

此時,我們已經在HDFS中擁有了Avro檔案。儘管本章是關於HDFS的,但可能要做的下一件事是處理在MapReduce中編寫的檔案。這需要編寫一個Map-only MapReduce作業,它可以讀取Avro記錄作為輸入,並輸出一個包含檔名和檔案內容的MD5雜湊值的文字檔案,如圖4.3所示。

圖4.3 map作業讀取Avro檔案並輸出文字檔案

以下顯示了此MapReduce作業的程式碼:


程式碼4.2 一個MapReduce作業,包含小檔案的Avro檔案作為輸入

如果透過之前建立的Avro檔案執行此MapReduce作業,則作業日誌檔案將包含檔名和雜湊:

該技術假設使用的檔案格式(例如影像檔案)無法將單獨的檔案連線在一起。如果檔案可以連線,應該考慮該選項。採用該方法請儘量確保檔案大小與HDFS塊一樣,以最大限度減少儲存在NameNode中的資料。

總結

我們可以使用Hadoop的SequenceFile作為儲存小檔案的機制。SequenceFile是一種比較成熟的技術,比Avro檔案更成熟。但是SequenceFiles是特定於Java的,並且不提供Avro帶來的豐富的互操作性和版本控制語義。

谷歌的Protocol Buffers以及Apache Thrift(源自Facebook)也可用於儲存小檔案。 但是都沒有適用於本機Thrift或Protocol Buffers檔案的輸入格式。我們也可以使用另一種方法就是將檔案寫入zip檔案。缺點是必須編寫自定義輸入格式來處理zip檔案,其次是zip檔案不可拆分(與Avro檔案和SequenceFiles相反)。這可以透過生成多個zip檔案並嘗試使它們接近HDFS塊大小來減輕。Hadoop還有一個CombineFileInputFormat,可以將多個輸入拆分(跨多個檔案)提供給單個map任務,這大大減少了執行所需的map任務數量。

我們還可以建立一個包含所有檔案的tarball檔案,生成一個單獨的文字檔案,其中包含HDFS中tarball檔案的位置。此文字檔案將作為MapReduce作業的輸入提供,mapper將直接開啟tarball。但是,這種方法會繞過MapReduce的區域性性,因為mapper將被安排在包含文字檔案的節點上執行,因此可能需要從遠端HDFS節點讀取tarball塊,從而導致不必要的網路I/O。

Hadoop歸檔檔案(HAR)是專門為解決小檔案問題而建立的,是位於HDFS之上的虛擬檔案系統。HAR檔案的缺點是無法針對MapReduce中的本地磁碟訪問進行最佳化,並且無法進行壓縮。Hadoop版本2支援HDFS Federation,其中HDFS被劃分為多個不同的名稱空間,每個名稱空間由一個單獨的NameNode獨立管理。實際上,這意味著將塊資訊儲存在記憶體中的總體影響可以分佈在多個NameNode上,從而支援更多數量的小檔案。

最後,提供Hadoop發行版的MapR擁有自己的分散式檔案系統,支援大量小檔案。將MapR用於分散式儲存對系統來說需要很大改變,因此,企業不太可能轉移到MapR來緩解HDFS的這個問題。你可能會遇到想要在Hadoop中處理小檔案的時間,並且直接使用它們會導致膨脹的NameNode記憶體和執行緩慢的MapReduce作業。此技術透過將小檔案打包到更大的容器檔案中來幫助緩解這些問題。我之所以選擇Avro是因為它支援可拆分檔案,壓縮及其富有表現力的架構語言,這將有助於版本控制。如果檔案很大,想要更有效地儲存應該怎麼辦呢?這將在第4.2節得到解決。

4.1.5原子資料移動

分割槽和壓縮等行為往往遵循類似的模式,比如在暫存目錄中生成輸出檔案,然後需要在成功暫存所有輸出檔案後以原子方式移動到最終目標目錄。這可能會帶來一些問題:

  • 使用什麼觸發器來確定已準備好執行原子移動?

  • 如何在HDFS中原子移動資料?

  • 資料移動對最終資料讀者有何影響?

使用MapReduce驅動程式作為後處理步驟執行原子移動可能很好,但如果客戶端程式在MapReduce應用程式完成之前死亡會發生什麼?這是在Hadoop中使用OutputCommitter非常有用的地方,因為可以將任何原子檔案移動作為工作的一部分,而不是使用驅動程式。

接下來的問題是如何在HDFS中原子移動資料。在最長的時間內,人們認為DistributedFileSystem類(這是支援HDFS的具體實現)上的重新命名方法是原子的。但事實證明,在某些情況下,這不是原子操作。這在HADOOP-6240中得到了解決,但出於向後相容性原因,重新命名方法未更新。因此,重新命名方法仍然不是真正的原子。相反,你需要使用新的API。如下所示,程式碼很麻煩,只適用於較新版本的Hadoop:

HDFS缺少的是原子交換目錄能力,這在壓縮等情況下非常有用。因為在這些情況下,你需要替換其他程式(如Hive)正在使用的目錄的全部內容。有一個名為““Atomic Directory Swapping Operation”的專案可能有對你有些幫助。

以上就是使用Hadoop系統需要掌握的資料組織技術及相應原則。下一節我們將介紹Hadoop中的另一個重要資料管理主題——資料壓縮。

相關文章:

1、 《第一章:Hadoop生態系統及執行MapReduce任務介紹!》

2、 《學習Hadoop生態第一步:Yarn基本原理和資源排程解析!》

3、 《MapReduce如何作為Yarn應用程式執行?》

4、 《Hadoop生態系統各元件與Yarn的相容性如何?》

5、 《MapReduce資料序列化讀寫概念淺析!》

6、 《MapReuce中對大資料處理最合適的資料格式是什麼?》

7、 《如何在MapReduce中使用SequenceFile資料格式?》

8、 《如何在MapReduce中使用Avro資料格式?》

9、 《企業自有資料格式雜亂,MapReduce如何搞定?》

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31545816/viewspace-2215158/,如需轉載,請註明出處,否則將追究法律責任。

相關文章