第一章:初始Hadoop Hadoop提供了一個(gè)穩(wěn)定的共享存儲(chǔ)和分析系統(tǒng)。存儲(chǔ)由HDFS實(shí)現(xiàn),分析由MapReduce實(shí)現(xiàn)。 MapReduce很適合處理那些需要分析整個(gè)數(shù)據(jù)集的問(wèn)題,以批處理的方式,尤其是Ad Hoc(自主或及時(shí))分析。MapReduce適合數(shù)據(jù)被一次寫(xiě)入和多次讀取的應(yīng)用,對(duì)于非結(jié)構(gòu)化或半結(jié)構(gòu)化數(shù)據(jù)非常有效,它被設(shè)計(jì)為在處理時(shí)間內(nèi)解釋數(shù)據(jù)。即MapReduce輸入的鍵和值并不是數(shù)據(jù)固有的屬性,是由分析數(shù)據(jù)的人來(lái)選擇的。Web服務(wù)器日志是記錄集的一個(gè)很好的非規(guī)范化例子,也是MapReduce非常適合用于分析各種日志文件的原因。 MapReduce是一種線性的可伸縮的編程模型。程序員編寫(xiě)兩個(gè)函數(shù)——map函數(shù)和Reduce函數(shù),每一個(gè)都定義一個(gè)鍵/值對(duì)集映射到另一個(gè)。這些函數(shù)無(wú)視數(shù)據(jù)的大小或者他們正在使用的集群的特性,這樣他們就可以原封不動(dòng)地應(yīng)用到小規(guī)模數(shù)據(jù)集或者大的數(shù)據(jù)集上。 MapReduce嘗試在計(jì)算節(jié)點(diǎn)本地存儲(chǔ)數(shù)據(jù),這種“數(shù)據(jù)本地化”功能,成為MapReduce的核心功能并且也是它擁有良好性能的原因之一。 在一個(gè)大規(guī)模分布式計(jì)算平臺(tái)上協(xié)調(diào)進(jìn)程是一個(gè)很大的挑戰(zhàn)。而MapReduce是一個(gè)無(wú)共享的架構(gòu),這意味著各個(gè)任務(wù)之間彼此并不依賴(lài)。 Hadoop的子項(xiàng)目: Core:一系列分布式文件系統(tǒng)和通用I/O的組件和接口(序列號(hào)、java RPC和持久化數(shù)據(jù)結(jié)構(gòu)) Avro:一種提供高效、跨語(yǔ)言RPC的數(shù)據(jù)序列系統(tǒng),持久化數(shù)據(jù)存儲(chǔ)。 MapReduce:分布式數(shù)據(jù)處理模式和執(zhí)行環(huán)境,運(yùn)行于大型商用機(jī)集群。 HDFS:分布式文件系統(tǒng),運(yùn)行于大型商用機(jī)集群。 Pig:一種數(shù)據(jù)流語(yǔ)言和運(yùn)行環(huán)境,用以檢索非常大的數(shù)據(jù)集。Pig運(yùn)行在MapReduce和HDFS的集群上。 Hbase:一個(gè)分布式的、列存儲(chǔ)數(shù)據(jù)庫(kù)。HBase使用HDFS作為底層存儲(chǔ),同時(shí)支持MapReduce的批量式計(jì)算和點(diǎn)查詢(xún)。 ZooKeeper:一個(gè)分布式的、高可用性的協(xié)調(diào)服務(wù)。ZooKeeper提供分布式鎖之類(lèi)的基本服務(wù)用于構(gòu)建分布式應(yīng)用。 Hive:分布式數(shù)據(jù)倉(cāng)庫(kù)。Hive管理HDFS中存儲(chǔ)的數(shù)據(jù),并提供基于SQL的查詢(xún)語(yǔ)言用以查詢(xún)數(shù)據(jù)。 Chukwa:分布式數(shù)據(jù)收集和分析系統(tǒng)。Chukwa運(yùn)行HDFS中存儲(chǔ)數(shù)據(jù)的收集器,它使用MapReduce來(lái)生成報(bào)告。 第二章:MapReduce簡(jiǎn)介 MapReduce的工作過(guò)程分為兩個(gè)階段:map階段和reduce階段。每個(gè)階段都有鍵/值對(duì)作為輸入和輸出,并且它們的類(lèi)型可由程序員選擇。程序員還具體定義了兩個(gè)函數(shù):map函數(shù)和reduce函數(shù)。map函數(shù)的輸出先由MapReduce框架處理,然后再被發(fā)送到reduce函數(shù)。這一處理過(guò)程根據(jù)鍵來(lái)對(duì)鍵/值對(duì)進(jìn)行排序和分組。 明白了MapReduce程序的工作原理之后,就是要用代碼來(lái)實(shí)現(xiàn)它,需要三樣?xùn)|西:一個(gè)map函數(shù)、一個(gè)reduce函數(shù)和一些來(lái)運(yùn)行作業(yè)的代碼。map函數(shù)是由一個(gè)Mapper接口來(lái)實(shí)現(xiàn)的,其中聲明了一個(gè)map()方法。Mapper接口是一個(gè)泛型類(lèi)型,它有4個(gè)形式參數(shù)類(lèi)型,由它們來(lái)指定map函數(shù)的輸入鍵、輸入值、輸出鍵和輸出值的類(lèi)型。Hadoop規(guī)定了自己的一套可用于網(wǎng)絡(luò)序列化的基本類(lèi)型,而不是使用內(nèi)置的Java類(lèi)型。這些在org.apache.hadoop.io包中可以找到。 MapReduce作業(yè)(job)是客戶(hù)端執(zhí)行的單位:它包括輸入數(shù)據(jù)、MapReduce程序和配置信息庫(kù)。Hadoop通過(guò)把作業(yè)分成若干個(gè)小任務(wù)(task)來(lái)工作,其中包括兩種類(lèi)型的任務(wù):map任務(wù)和reduce任務(wù)。有兩種類(lèi)型的節(jié)點(diǎn)控制著作業(yè)執(zhí)行過(guò)程:jobtracker和多個(gè)tasktracker。jobtracker通過(guò)調(diào)度任務(wù)在tasktracker上運(yùn)行,來(lái)協(xié)調(diào)所有運(yùn)行在系統(tǒng)上的作業(yè)。Tasktracker運(yùn)行任務(wù)的同時(shí),把進(jìn)度報(bào)告?zhèn)魉偷絡(luò)obtracker,jobtracker則記錄這每項(xiàng)任務(wù)的整體進(jìn)展情況。如果其中一個(gè)任務(wù)失敗,jobtracker可以重新調(diào)度任務(wù)到另外一個(gè)tasktracker。Hadoop把輸入數(shù)據(jù)劃分成等長(zhǎng)的小數(shù)據(jù)發(fā)送到Mapreduce,稱(chēng)為輸入分片或者分片。Hadoop為每個(gè)分片split創(chuàng)建一個(gè)map任務(wù),由它來(lái)運(yùn)行用戶(hù)自定義的map函數(shù)來(lái)分析每個(gè)分片中的記錄。對(duì)于大多數(shù)作業(yè),一個(gè)理想的分片大小往往是一個(gè)HDFS塊的大小,默認(rèn)是64MB。 map任務(wù)的執(zhí)行階段和輸入數(shù)據(jù)的存儲(chǔ)節(jié)點(diǎn)是同一節(jié)點(diǎn),Hadoop的性能達(dá)到最佳。這就是所謂的data locality optimization(數(shù)據(jù)局部性?xún)?yōu)化)。如果分區(qū)跨越兩個(gè)塊,那么對(duì)于任何一個(gè)HDFS節(jié)點(diǎn)而言,基本上不可能同時(shí)存儲(chǔ)這兩數(shù)據(jù)塊,因此此分布的某部分必須通過(guò)網(wǎng)絡(luò)傳輸?shù)焦?jié)點(diǎn),這與使用本地?cái)?shù)據(jù)運(yùn)行map任務(wù)相比,顯然效率更低。map任務(wù)把輸出寫(xiě)入本地硬盤(pán),而不是HDFS。map的輸出作為中間輸出,中間輸出則被reduce任務(wù)處理后產(chǎn)生最終的輸出,一旦作業(yè)完成,map輸出就可以刪除了。所以沒(méi)有把它存儲(chǔ)在HDFS上,如果節(jié)點(diǎn)上運(yùn)行的map任務(wù)在map輸出給reduce任務(wù)處理之前崩潰,那么Hadoop將在另一個(gè)節(jié)點(diǎn)上重新運(yùn)行map任務(wù)以再次創(chuàng)建map的輸出。 reduce任務(wù)并不具備數(shù)據(jù)本地讀取的優(yōu)勢(shì),一個(gè)單一的reduce任務(wù)的輸入往往來(lái)自于所有mapper的輸出。因此,有序map的輸出必須通過(guò)網(wǎng)絡(luò)傳輸?shù)絩educe任務(wù)運(yùn)行的節(jié)點(diǎn),并在那里進(jìn)行合并,然后傳遞到用戶(hù)定義的reduce函數(shù)中。為增加其可靠性,reduce的輸出通常存儲(chǔ)在HDFS中。 reduce任務(wù)的數(shù)目并不是由輸入的大小來(lái)決定,而是單獨(dú)具體指定的。如果有多個(gè)reducer,map任務(wù)會(huì)對(duì)其輸出進(jìn)行分區(qū),為每個(gè)reduce任務(wù)創(chuàng)建一個(gè)分區(qū)。每個(gè)分區(qū)包含許多鍵機(jī)器關(guān)聯(lián)的值,但每個(gè)鍵的記錄都在同一個(gè)分區(qū)中。分區(qū)可以通過(guò)用戶(hù)定義的partitioner來(lái)控制,但通常是用默認(rèn)的分區(qū)工具,它使用的是hash函數(shù)來(lái)形成”木桶“鍵/值。 Hadoop運(yùn)行用戶(hù)聲明一個(gè)combiner,運(yùn)行在map的輸出上——該函數(shù)的輸出作為reduce函數(shù)的輸入,由于combiner是一個(gè)優(yōu)化方法,所以Hadoop不博鰲鎮(zhèn)對(duì)于某個(gè)map的輸出記錄是否調(diào)用該方法,調(diào)用該方法多少次。換言之,不調(diào)用該方法或者調(diào)用該方法多次,reducer的輸出結(jié)果都一樣。 Hadoop提供了一個(gè)API來(lái)運(yùn)行Mapreduce,并運(yùn)行用除java以外的語(yǔ)言來(lái)編寫(xiě)自己的map函數(shù)和reduce函數(shù)。Hadoop流使用Unix標(biāo)準(zhǔn)流作為Hadoop和程序之間的接口,所以可以使用任何語(yǔ)言,只要編寫(xiě)的Mapreduce程序能夠讀取標(biāo)準(zhǔn)輸入并寫(xiě)入到標(biāo)準(zhǔn)輸出。 Hadoop管道式Hadoop MapReduce的C++接口的代稱(chēng),于流不同,流使用標(biāo)準(zhǔn)輸入和輸出讓map和reduce節(jié)點(diǎn)之間相互交流,管道使用sockets作為tasktracker與C++編寫(xiě)的map或者reduce函數(shù)的進(jìn)程之間的通道。 HDFS是為流式數(shù)據(jù)訪問(wèn)模式存儲(chǔ)超大文件而設(shè)計(jì)的文件系統(tǒng),在商用硬件的集群上運(yùn)行。 HDFS建立在這樣一個(gè)思想上:一次寫(xiě)入、多次讀取模式是最高效的。一個(gè)數(shù)據(jù)集通常由數(shù)據(jù)源生成或復(fù)制,接著在此基礎(chǔ)上進(jìn)行各種各樣的分析。每個(gè)分析至少都會(huì)涉及數(shù)據(jù)集中的大部分?jǐn)?shù)據(jù)(甚至全部),因此讀取整個(gè)數(shù)據(jù)集的時(shí)間比讀取第一條記錄的延遲更為重要。 HDFS中的文件只有一個(gè)寫(xiě)入者,而且寫(xiě)操作總是在文件的末尾。它不支持多個(gè)寫(xiě)入者,或是在文件的任意位置修改。HDFS數(shù)據(jù)庫(kù)的大小默認(rèn)為64MB。HDFS的塊比磁盤(pán)的塊大,目的是為了減少尋址開(kāi)銷(xiāo)。通過(guò)讓一個(gè)塊足夠大,從磁盤(pán)轉(zhuǎn)移數(shù)據(jù)的時(shí)間能夠遠(yuǎn)遠(yuǎn)大于定位這個(gè)塊開(kāi)始端的時(shí)間,因此,傳送一個(gè)由多個(gè)塊組成的文件的時(shí)間就取決于磁盤(pán)傳輸率。 在分布式文件系統(tǒng)中使用抽象塊會(huì)帶來(lái)很多好處。第一,一個(gè)文件可以大于網(wǎng)絡(luò)中任意一個(gè)磁盤(pán)的容量。文件的分塊不需要存儲(chǔ)在同一個(gè)磁盤(pán)上,因此它們可以利用集群上的任意一個(gè)磁盤(pán)。第二,使用抽象單元而不是文件會(huì)簡(jiǎn)化存儲(chǔ)子系統(tǒng)。另外,塊很適合于偉提供容錯(cuò)和實(shí)用性而做的復(fù)制操作。 HDFS集群有兩種節(jié)點(diǎn),以管理者-工作者的模式運(yùn)行,即一個(gè)名稱(chēng)節(jié)點(diǎn)(管理者)和多個(gè)數(shù)據(jù)節(jié)點(diǎn)(工作者)。名稱(chēng)節(jié)點(diǎn)管理文件系統(tǒng)的命名空間。它維護(hù)著這個(gè)文件系統(tǒng)樹(shù)及這個(gè)樹(shù)內(nèi)所有的文件和索引目錄。這些信息以?xún)煞N形式將文件永久保存在本地磁盤(pán)上:命名空間鏡像和編輯日志。名稱(chēng)節(jié)點(diǎn)也記錄這米格文件的每個(gè)塊所在的數(shù)據(jù)節(jié)點(diǎn),但它并不永久保存塊的位置,因?yàn)檫@些信息會(huì)在系統(tǒng)啟動(dòng)時(shí)由數(shù)據(jù)節(jié)點(diǎn)重建。數(shù)據(jù)節(jié)點(diǎn)是文件系統(tǒng)的工作者,他們存儲(chǔ)并提供定位塊的服務(wù),并且定時(shí)的向名稱(chēng)節(jié)點(diǎn)發(fā)送它們存儲(chǔ)的塊的列表。 Hadoop提供了兩種機(jī)制來(lái)確保名稱(chēng)節(jié)點(diǎn)因故障而不損失數(shù)據(jù),第一種機(jī)制就是復(fù)制那些組成文件系統(tǒng)元數(shù)據(jù)持久狀態(tài)的文件。Hadoop可以通過(guò)配置使名稱(chēng)節(jié)點(diǎn)在多個(gè)文件系統(tǒng)上寫(xiě)入其持久化狀態(tài)。這些寫(xiě)操作是具同步性和原子性的。一般的配置選擇是,在本地磁盤(pán)上寫(xiě)入的同時(shí),寫(xiě)入一個(gè)遠(yuǎn)程N(yùn)FS掛載(mount)。另一種可行的方法是運(yùn)行一個(gè)二級(jí)名稱(chēng)節(jié)點(diǎn),雖然它不能作為名稱(chēng)節(jié)點(diǎn)使用,這個(gè)二級(jí)名稱(chēng)節(jié)點(diǎn)的重要作用就是定期的通過(guò)編輯日志合并命名空間鏡像,以防止編輯日志過(guò)大。這個(gè)二級(jí)名稱(chēng)節(jié)點(diǎn)一般在其他單獨(dú)的物理計(jì)算機(jī)上運(yùn)行,因?yàn)樗残枰加么罅緾PU和內(nèi)存來(lái)執(zhí)行合并操作。它會(huì)保存合并后的命名空間鏡像的副本,在名稱(chēng)節(jié)點(diǎn)失效后就可以使用。二級(jí)名稱(chēng)節(jié)點(diǎn)的狀態(tài)是比主節(jié)點(diǎn)滯后的,所以主節(jié)點(diǎn)的數(shù)據(jù)若全部丟失,損失仍在所難免。 HDFS的配置:fs.default.name用來(lái)配置HDFS的默認(rèn)文件系統(tǒng),HDFS的守護(hù)程序?qū)⑼ㄟ^(guò)這個(gè)屬性來(lái)決定HDFS名稱(chēng)節(jié)點(diǎn)的宿主機(jī)和端口。HDFS用戶(hù)將通過(guò)這個(gè)屬性得知名稱(chēng)節(jié)點(diǎn)在哪里運(yùn)行以便于連接到它。dfs.replication用來(lái)設(shè)置HDFS文件系統(tǒng)塊的復(fù)制份數(shù)。 第四章 Hadoop的I/O 客戶(hù)端讀取數(shù)據(jù)節(jié)點(diǎn)上的數(shù)據(jù)時(shí),會(huì)驗(yàn)證校驗(yàn)和,將其與數(shù)據(jù)節(jié)點(diǎn)上存儲(chǔ)的校驗(yàn)和進(jìn)行對(duì)比。每個(gè)數(shù)據(jù)節(jié)點(diǎn)維護(hù)一個(gè)連續(xù)的校驗(yàn)和和驗(yàn)證日志,因此它知道每個(gè)數(shù)據(jù)塊最后驗(yàn)證的時(shí)間??蛻?hù)端成功驗(yàn)證數(shù)據(jù)塊之后,便會(huì)告訴數(shù)據(jù)節(jié)點(diǎn),后者便隨之更新日志。保持這種統(tǒng)計(jì),它對(duì)檢測(cè)損壞磁盤(pán)是很有價(jià)值的。 序列化(serialization)指的是將結(jié)構(gòu)化對(duì)象轉(zhuǎn)為字節(jié)流以便通過(guò)網(wǎng)絡(luò)進(jìn)行傳輸或?qū)懭氤志没鎯?chǔ)的過(guò)程。反序列化指的是將字節(jié)流轉(zhuǎn)為一系列結(jié)構(gòu)化對(duì)象的過(guò)程。序列化用于分布式數(shù)據(jù)處理中兩個(gè)不同的領(lǐng)域:進(jìn)程間通信和持久存儲(chǔ)。 Hadoop中,節(jié)點(diǎn)之間的進(jìn)程通信是用遠(yuǎn)程過(guò)程調(diào)用(RPC,remote procedures call)來(lái)實(shí)現(xiàn)的。RPC協(xié)議使用序列化將消息編碼為二進(jìn)制流(將被發(fā)送到遠(yuǎn)程節(jié)點(diǎn)),此后,二進(jìn)制流被反序列化為原始消息。一般情況下,可用的RPC序列化格式特定如下:緊湊性,一個(gè)緊湊的格式使網(wǎng)絡(luò)帶寬得到充分利用,帶寬是數(shù)據(jù)中心中最稀缺的資源。快速性,進(jìn)程間通信是分布式系統(tǒng)的骨干,因此它必須盡量減少序列化和反序列化開(kāi)銷(xiāo)??蓴U(kuò)展性,協(xié)議隨時(shí)間而變以滿(mǎn)足新的要求,因此它應(yīng)該直接演變?yōu)榭蛻?hù)端和服務(wù)器端的控制協(xié)議?;ゲ僮餍?,對(duì)于某些系統(tǒng),最好能夠支持不同語(yǔ)言編寫(xiě)的客戶(hù)端被寫(xiě)入服務(wù)器端,所以需要為此而精心設(shè)計(jì)文件格式。 Hadoop使用自己的序列化格式Writables,它緊湊、快速(不容易擴(kuò)展java之外的語(yǔ)言)Mapreduce程序使用它來(lái)序列化鍵/值對(duì)。 W日table接口定義了兩個(gè)方法:一個(gè)用于將其狀態(tài)寫(xiě)入二進(jìn)制格式的DataOutput流,另一個(gè)用于從二進(jìn)制格式的DataInput流讀取其態(tài)。 類(lèi)型的比較對(duì)MapReduce而言至關(guān)重要,鍵和鍵之間的比較式在排序階段完成的。Hadoop提供的一個(gè)優(yōu)化方法是從Java Comparator的RawComparator擴(kuò)展的: package org.apache.hadoop.io; import java.util.Comparator; public interface RawComparator public int compare(byte[] b1,int s1,int l1,byte[] b2,int s2,int l2); } 這個(gè)接口運(yùn)行執(zhí)行者比較從流中讀取的未被發(fā)序列化的對(duì)象的記錄,從而省去了創(chuàng)建對(duì)象的所有開(kāi)銷(xiāo)。例如:IntWritables 的comparator使用原始的compare()方法從每個(gè)字節(jié)數(shù)組的指定開(kāi)始位置(S1和S2)和長(zhǎng)度(L1和L2)讀取整數(shù)b1和b2然后直接進(jìn)行比較。 WritableComparator是RawComparator對(duì)WritableComparable類(lèi)的一個(gè)通用實(shí)現(xiàn)。它提供兩個(gè)主要功能。首先,它提供了一個(gè)默認(rèn)的對(duì)原始compare()函數(shù)的調(diào)用,對(duì)從數(shù)據(jù)流中要比較的對(duì)象進(jìn)行反序列化,然后調(diào)用對(duì)象的compare()方法。其次,它充當(dāng)?shù)氖荝awComparator實(shí)例的一個(gè)工廠方法(Writable方法已經(jīng)注冊(cè))。例如,為獲得IntWritable的comparator,我們只需要使用: RawComparator comparator可以用來(lái)比較兩個(gè)IntWritable對(duì)象: IntWritable w1=new IntWritable(163); IntWritable w2=new Intwritable(67); assertThat(comparator.compare(w1,w2,),greaterThan(0)); 或者他們的序列號(hào)描述: byte【】 b1=serialize(w1); byte【】 b2=serialize(w2); assertThat(comparator.compare(b1,0,b1.length,b2,0,b2.length),greaterThan(0)); Hadoop將許多Writable類(lèi)歸入org.apache.hadoop.io包。它們的類(lèi)層次結(jié)構(gòu)為: Text類(lèi) Text類(lèi)是一種UTF-8格式的Writable??梢詫⑺斫鉃槭且环N與java.lang.String相似的Writable。Text類(lèi)代替了UTF8類(lèi),UTF8類(lèi)不支持編碼大于32767個(gè)字節(jié)的字符串,使用了java改進(jìn)的UTF-8。Text在字符串編碼中使用int型存儲(chǔ)字節(jié)數(shù),最大值為2GN。Text使用標(biāo)準(zhǔn)的UTF-8,使其更易于與理解UTF-8的其他工具協(xié)同工作。 迭代Text BytesWritable是對(duì)二進(jìn)制數(shù)據(jù)數(shù)組的封裝。它的序列化格式為一個(gè)用于指定后面數(shù)據(jù)字節(jié)數(shù)的整數(shù)域(4字節(jié)),后跟字節(jié)本身。例如,長(zhǎng)度為2的字節(jié)數(shù)組包含數(shù)值3和5,序列化形式為一個(gè)4字節(jié)整數(shù)(00000002)和該數(shù)組中的兩個(gè)字節(jié)(03)和(05)。 NullWritable是Writable的一個(gè)特殊類(lèi)型。它的序列化長(zhǎng)度為0,它并不從數(shù)據(jù)流中讀取數(shù)據(jù),也不寫(xiě)入數(shù)據(jù)。它充當(dāng)占位符;例如,在MapReduce中,如果不需要使用鍵或值,就可以將鍵或值聲明為NullWritable——結(jié)果是存儲(chǔ)常量控制。它是一個(gè)可變的單實(shí)例類(lèi)型:通過(guò)調(diào)用NullWritable.get()方法可以獲取這個(gè)實(shí)例。
ObjectWritable和GenericWritable Writable集合:org.apache.hadoop.io包中有四種Writable集合類(lèi)型,分別是ArrayWritable,TwoDArrayWritable,MapperWritablehe SortedMapWritable. ArrayWritable和TwoDArrayWritable是Writable針對(duì)數(shù)組和二維數(shù)據(jù)(數(shù)組的數(shù)組)實(shí)例的實(shí)現(xiàn)。所有對(duì)ArrayWritable或者TwoDArrayWritable的使用都必須實(shí)例化相同的類(lèi),這是在構(gòu)造時(shí)指定的,如下所示: ArrayWritable writable=new ArrayWritable(Text.class); ArrayWritable和TwoDArrayWritable都有g(shù)et()和set()方法,也有toArray()方法,后者用于創(chuàng)建數(shù)組(或者二維數(shù)組)的淺拷貝。 MapWritable和SortedMapWritable分別是java.util.Map(Writable,Writable)和java.util.SortedMap(WritableComparable,Writable)的實(shí)例。 每個(gè)鍵/值字段的類(lèi)型都是此字段序列化格式的一部分。 基于文件的數(shù)據(jù)結(jié)構(gòu) 對(duì)于某些應(yīng)用而言,需要特殊的數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)自己的數(shù)據(jù)。對(duì)于基于MapReduce的數(shù)據(jù)處理,將每個(gè)二進(jìn)制數(shù)據(jù)的大對(duì)象融入自己的文件中并不能實(shí)現(xiàn)很高的可擴(kuò)展性,針對(duì)上述情況,Hadoop開(kāi)發(fā)了一組更高層次的容器。 SequenceFile 考慮日志文件,其中每一條日志記錄是一行文本。如果想記錄二進(jìn)制類(lèi)型,純文本是不合適的。這種情況下,Hadoop的SequenceFile類(lèi)非常合適,因?yàn)樯鲜鎏峁┝硕M(jìn)制鍵/值對(duì)的永久存儲(chǔ)的數(shù)據(jù)結(jié)構(gòu)。當(dāng)作為日志文件的存儲(chǔ)格式時(shí),可以自己選擇鍵,比如由LongWritable類(lèi)型表示的時(shí)間戳,以及值可以是Writable類(lèi)型,用于表示日志記錄的數(shù)量。SequenceFile同樣為可以作為小文件的容器。而HDFS和MapReduce是針對(duì)大文件進(jìn)行優(yōu)化的,所以通過(guò)SequenceFile類(lèi)型將小文件包裝起來(lái),可以獲得更高效率的存儲(chǔ)和處理。 通過(guò)createWriter()靜態(tài)方法可以創(chuàng)建SequenceFile對(duì)象,并返回SequenceFile.Writer實(shí)例。該靜態(tài)方法有多個(gè)重載版本,但都需要指定待寫(xiě)入的數(shù)據(jù)流(FSDataOutputStream或FileSystem對(duì)象和Path對(duì)象),Configuration對(duì)象,以及鍵和值的類(lèi)型。另外可選參數(shù)包括壓縮類(lèi)型以及相應(yīng)的codec,Progressable回調(diào)函數(shù)用于通知寫(xiě)入的進(jìn)度,以及在SequenceFile頭文件中存儲(chǔ)的Metadata實(shí)例。 存儲(chǔ)在SequenceFile中的鍵和值對(duì)并不一定是Writable類(lèi)型。任意可以通過(guò)Serialization類(lèi)實(shí)現(xiàn)序列化和反序列化的類(lèi)型均可被使用。一旦擁有SequenceFile.Writer實(shí)例,就可以通過(guò)append()方法在文件末尾附件鍵/值對(duì)。寫(xiě)完后,可以調(diào)用close()方法(SequenceFile.Writer實(shí)現(xiàn)了java.io.Closeable接口)。 寫(xiě)入SequenceFile對(duì)象 import java.io.IOException; import org.apache.hadoop.conf.Configuration; public class SequenceFileWriteDemo { 讀取SequenceFile 從頭到尾讀取順序文件的過(guò)程是創(chuàng)建SequenceFile.Reader實(shí)例后反復(fù)調(diào)用next()方法迭代讀取記錄的過(guò)程。讀取的是哪條記錄與你使用的序列化框架相關(guān)。如果使用的是Writable類(lèi)型,那么通過(guò)鍵和值作為參數(shù)的Next()方法可以將數(shù)據(jù)流中的下一條鍵值對(duì)讀入變量中:public boolean next(Writable key,Writable val),如果鍵值對(duì)成功讀取,則返回true,如果已讀到文件末尾,則返回false。 對(duì)其他非Writable類(lèi)型的序列化框架(如Apache Thrift),可使用下面兩種方法: public Object next(Object key) throws IOException public Object getCurrentValue(Object val) throws IOException 必須確定希望使用的序列化放已經(jīng)設(shè)置了io.serializations屬性。 如果next()方法返回一個(gè)非null對(duì)象,則可以從數(shù)據(jù)流中讀取一個(gè)鍵/值對(duì),并用getCurrentValue()方法來(lái)檢索當(dāng)前值。否則,如果next()返回null,則表示已經(jīng)到達(dá)文件末尾。 類(lèi)型是通過(guò)調(diào)用getKeyClass()和getValueClass()方法是從SequenceFile.Reader中找到的,之后通過(guò)ReflectionUtils來(lái)建立一個(gè)鍵的實(shí)例和值的實(shí)例。 讀取一個(gè)序列文件 import java.io.IOException; import org.apache.hadoop.conf.Configuration; public class SequenceFileReadDemo { } |
|