發現一篇不錯的文章,轉一下。http://www.cnblogs.com/xuekyo/p/3386610.htmlhtml
1.流式數據訪問node
HDFS的構建思想是這樣的:一次寫入,屢次讀取是最高效的訪問模式。數據集一般有數據源生成或從數據源複製而來,接着長時間在此數據集上進行各種分析。每次分析都將設計數據集的大部分數據甚至所有,所以讀取整個數據集的時間延遲比讀取第一條數據的時間延遲更重要。算法
2.關於時間延遲的數據訪問緩存
要求低時間延遲數據訪問的應用,例如幾十毫秒的範圍,不適合在HDFS上運行,記住,HDFS是爲高數據吞吐量應用優化的,這可能會以高時間延遲爲代價。目前對於低時間延遲的數據訪問應用,HBase是更好的選擇。網絡
3.大量的小文件併發
因爲namenode將文件系統的元文件存儲在內存中,所以該文件系統所能存儲的文件總數受限於namenode的內存容量。根據經驗,每一個文件、目錄和 數據快的存儲信息大約佔150字節。所以,舉例來講,若是有一百萬個文件,且每一個文件佔一個數據塊,那麼至少須要300MB的內存,儘管存儲上百萬的文件 是可行的,可是存儲數十億個文件就超出了當前硬件的能力。框架
4.HDFS中的塊分佈式
HDFS中的塊默認大小爲64MB,與單一磁盤上的文件系統相似,HDFS中的文件也被劃分爲塊大小的多個分塊(chunk),做爲獨立的存儲單 元。但與其餘的文件系統不一樣的是,HDFS中小於一個塊大小的文件不會佔據整個塊的空間。HDFS的塊比磁盤塊(通常爲512KB)大,目的是爲了最小化 尋址開銷。若是塊設置的足夠大,從磁盤傳輸數據的時間明顯大於定位這個塊起始位置所需的時間。這樣傳輸一個由多個塊組成的文件的時間取決於磁盤傳輸速率。 可是該參數也不該過大,MapReduce中map任務一般一次處理一個塊的數據,所以若是任務數太少(少於集羣中的節點數),做業的運行速度就會比較慢。函數
對分佈式文件系統中的塊進行抽象會帶來許多好處。第一個明顯的好處是,一個文件的大小能夠大於網絡中任意一個磁盤的容量。文件的全部塊並不須要存儲 在同一個磁盤上,所以他們能夠利用集羣中的任意一個磁盤進行存儲。事實上,儘管不常見,但對於HDFS集羣而言,也能夠僅存儲一個文件,該文件的塊佔滿集 羣中的全部磁盤。工具
第二個好處是,使用塊而非整個文件做爲存儲單元,大大簡化了存儲子系統的設計。
不只如此,塊很是適合用於數據備份進而提供數據容錯能力和可用性。將每一個塊複製到少數幾個獨立的機器上(默認是3個),能夠確保塊、磁盤或機器故障時數據不丟失。
與磁盤文件系統相似,HDFS中fsck指令能夠顯示塊信息。
hadoop fsck / -files -blocks
在HDFS裏面,data node上的塊大小默認是64MB(或者是128MB或256MB)
爲何不能遠少於64MB(或128MB或256MB) (普通文件系統的數據塊大小通常爲4KB)?
一、減小硬盤尋道時間(disk seek time)
HDFS設計前提是支持大容量的流式數據操做,因此即便是通常的數據讀寫操做,涉及到的數據 量都是比較大的。假如數據塊設置過少,那須要讀取的數據塊就比較多,因爲數據塊在硬盤上非連續存儲,普通硬盤由於須要移動磁頭,因此隨機尋址較慢,讀越多 的數據塊就增大了總的硬盤尋道時間。當硬盤尋道時間比io時間還要長的多時,那麼硬盤尋道時間就成了系統的一個瓶頸。合適的塊大小有助於減小硬盤尋道時間,提升系統吞吐量。
二、減小Namenode內存消耗
對於HDFS,他只有一個Namenode節點,他的內存相對於Datanode來講,是極 其有限的。然而,namenode須要在其內存FSImage文件中中記錄在Datanode中的數據塊信息,假如數據塊大小設置過少,而須要維護的數據 塊信息就會過多,那Namenode的內存可能就會傷不起了。
爲何不能遠大於64MB(或128MB或256MB)?(這裏主要從上層的MapReduce框架來討論)
一、Map崩潰問題
系統須要從新啓動,啓動過程須要從新加載數據,數據塊越大,數據加載時間越長,系統恢復過程越長。
二、監管時間問題
主節點監管其餘節點的狀況,每一個節點會週期性的把完成的工做和狀態的更新報告回來。若是一個節點保持沉默超過一個預設的時間間隔,主節點記錄下這個節點狀 態爲死亡,並把分配給這個節點的數據發到別的節點。對於這個「預設的時間間隔」,這是從數據塊的角度大概估算的。假如是對於64MB的數據塊,我能夠假設 你10分鐘以內不管如何也能解決了吧,超過10分鐘也沒反應,那就是死了。可對於640MB或是1G以上的數據,我應該要估算個多長的時間內?估算的時間 短了,那就誤判死亡了,分分鐘更壞的狀況是全部節點都會被判死亡。估算的時間長了,那等待的時間就過長了。因此對於過大的數據塊,這個「預設的時間間隔」 很差估算。
三、問題分解問題
數據量大小是問題解決的複雜度是成線性關係的。對於同個算法,處理的數據量越大,它的時間複雜度也就越大。
四、約束Map輸出
在Map Reduce框架裏,Map以後的數據是要通過排序才執行Reduce操做的。想一想歸併排序算法的思想,對小文件進行排序,而後將小文件歸併成大文件的思想,而後就會懂這點了....
5.namenode和datanode
HDFS集羣中有兩類節點,並以管理者-工做者模式運行,即一個namenode(管理者)和多個datanode(工做者)。
namenode管理文件系統的命名空間。它維護文件系統樹和文件系統數中全部文件和目錄。這些信息以兩種方式永久保存在本地磁盤上:命名空間鏡像文件和編輯日誌文件。
datanode是文件系統的工做者。它們存儲並提供定位塊的服務(被用戶或名稱節點調用時),而且定時的向名稱節點發送它們存儲的塊的列表。
沒有namenode,文件系統將沒法使用。若是namenode機器損壞,那麼文件系統上的文件將會丟失,所以對實現namenode的容錯很是重要,Hadoop爲此提供了兩種機制:
第一種機制是備份那些組成文件系統元數據持久狀態的文件。通常配置是,將持久態寫入本地磁盤的同時,寫入一個遠程掛在的網絡文件系統(NFS)。
另外一種方式是運行一個輔助的namenode。雖然它不能做爲名稱節點使用。這個二級名稱節點的重要做用就是按期的經過編輯日誌合併命名空間鏡像, 以防止編輯日誌過大。這個二級名稱節點通常在其餘單獨的物理計算機上運行,由於它也須要佔用大量 CPU 和內存來執行合併操做。它會保存合併後的命名空間鏡像的副本,在名稱節點失效後就可使用。
可是,二級名稱節點的狀態是比主節點滯後的,因此主節點的數據若所有丟失,損失仍在所不免。在這種狀況下,通常把存在 NFS 上的主名稱節點元數據複製到二級名稱節點上並將其做爲新的主名稱節點運行。
6.客戶端讀取HDFS中的數據
(1)客戶端經過調用FileSystem對象的open()方法來打開但願讀取的文件,對於HDFS來講,這個對象是分佈式文件系統的一個實例。
(2)DistributedFileSystem經過RPC(遠程過程調用)來調用namenode,以肯定文件起始塊的位置。對於每個塊,namenode返回存有該塊副本的datanode地址。此外這些datanode根據他們與namenode的距離來排序。
DistributedFileSystem類返回一個FSDataInputStream對象(一個支持文件定位的輸入流)給客戶端並讀取數據。 FSDataInputStream類轉而封裝DFSInputStream對象,該對象管理着namenode和datanode的I/O。
(3)接着客戶端對這個輸入流調用read()方法。存儲着文件起始塊的datanode地址的DFSInputStream隨即鏈接距離最近的datanode。
(4)經過對數據流反覆調用read()方法,能夠將數據從datanode傳輸到客戶端。
(5)到達塊的末端時,DFSInputStream會關閉與該datanode的鏈接,而後尋找下一個塊的最佳datanode。客戶端只需連續的讀取連續的流,而且對於客戶端都是透明的。
客戶端從流中讀取數據時,塊是按照打開DFSInputStream與datanode新建鏈接的順序讀取的。它也須要詢問namenode來檢索下一批所需塊的datanode的位置。
(6)一旦客戶端完成讀取,就對DFSInputStream調用close()方法。
在讀取數據的時候,若是DFSInputStream與datanode的通訊出現錯誤,它便會嘗試從這個塊的另一個最鄰近的datanode讀取數據。它也會記住出現故障的datanode,以保證之後不會反覆讀取該節點上後續的塊。DFSInputStream也會經過「校驗和」確認從 datanode發來的數據是否完整。若是發現一個損壞的塊,它就會在DFSInputStream試圖從其餘datanode讀取一個塊副本以前通知 namenode。
7.寫入HDFS
咱們要考慮的狀況是如何建立一個新文件,並把數據寫入該文件,最後關閉該文件。
(1)客戶端對DistributedFileSystem對象調用create()方法來建立文件。
(2)DistributedFileSystem對namenode建立一個RPC調用,在文件系統的命名空間中建立一個新文件,此時該文件中還 沒有相應的數據塊。namenode執行各類檢查以確保這個文件不存在,而且客戶端有建立該文件的權限。若是這些檢查均經過,namenode就會爲建立 新文件記錄一條記錄;不然,建立失敗,並向客戶端拋出一個IOException異常。DistributedFileSystem向客戶端返回一個 FSDataOutputStream對象,由此客戶端能夠開始寫數據。就像讀取數據同樣,FSDataOutputStream封裝一個 DFSOutputStream對象,該對象負責處理datanode和namenode之間的通訊。
(3)在客戶端寫入數據時,DFSOutputStream將他們分紅一個個的數據包,並寫入內部隊列,成爲數據隊列(data queue)。
(4)DataStreamer處理數據隊列,它的責任是根據datanode隊列來要求namenode分配合適的新塊來存儲數據備份。這一組 datanode組成一個管線——咱們假設副本數量爲3,因此管線中有3個節點。DataStreamer將數據包流式的傳輸到管線的第一個 datanode,該datanode存儲數據並將數據發送到管線的第二個datanode。一樣的,第二個datanode存儲該數據包併發送給管線中 的第三個(也就是最後一個)datanode。
(5)FSOutputStream也維護着一個內部數據包隊列來等待datanode的收到確認回執,成爲「確認隊列」(ack queue)。當管線中全部datanode確認信息後,該數據包纔會從確認隊列中刪除。
若是在寫入期間,datanode遇到故障,則執行一下操做,這對於寫入客戶端是透明的。首先關閉管線,確認把隊列中的任何數據包都放回到數據隊列的最前 端,以保證故障點下游的datanode不會漏掉任何一個數據包。爲存儲在另外一個正常datanode的當前數據塊指定一個新的標識,並將標識傳遞給 namenode,以便故障datanode在恢復後能夠刪除存儲的部分數據塊。從管線中刪除故障節點並把餘下的數據塊寫入管線中的兩個正常 datanode。namenode注意到塊副本量不足,會在另外一個節點上建立一個新的副本。後續的數據塊繼續正常接受處理。
(6)客戶端完成寫入後,會對數據流調用close()方法。該操做將剩餘的全部數據包寫入datanode管線中,並在聯繫namenode且發送文件寫入完成信號以前,等待確認。
(7)namenode已經知道文件由那些數據塊組成(經過DataStreamer詢問數據塊的分配),因此它在返回成功以前只需等待數據塊進行最小量的複製。
8.複本的佈局
namenode如何選擇在哪一個datanode存儲複本(replica)?這裏須要在可靠性,寫入帶寬和讀取帶寬之間進行權衡。
Hadoop的默認佈局策略是在運行客戶端的節點上放第一個複本。第二個複本與第一個不一樣且隨機另外選擇的機架中節點上(離架)。第三個複本與第二 個複本放在相同的機架上,且隨機選擇另一個節點。其餘的複本放在集羣中隨機選擇的節點上,不過系統會避免在相同的機架上放太多複本。
9.一致模型
HDFS提供一個方法來強制全部緩存與數據節點同步,及對DataOutputStream調用sync()方法。當sync()方法放回成功後,對全部新的reader而言,HDFS能保證到目前爲止寫入的數據均一致且可見。
10.HDFS的數據完整性
HDFS會對寫入的全部數據計算校驗和(checksum),並在讀取數據時驗證校驗和。它針對每一個有io.bytes.per.checksum 指定字節的數據計算校驗和。默認狀況下爲512字節,因爲CRC -32校驗和是4個字節,因此存儲校驗和的額外開銷小於1%。
datanode負責在驗證收到的數據後存儲數據及其校驗和。它在收到客戶端數據或複製其它datanode數據期間執行這個操做。正在寫數據的客戶端將數據及其校驗和發送到由一系列datanode組成的管線,管線的最後一個datanode負責驗證校驗和。
客戶端從datanode中讀取數據時也會驗證校驗和,將它們與datanode中的校驗和進行比較。每一個datanode都會持久化存儲一個用戶 驗證的校驗和日誌,因此它知道每一個塊最後一次驗證時間。客戶端成功驗證一個數據塊後,會告訴這個datanode,datanode由此更新日誌。
不僅是客戶端讀取數據時會驗證校驗和,每一個datanode也會在一個後臺線程中運行一個DataBlockScanner,從而按期檢查存儲在這個datanode上的全部數據塊。
可使用RawLocalFileSystem類來禁用校驗和。
11.壓縮
壓縮格式 | 工具 | 算法 | 文件擴展名 | 是否包含多個文件 | 是否可切分 |
DEFLATE | N/A | DEFLATE | .deflate | 否 | 否 |
Gzip | gzip | DEFLATE | .gz | 否 | 否 |
bzip2 | bzip2 | bzip2 | .bz2 | 否 | 是 |
LZO | Lzop | LZO | .lzo | 否 | 否 |
全部壓縮算法都要權衡時間/空間:壓縮和解壓縮速度更快,其代價一般只是節省少許空間。表中列出的壓縮工具都提供9個不一樣的選項來控制壓縮時必須考慮的權衡:選項-1爲優化速度,-9爲優化壓縮空間。
gzip是一個通用的壓縮工具,在空間/時間權衡中,居於其餘兩種壓縮方法之間。bzip2更高效,可是更慢。LZO優化壓縮速度,可是壓縮效率稍遜一籌。
在hadoop中可使用CompressionCodec對數據流進行壓縮和解壓縮。若是要對寫入輸出流的數據進行壓縮,可用 createOutputStream(OutputStream out)方法在在底層的數據流中對須要以壓縮格式寫入在此以前還沒有壓縮的數據創建一個CompressionOutputStream對象,相反,對輸入 數據流讀取數據進行解壓縮時,調用createInputStream(InputStream in)獲取CompressionInputStream。
12.序列化
所謂序列化(serialization),是將結構化對象轉化成字節流,以便在網絡上傳輸或寫入磁盤永久保存。反序列化,是將字節流轉化回結構化對象的過程。
序列化在分佈式數據處理的兩大領域中普遍出現:進程間通訊(RPC)和永久儲存。
hadoop只用本身的序列化格式Writable,它格式緊湊,速度快。
13.Writable
Writable類的層次結構:
Java基本類型 | Writable實現 | 序列化大小(字節) |
boolean | BooleanWritable | 1 |
byte | ByteWritable | 1 |
int | IntWritable | 4 |
VintWritable | 1~5 | |
float | FloatWritable | 4 |
long | LongWritable | 8 |
VlongWritable | 1~9 | |
double | DoubleWritable | 8 |
String(UTF-8) | Text |
14.MapReduce做業運行機制
能夠只用一行代碼來運行一個MapReduce做業:JobClient.runJob(conf)(若是是較新的版本,其實質也是調用這個方法)。分析其過程細節:
整個過程如圖所示,包含以下4個獨立的實體:
(1)做業的提交:
JobClient的runjob()方法是建立JobClient實例並調用它的submitJob()方法的快捷方式(步驟1)。做業提交 後,runJob()每秒輪詢做業的進度,若是發現自上次報告後有變化,便把進度報告到進度臺。做業完成後,若是成功,就顯示做業計數器。若是失敗,致使 做業失敗的錯誤被記錄到控制檯。
JobClient實現的submitJob()方法實現的做業提交過程以下:
(2)做業的初始化
當JobTracker接收到jobclient的submitJob()方法調用後,會把此調用放入一個內部隊列中,交由做業調度器(job scheduler)進行調度,並對其進行初始化。初始化包括創建一個正在運行做業的對象——封裝任務和記錄信息,以便跟蹤任務的狀態和進程。
爲了建立任務運行列表,做業調度器首先從共享文件系統獲取JobClient已經計算好的輸入分片信息。而後爲每一個分片建立一個map任務。建立的 reduce任務數有JobConf的mapred.reduce.task屬性決定。而後調度器建立相應數量的reduce任務。任務ID在此時被指 定。
(3)任務的分配
tasktracker運行一個簡單的循環來按期發送「心跳」(heartbeat)給jobtracker。心跳告訴 jobtracker,tasktracker是否還存活,同時也充當二者之間的消息通道。做爲「心跳」的一部分,tasktracker會指明它是否已 經準備好運行新的任務,若是是,jobtracker會爲它分配一個任務,並使用「心跳」的返回值與tasktracker進行通訊(步驟7)。
在jobtracker爲tasktracker選定任務以前,jobtracker必須先選定任務所在的做業。默認的方法是維護一個簡單的做業優先級列表。固然還有各類調度算法。
對於map任務和reduce任務,tasktracker有固定數量的任務槽。默認調度器會在處理reduce任務槽以前,先填滿map任務槽。
爲了選擇一個reduce任務,jobtracker簡單的從待運行的reduce任務列表中選取下一個來執行,用不着考慮數據的本地化。而後,對 於一個map任務,jobtracker會考慮tasktracker的網絡位置,並選取一個距離其輸入分片最近的tasktracker。
(4)任務的執行
如今,tasktracker已經被分配了一個任務,下一步是運行任務。第一步,經過共享文件系統將做業的JAR複製到tasktracker所在 的文件系統,從而實現JAR文件本地化。同時,tasktracker將程序所需的所有文件從分佈式緩存複製到本地磁盤(步驟8)。第二 步,tasktracker爲任務新建一個本地工做目錄,並把JAR文件解壓到這個文件夾下。第三步,tasktracker新建一個 TaskRunner實例來運行該任務。
TaskRunner啓動一個新的JVM(步驟9)來運行每一個任務(步驟10),以便用戶定義的map和reduce函數的任何軟件問題都不會影響 到tasktracker(例如致使崩潰或掛起等)。可是在不一樣的任務間共享JVM是可能的。子進程經過umbilical接口與父進程進行通訊。任務的 子進程每隔幾秒便告訴父進程它的進度,直到任務完成。
(5)做業的完成
當jobtracker收到做業最後一個任務完成的通知後,便把做業的狀態設爲「成功」。而後,JobClient查看做業狀態時,便知道任務已完成,因而JobClient打印一條消息告知用戶,而後從runJob()方法返回。
最後jobtracker清空做業的工做狀態,指示tasktracker也清空工做狀態(如刪除中間輸出等)。