MapReuce中對大數(shù)據(jù)處理最合適的數(shù)據(jù)格式是什么?
在本章的***章節(jié)介紹中,我們簡單了解了Mapreduce數(shù)據(jù)序列化的概念,以及其對于XML和JSON格式并不友好。本節(jié)作為《Hadoop從入門到精通》大型專題的第三章第二節(jié)將教大家如何在Mapreduce中使用XML和JSON兩大常見格式,并分析比較最適合Mapreduce大數(shù)據(jù)處理的數(shù)據(jù)格式。
3.2.1 XML
XML自1998年誕生以來就作為一種數(shù)據(jù)格式來表示機(jī)器和人類都可讀的數(shù)據(jù)。它成為系統(tǒng)之間數(shù)據(jù)交換的通用語言,現(xiàn)在被許多標(biāo)準(zhǔn)所采用,例如SOAP和RSS,并且被用作Microsoft Office等產(chǎn)品的開放數(shù)據(jù)格式。
MapReduce和XML
MapReduce捆綁了與文本一起使用的InputFormat,但沒有支持XML,也就是說,原生Mapreduce對XML十分不友好。在MapReduce中并行處理單個XML文件很棘手,因為XML不包含其數(shù)據(jù)格式的同步標(biāo)記。
問題
希望在MapReduce中使用大型XML文件,并能夠并行拆分和處理。
解決方案
Mahout的XMLInputFormat可用于MapReduce處理HDFS中的XML文件。 它讀取由特定XML開始和結(jié)束標(biāo)記分隔的記錄,此技術(shù)還解釋了如何在MapReduce中將XML作為輸出發(fā)送。
MapReduce不包含對XML的內(nèi)置支持,因此我們轉(zhuǎn)向另一個Apache項目——Mahout,一個提供XML InputFormat的機(jī)器學(xué)習(xí)系統(tǒng)。 要了解XML InputFormat,你可以編寫一個MapReduce作業(yè),該作業(yè)使用Mahout的XML輸入格式從Hadoop的配置文件(HDFS)中讀取屬性名稱和值。
***步是對作業(yè)進(jìn)行配置:

Mahout的XML輸入格式很簡陋,我們需要指定文件搜索的確切開始和結(jié)束XML標(biāo)記,并使用以下方法拆分文件(并提取記錄):
- 文件沿著HDFS塊邊界分成不連續(xù)的部分,用于數(shù)據(jù)本地化。
- 每個map任務(wù)都在特定的輸入拆分上運(yùn)行,map任務(wù)尋求輸入拆分的開始,然后繼續(xù)處理文件,直到***個xmlinput.start。
- 重復(fù)發(fā)出xmlinput.start和xmlinput.end之間的內(nèi)容,直到輸入拆分的末尾。
接下來,你需要編寫一個映射器來使用Mahout的XML輸入格式。Text表單已提供XML元素,因此需要使用XML解析器從XML中提取內(nèi)容。

表3.1 使用Java的STAX解析器提取內(nèi)容
該map具有一個Text實例,該實例包含start和end標(biāo)記之間數(shù)據(jù)的String表示。在此代碼中,我們可以使用Java的內(nèi)置Streaming API for XML(StAX)解析器提取每個屬性的鍵和值并輸出。
如果針對Cloudera的core-site.xml運(yùn)行MapReduce作業(yè)并使用HDFS cat命令顯示輸出,將看到以下內(nèi)容:

此輸出顯示已成功使用XML作為MapReduce的輸入序列化格式。不僅如此,還可以支持巨大的XML文件,因為輸入格式支持拆分XML。
寫XML
當(dāng)可以正常讀XML之后,我們要解決的就是如何寫XML。 在reducer中,調(diào)用main reduce方法之前和之后都會發(fā)生回調(diào),可以使用它來發(fā)出開始和結(jié)束標(biāo)記,如下所示。


表3.2 用于發(fā)出開始和結(jié)束標(biāo)記的reducer
這也可以嵌入到OutputFormat中。
Pig
如果想在Pig中使用XML,Piggy Bank library(用戶貢獻(xiàn)的Pig代碼庫)包含一個XMLLoader。其工作方式與此技術(shù)非常相似,可捕獲開始和結(jié)束標(biāo)記之間的所有內(nèi)容,并將其作為Pig元組中的單字節(jié)數(shù)組字段提供。
Hive
目前沒有辦法在Hive中使用XML,必須寫一個自定義SerDe。
總結(jié)
Mahout的XmlInputFormat可幫助使用XML,但它對開始和結(jié)束元素名稱的精確字符串匹配很敏感。如果元素標(biāo)記包含具有變量值的屬性,無法控制元素生成或者可能導(dǎo)致使用XML命名空間限定符,則此方法不可用。
如果可以控制輸入中的XML,則可以通過在每行使用單個XML元素來簡化此練習(xí)。這允許使用內(nèi)置的MapReduce基于文本的輸入格式(例如TextInputFormat),它將每一行視為記錄并拆分。
值得考慮的另一個選擇是預(yù)處理步驟,可以將原始XML轉(zhuǎn)換為每個XML元素的單獨(dú)行,或者將其轉(zhuǎn)換為完全不同的數(shù)據(jù)格式,例如SequenceFile或Avro,這兩種格式都解決了拆分問題。
現(xiàn)在,你已經(jīng)了解如何使用XML,讓我們來處理另一種流行的序列化格式JSON。
3.2.2 JSON
JSON共享XML的機(jī)器和人類可讀特征,并且自21世紀(jì)初以來就存在。它比XML簡潔,但是沒有XML中豐富的類型和驗證功能。
如果有一些代碼正在從流式REST服務(wù)中下載JSON數(shù)據(jù),并且每小時都會將文件寫入HDFS。由于下載的數(shù)據(jù)量很大,因此生成的每個文件大小為數(shù)千兆字節(jié)。
如果你被要求編寫一個MapReduce作業(yè),需要將大型JSON文件作為輸入。你可以將問題分為兩部分:首先,MapReduce沒有與JSON一起使用的InputFormat; 其次,如何分割JSON?
圖3.7顯示了拆分JSON問題。 想象一下,MapReduce創(chuàng)建了一個拆分,如圖所示。對此輸入拆分進(jìn)行操作的map任務(wù)將執(zhí)行對輸入拆分的搜索,以確定下一條記錄的開始。對于諸如JSON和XML之類的文件格式,由于缺少同步標(biāo)記或任何其他標(biāo)識記錄開頭,因此知道下一條記錄何時開始是很有挑戰(zhàn)性的。
JSON比XML等格式更難分割成不同的段,因為JSON沒有token(如XML中的結(jié)束標(biāo)記)來表示記錄的開頭或結(jié)尾。
問題
希望在MapReduce中使用JSON輸入,并確保可以為并發(fā)讀取分區(qū)輸入JSON文件。
解決方案
Elephant Bird LzoJsonInputFormat被用來作為創(chuàng)建輸入格式類以使用JSON元素的基礎(chǔ),該方法可以使用多行JSON。

圖3.7 使用JSON和多個輸入拆分的問題示例
討論
Elephant Bird(https://github.com/kevinweil/elephant-bird)是一個開源項目,包含用于處理LZOP壓縮的有用程序,它有一個可讀取JSON的LzoJsonInputFormat,盡管要求輸入文件是LZOP-compressed。,但可以將Elephant Bird代碼用作自己的JSON InputFormat模板,該模板不具有LZOP compression要求。
此解決方案假定每個JSON記錄位于單獨(dú)的行上。JsonRecordFormat很簡單,除了構(gòu)造和返回JsonRecordFormat之外什么也沒做,所以我們將跳過該代碼。JsonRecordFormat向映射器發(fā)出LongWritable,MapWritable key/value,其中MapWritable是JSON元素名稱及其值的映射。
我們來看看RecordReader的工作原理,它使用LineRecordReader,這是一個內(nèi)置的MapReduce讀取器。要將該行轉(zhuǎn)換為MapWritable,讀取器使用json-simple解析器將該行解析為JSON對象,然后迭代JSON對象中的鍵并將它們與其關(guān)聯(lián)值一起放到MapWritable。映射器在LongWritable中被賦予JSON數(shù)據(jù),MapWritable pairs可以相應(yīng)地處理數(shù)據(jù)。

以下顯示了JSON對象示例:


該技巧假設(shè)每行一個JSON對象,以下代碼顯示了在此示例中使用的JSON文件:

現(xiàn)在將JSON文件復(fù)制到HDFS并運(yùn)行MapReduce代碼。MapReduce代碼寫入每個JSON key/value對并輸出:

寫JSON
類似于3.2.1節(jié),編寫XML的方法也可用于編寫JSON。
Pig
Elephant Bird包含一個JsonLoader和LzoJsonLoader,可以使用它來處理Pig中的JSON,這些加載器使用基于行的JSON。每個Pig元組都包含該行中每個JSON元素的chararray字段。
Hive
Hive包含一個可以序列化JSON的DelimitedJSONSerDe類,但遺憾的是無法對其進(jìn)行反序列化,因此無法使用此SerDe將數(shù)據(jù)加載到Hive中。
總結(jié)
此解決方案假定JSON輸入的結(jié)構(gòu)為每個JSON對象一行。那么,如何使用跨多行的JSON對象?GitHub上有一個項目( https://github.com/alexholmes/json-mapreduce)可以在單個JSON文件上進(jìn)行多個輸入拆分,此方法可搜索特定的JSON成員并檢索包含的對象。
你可以查看名為hive-json-serde的Google項目,該項目可以同時支持序列化和反序列化。
正如你所看到的,在MapReduce中使用XML和JSON是非常糟糕的,并且對如何布局?jǐn)?shù)據(jù)有嚴(yán)格要求。MapReduce對這兩種格式的支持也很復(fù)雜且容易出錯,因為它們不適合拆分。顯然,需要查看具有內(nèi)部支持且可拆分的替代文件格式。
下一步是研究更適合MapReduce的復(fù)雜文件格式,例如Avro和SequenceFile。
3.3 大數(shù)據(jù)序列化格式
當(dāng)使用scalar或tabular數(shù)據(jù)時,非結(jié)構(gòu)化文本格式很有效。諸如XML和JSON之類的半結(jié)構(gòu)化文本格式可以對包括復(fù)合字段或分層數(shù)據(jù)的復(fù)雜數(shù)據(jù)結(jié)構(gòu)進(jìn)行建模。但是,當(dāng)處理較大數(shù)據(jù)量時,我們更需要具有緊湊序列化表單的序列化格式,這些格式本身支持分區(qū)并具有模式演變功能。
在本節(jié)中,我們將比較最適合MapReduce大數(shù)據(jù)處理的序列化格式,并跟進(jìn)如何將它們與MapReduce一起使用。
3.3.1 比較SequenceFile,Protocol Buffers,Thrift和Avro
根據(jù)經(jīng)驗,在選擇數(shù)據(jù)序列化格式時,以下特征非常重要:
- 代碼生成——某些序列化格式具有代碼生成作用的庫,允許生成豐富的對象,使更容易與數(shù)據(jù)交互。生成的代碼還提供了類似安全性等額外好處,以確保消費(fèi)者和生產(chǎn)者使用正確的數(shù)據(jù)類型。
- 架構(gòu)演變 - 數(shù)據(jù)模型隨著時間的推移而發(fā)展,重要的是數(shù)據(jù)格式支持修改數(shù)據(jù)模型的需求。模式演變功能允許你添加、修改并在某些情況下刪除屬性,同時為讀和寫提供向后和向前兼容性。
- 語言支持 - 可能需要使用多種編程語言訪問數(shù)據(jù),主流語言支持?jǐn)?shù)據(jù)格式非常重要。
- 數(shù)據(jù)壓縮 - 數(shù)據(jù)壓縮非常重要,因為可以使用大量數(shù)據(jù)。并且,理想的數(shù)據(jù)格式能夠在寫入和讀取時內(nèi)部壓縮和解壓縮數(shù)據(jù)。如果數(shù)據(jù)格式不支持壓縮,那么對于程序員而言,這是一個很大的問題,因為這意味著必須將壓縮和解壓縮作為數(shù)據(jù)管道的一部分進(jìn)行管理(就像使用基于文本的文件格式一樣)。
- 可拆分性 - 較新的數(shù)據(jù)格式支持多個并行讀取器,可讀取和處理大型文件的不同塊。文件格式包含同步標(biāo)記至關(guān)重要(可隨機(jī)搜索和掃描到下一條記錄開頭)。
- 支持MapReduce和Hadoop生態(tài)系統(tǒng) - 選擇的數(shù)據(jù)格式必須支持MapReduce和其他Hadoop生態(tài)系統(tǒng)關(guān)鍵項目,例如Hive。如果沒有這種支持,你將負(fù)責(zé)編寫代碼以使文件格式適用于這些系統(tǒng)。
表3.1比較了流行的數(shù)據(jù)序列化框架,以了解它們?nèi)绾蜗嗷クB加。以下討論提供了有關(guān)這些技術(shù)的其他背景知識。

表3.1數(shù)據(jù)序列化框架的功能比較
讓我們更詳細(xì)地看一下這些格式。
SequenceFile
創(chuàng)建SequenceFile格式是為了與MapReduce、Pig和Hive一起使用,因此可以很好地與所有工具集成。缺點(diǎn)主要是缺乏代碼生成和版本控制支持,以及有限的語言支持。
Protocol Buffers
Protocol Buffers 已被Google大量用于互操作,其優(yōu)勢在于其版本支持二進(jìn)制格式。缺點(diǎn)是MapReduce(或任何第三方軟件)缺乏對讀取Protocol Buffers 序列化生成的文件支持。但是,Elephant Bird可以在容器文件中使用Protocol Buffers序列化。
Thrift
Thrift是Facebook內(nèi)部開發(fā)的數(shù)據(jù)序列化和RPC框架,在本地數(shù)據(jù)序列化格式中不支持MapReduce,但可以支持不同的wire-level數(shù)據(jù)表示,包括JSON和各種二進(jìn)制編碼。 Thrift還包括具有各種類型服務(wù)器的RPC層。本章將忽略RPC功能,并專注于數(shù)據(jù)序列化。
Avro
Avro格式是Doug Cutting創(chuàng)建的,旨在幫助彌補(bǔ)SequenceFile的不足。
Parquet
Parquet是一種具有豐富Hadoop系統(tǒng)支持的柱狀文件格式,可以與Avro、Protocol Buffers和Thrift等友好工作。盡管 Parquet 是一個面向列的文件格式,不要期望每列一個數(shù)據(jù)文件。Parquet 在同一個數(shù)據(jù)文件中保存一行中的所有數(shù)據(jù),以確保在同一個節(jié)點(diǎn)上處理時一行的所有列都可用。Parquet 所做的是設(shè)置 HDFS 塊大小和***數(shù)據(jù)文件大小為 1GB,以確保 I/O 和網(wǎng)絡(luò)傳輸請求適用于大批量數(shù)據(jù)。
基于上述評估標(biāo)準(zhǔn),Avro似乎最適合作為Hadoop中的數(shù)據(jù)序列化框架。SequenceFile緊隨其后,因為它與Hadoop具有內(nèi)在兼容性(它設(shè)計用于Hadoop)。
你可以在Github上查看jvm-serializers項目,該項目運(yùn)行各種基準(zhǔn)測試,以根據(jù)序列化和反序列化時間等比較文件格式。它包含Avro,Protocol Buffers和Thrift基準(zhǔn)測試以及許多其他框架。
在了解了各種數(shù)據(jù)序列化框架后,我們將在接下來幾節(jié)中專門討論這些格式。