官术网_书友最值得收藏!

2.3 HDFS的數據存儲

前面主要介紹了HDFS系統的運行機制和原理,本節將介紹HDFS系統中的文件數據是如何存儲和管理的。

2.3.1 數據完整性

I/O操作過程中,難免會出現數據丟失或臟數據,數據傳輸的量越大,出錯的概率越高。校驗錯誤最常用的辦法,就是傳輸前計算一個校驗和,傳輸后計算一個校驗和,兩個校驗和如果不相同,就說明數據存在錯誤。為了保證數據的完整性,一般采用下列數據校驗技術:①奇偶校驗技術;②MD5、SHA1等校驗技術;③CRC-32循環冗余校驗技術;④ECC內存糾錯校驗技術。其中,比較常用的錯誤校驗碼是CRC-32。

HDFS將一個文件分割成一個或多個數據塊,這些數據塊被編號后,由名字節點保存,通常需要記錄的信息包括文件的名稱、文件被分成多少塊、每塊有多少個副本、每個數據塊存放在哪個數據節點上、其副本存放于哪些節點上,這些信息被稱為元數據。

HDFS為了保證數據的完整性,采用校驗和(checksum)檢測數據是否損壞。當數據第一次引入系統時計算校驗和,并且在一個不可靠的通道中傳輸的時候,再次檢驗校驗和。但是,這種技術并不能修復數據(注意:校驗和也可能損壞,但是,由于校驗和小得多,所以可能性非常?。?。數據校驗和采用的是CRC-32,任何大小的數據輸入都可以通過計算,得出一個32位的整數校驗和。

DataNode在接收到數據后存儲該數據及其校驗和,或者將數據和校驗和復制到其他的DataNode上。當客戶端寫數據時,會將數據及其DataNode發送到DataNode組成的管線,最后一個DataNode負責驗證校驗和,如果有損壞,則拋出ChecksumException,這個異常屬于IOException的子類??蛻舳俗x取數據的時候,也會檢驗校驗和,會與DataNode上的校驗和進行比較。每個DataNode上面都會有一個用于記錄校驗和的日志??蛻舳蓑炞C完之后,會告訴DataNode,然后更新這個日志。

不僅客戶端在讀寫數據的時候驗證校驗和,每個DataNode也會在后臺運行一個DataBlockScanner,從而定期檢查存儲在該DataNode上面的數據塊。

如果客戶端發現有block壞掉,按照以下步驟進行恢復。

(1)客戶端在拋出ChecksumException之前,會把壞的block和block所在的DataNode報告給NameNode。

(2)NameNode把這個block標記為已損壞,這樣,NameNode就不會把客戶端指向這個block,也不會復制這個block到其他的DataNode。

(3)NameNode會把一個好的block復制到另外一個DataNode。

(4)NameNode把壞的block刪除。

HDFS會存儲每個數據塊的副本,可以通過數據副本來修復損壞的數據塊??蛻舳嗽谧x取數據塊時,如果檢測到錯誤,首先向NameNode報告已損壞的數據塊及其正在嘗試讀取操作的這個DataNode。NameNode會將這個數據塊標記為已損壞,對這個數據塊的請求會被NameNode安排到另一個副本上。之后,它安排這個數據塊的另一個副本復制到另一個DataNode上,如此,數據塊的副本因子又回到期望水平。此后,已損壞的數據塊副本會被刪除。

Hadoop的LocalFileSystem執行客戶端的校驗和驗證。當寫入一個名為filename的文件時,文件系統客戶端會明確地在包含每個文件塊校驗和的同一個目錄內建立一個名為filename.crc的隱藏文件。

2.3.2 數據壓縮

Hadoop作為一個較通用的海量數據處理平臺,每次運算都會需要處理大量的數據。使用文件和數據壓縮技術有明顯的優點:①節省數據占用的磁盤空間;②加快數據在磁盤和網絡中的傳輸速度,從而提高系統的處理速度。我們來了解一下Hadoop中的文件壓縮。

Hadoop支持多種壓縮格式。我們可以把數據文件壓縮后再存入HDFS,以節省存儲空間。在表2-2中,列出了幾種壓縮格式。

表2-2 Hadoop中的壓縮格式

所有的壓縮算法都存在空間與時間的權衡:更快的壓縮速率和解壓速率是以犧牲壓縮率為代價的。Deflate算法是同時使用了LZ77與哈夫曼編碼的一個無損數據壓縮算法,源代碼可以在zlib庫中找到。Gzip算法是以Deflate算法為基礎擴展出來的一種算法。Gzip在時間和空間上比較適中,Bzip2算法壓縮比Gzip更有效,但速度更慢。Bzip2的解壓速度比它的壓縮速度要快,但與其他壓縮格式相比,又是最慢的,但壓縮效果明顯是最好的。

使用壓縮,有兩個比較麻煩的地方:第一,有些壓縮格式不能被分塊、并行地處理,比如Gzip;第二,另外的一些壓縮格式雖然支持分塊處理,但解壓的過程非常緩慢,使作業瓶頸轉移到了CPU上,例如Bzip2。LZO是一種既能夠被分塊并且并行處理速度也非常快的壓縮算法。在Hadoop中,使用LZO壓縮算法可以減小數據的大小并縮短數據的磁盤讀寫時間,在HDFS中存儲壓縮數據,可以使集群能保存更多的數據,延長集群的使用壽命。不僅如此,由于MapReduce作業通常瓶頸都在I/O上,存儲壓縮數據就意味著更少的I/O操作,作業運行更加高效。例如,將壓縮文件直接作為入口參數交給MapReduce處理,MapReduce會自動根據壓縮文件的擴展名來自動選擇合適的解壓器處理數據。處理流程如圖2-12所示。

圖2-12 MapReduce的壓縮框架

LZO的壓縮文件是由許多小的blocks組成(約256KB),使得Hadoop的作業可以根據block的劃分來分塊工作(split job)。不僅如此,LZO在設計時就考慮到了效率問題,它的解壓速度是Gzip的兩倍,這就讓它能夠節省很多的磁盤讀寫,它的壓縮比不如Gzip,大約壓縮出來的文件比Gzip壓縮的大一半,但是,這仍然比沒有經過壓縮的文件要節省20%~50%的存儲空間,這樣,就可以在效率上大大地提高作業執行的速度。

在考慮如何壓縮由MapReduce程序將要處理的數據時,壓縮格式是否支持分割是很重要的。比如,存儲在HDFS中的未壓縮的文件大小為1GB, HDFS的塊大小為64MB,所以該文件將被存儲為16塊,將此文件用作輸入的MapReduce作業,會創建1個輸入分片(split,也稱為“分塊”。對應block,我們統一稱為“塊”),每個分片都被作為一個獨立map任務的輸入,單獨進行處理。

現在假設該文件是一個Gzip格式的壓縮文件,壓縮后的大小為1GB。與前面一樣,HDFS將此文件存儲為16塊。然而,針對每一塊創建一個分塊是沒有用的,因為不可能從Gzip數據流中的任意點開始讀取,map任務也不可能獨立于其他分塊只讀取一個分塊中的數據。Gzip格式使用Deflate算法來存儲壓縮過的數據,Deflate將數據作為一系列壓縮過的塊進行存儲。但是,每塊的開始沒有指定用戶在數據流中任意點定位到下一個塊的起始位置,而是其自身與數據流同步。因此,Gzip不支持分割(塊)機制。

在這種情況下,MapReduce不分割Gzip格式的文件,因為它知道輸入是Gzip壓縮格式的(通過文件擴展名得知),而Gzip壓縮機制不支持分割機制。這樣是以犧牲本地化為代價的:一個map任務將處理16個HDFS塊。大都不是map的本地數據。與此同時,因為map任務少,所以作業分割的粒度不夠細,從而導致運行時間變長。

在我們假設的例子中,如果是一個LZO格式的文件,我們會遇到同樣的問題,因為基本壓縮格式不為reader提供方法使其與流同步。但是,Bzip2格式的壓縮文件確實提供了塊與塊之間的同步標記(一個48位的PI近似值),因此它支持分割機制。

對于文件的收集,這些問題會稍有不同。Zip是存檔格式,因此,它可以將多個文件合并為一個Zip文件。每個文件單獨壓縮,所有文檔的存儲位置存儲在Zip文件的尾部。這個屬性表明Zip文件支持文件邊界處分割,每個分片中包括Zip壓縮文件中的一個或多個文件。

2.3.3 序列化

序列化是指將結構化對象轉換成字節流,以便于進行網絡傳輸,或寫入持久存儲的過程。與之相對的反序列化,就是將字節流轉化為一系列結構化對象的過程。

(1)序列化有以下特征。

●緊湊:可以充分利用稀缺的帶寬資源。

●快速:通信時大量使用序列化機制,因此,需要減少序列化和反序列化的開銷。

●可擴展:隨著通信協議的升級而可升級。

●互操作:支持不同開發語言的通信。

(2)序列化的主要作用如下:

●作為一種持久化格式。

●作為一種通信的數據格式,支持不同開發語言的通信。

●作為一種數據拷貝機制。

Hadoop的序列化機制與Java的序列化機制不同,它實現了自己的序列化機制,將對象序列化到流中,值得一提的是,Java的序列化機制是不斷地創建對象,但在Hadoop的序列化機制中,用戶可以復用對象,減少了Java對象的分配和回收,提高了應用效率。

在分布式系統中,進程將對象序列化為字節流,通過網絡傳輸到另一進程,另一進程接收到字節流,通過反序列化,轉回到結構化對象,以實現進程間通信。在Hadoop中,Mapper、Combiner、Reducer等階段之間的通信都需要使用序列化與反序列化技術。舉例來說,Mapper產生的中間結果<key: value1, value2...>需要寫入到本地硬盤,這是序列化過程(將結構化對象轉化為字節流,并寫入硬盤),而Reducer階段,讀取Mapper的中間結果的過程則是一個反序列化過程(讀取硬盤上存儲的字節流文件,并轉回為結構化對象)。需要注意的是,能夠在網絡上傳輸的只能是字節流,Mapper的中間結果在不同主機間洗牌時,對象將經歷序列化和反序列化兩個過程。

序列化是Hadoop核心的一部分,在Hadoop中,位于org.apache.hadoop.io包中的Writable接口是Hadoop序列化格式的實現,Writable接口提供兩個方法:

        public interface Writable {
            void write(DataOutput out) throws IOException;
            void readFields(DataInput in) throws IOException;
        }

不過,沒有提供比較功能,需要進行比較的話,要實現WritableComparable接口:

        public interface WritableComparable<T> extends Writable, Comparable<T>
        { }

Hadoop的Writable接口是基于DataInput和DataOutput實現的序列化協議,緊湊(高效使用存儲空間)、快速(讀寫數據、序列化與反序列化的開銷小)。

Hadoop中的鍵(key)和值(value)必須是實現了Writable接口的對象(鍵還必須實現WritableComparable,以便進行排序)。

Hadoop自身提供了多種具體的Writable類,包含了常見的Java基本類型(boolean、byte、short、int、float、long和double等)和集合類型(BytesWritable、ArrayWritable和MapWritable等),如圖2-13所示。

圖2-13 Writable接口

Text:Text是UTF-8的Writable,可以理解為與java.lang.String相類似的Writable。

Text類替代了UTF-8類。Text是可變的,其值可以通過調用set()方法來改變。最大可以存儲2GB的大小。

NullWritable:NullWritable是一種特殊的Writable類型,它的序列化長度為零,可以用作占位符。

BytesWritable:BytesWritable是一個二進制數據數組封裝,序列化格式是一個int字段。BytesWritable是可變的,其值可以通過調用set()方法來改變。

ObjectWritable:ObjectWritable適用于字段使用多種類型時。

ArrayWritable和TwoDArrayWritable是針對數組和二維數組的。

MapWritable和SortedMapWritable是針對Map和SortMap的。

雖然Hadoop內建了多種Writable類供用戶選擇,Hadoop對Java基本類型的包裝Writable類實現的RawComparable接口,使得這些對象不需要反序列化過程,便可以在字節流層面進行排序,從而大大縮短了比較的時間開銷。但是,當我們需要更加復雜的對象時,Hadoop的內建Writable類就不能滿足我們的需求了(需要注意的是Hadoop提供的Writable集合類型并沒有實現RawComparable接口,因此也不滿足我們的需要),這時,我們就需要定制自己的Writable類,特別在將其作為鍵(key)的時候更應該如此,以求實現更高效的存儲和快速的比較。

主站蜘蛛池模板: 高密市| 大渡口区| 定兴县| 闽侯县| 张掖市| 云和县| 鹤峰县| 时尚| 烟台市| 谢通门县| 全椒县| 岫岩| 开鲁县| 北安市| 枝江市| 正安县| 日土县| 察隅县| 肇庆市| 富阳市| 金溪县| 肇源县| 莱芜市| 扬中市| 平潭县| 托克托县| 胶州市| 文成县| 乌审旗| 沙湾县| 开平市| 济南市| 千阳县| 西安市| 行唐县| 锡林浩特市| 西贡区| 罗定市| 汶上县| 合山市| 高雄市|