1顽铸、HDFS文件讀寫流程
? 1.1端盆、剖析文件讀取過程
? 1.2合蔽、剖析文件寫入過程
1击敌、HDFS文件讀寫流程:
作為一個文件系統(tǒng),文件的讀和寫是最基本的需求拴事,這一部分我們來了解客戶端是如何與HDFS進(jìn)行交互的沃斤,也就是客戶端與HDFS,以及構(gòu)成HDFS的兩類節(jié)點(namenode和datanode)之間的數(shù)據(jù)流是怎樣的刃宵。
1.1衡瓶、剖析文件讀取過程:
客戶端從HDFS讀取文件,其內(nèi)部的讀取過程實際是比較復(fù)雜的牲证,可以用下圖來表示讀取文件的基本流程鞍陨。
對于客戶端來說,首先是調(diào)用FileSystem對象的open()方法來打開希望讀取的文件从隆,然后HDFS會返回一個文件輸入流FSDataInputStream ,客戶端對這個輸入流調(diào)用read()方法缭裆,讀取數(shù)據(jù)键闺,一旦完成讀取,就對這個輸入流調(diào)用close()方法關(guān)閉澈驼,這三個過程對應(yīng)圖中的步驟1辛燥、3、6。
以上三個步驟是從客戶端的角度來分析的挎塌,實際上徘六,要實現(xiàn)文件讀取,HDFS內(nèi)部還需要比較復(fù)雜的機(jī)制來支持榴都,而這些過程都是對客戶端透明的待锈,所以客戶端感受不到,在客戶看來就像是在讀取一個連續(xù)的流嘴高。
具體的竿音,從HDFS的角度來說:
- 客戶端調(diào)用的FileSystem對象的open()方法,這個FileSystem對象實際上是分布式文件系統(tǒng)DistributedFileSystem的一個實例拴驮,DistributedFileSystem通過遠(yuǎn)程過程調(diào)用(RPC)來調(diào)用namenode春瞬,以獲得文件起始塊的位置(步驟2,namenode返回存有該數(shù)據(jù)塊副本的datanode的地址)套啤。當(dāng)然宽气,由于HDFS保存了一個數(shù)據(jù)塊的多個副本(默認(rèn)是3),所以滿足請求的datanode地址不止一個潜沦,此時會根據(jù)它們與客戶端的距離來排序萄涯,優(yōu)先選擇距離近的datanode,如果該客戶端本身就是一個datanode止潮,該客戶端就可以從本地讀取數(shù)據(jù)(比如:mapReduce就利用了這里的數(shù)據(jù)本地化優(yōu)勢)窃判。
- open方法完成后,DistributedFileSystem類返回一個FSDataInputStream(支持文件定位的輸入流)對象給客戶端以便于讀取數(shù)據(jù)喇闸。這個類轉(zhuǎn)而封裝為DFSInputStream對象袄琳,該對象管理著datanode和namenode的I/O。
- 這個DFSInputStream存儲著文件起始幾個塊的datanode地址燃乍,因此唆樊,客戶端對這個輸入流調(diào)用read()方法就可以知道到哪個datanode(網(wǎng)絡(luò)拓?fù)渲芯嚯x最近的)去讀取數(shù)據(jù),這樣刻蟹,反復(fù)調(diào)用read方法就可以將數(shù)據(jù)從datanode傳輸?shù)娇蛻舳耍ú襟E4)逗旁。到達(dá)一個塊的末端時,會關(guān)閉和這個datanode的連接舆瘪,尋找下一個塊的最佳datanode片效,重復(fù)這個過程。
當(dāng)然英古,上面我們說DistributedFileSystem只存儲著文件起始的幾個塊淀衣,在讀取過程中,也會根據(jù)需要再次詢問namenode來獲取下一批數(shù)據(jù)塊的datanode地址召调。一旦客戶端讀取完成膨桥,就調(diào)用close方法關(guān)閉數(shù)據(jù)流蛮浑。
如果在讀取過程中,datanode遇到故障只嚣,很明顯沮稚,輸入流只需要從另外一個保存了該數(shù)據(jù)塊副本的最近datanode讀取即可,同時記住那個故障datanode册舞,以后避免從那里讀取數(shù)據(jù)蕴掏。
總結(jié):
以上就是HDFS的文件讀取過程,從這個過程的分析中我們可以看出:其優(yōu)勢在于客戶端可以直接連接到datanode進(jìn)行數(shù)據(jù)的讀取环础,這樣由于數(shù)據(jù)分散在不同的datanode上囚似,就可以同時為大量并發(fā)的客戶端提供服務(wù)。而namenode作為管理節(jié)點线得,只需要響應(yīng)數(shù)據(jù)塊位置的請求饶唤,告知客戶端每個數(shù)據(jù)塊所在的最佳datanode即可(datanode的位置信息存儲在內(nèi)存中,非常高效的可以獲裙峁场)募狂。這樣使得namenode無需進(jìn)行具體的數(shù)據(jù)傳輸任務(wù),否則namenode在客戶端數(shù)量多的情況下會成為瓶頸角雷。
1.2祸穷、剖析文件寫入過程:
接下來我們分析文件寫入的過程,重點考慮的情況是如何新建一個文件勺三、如何將數(shù)據(jù)寫入文件并最后關(guān)閉該文件雷滚。
然而,具體的吗坚,從HDFS的角度來看祈远,這個寫數(shù)據(jù)的過程就相當(dāng)復(fù)雜了:
- 首先客戶端通過DistributedFileSystem上的create()方法指明一個預(yù)創(chuàng)建的文件的文件名;
- DistributedFileSystem會對namenode創(chuàng)建一個RPC調(diào)用商源,在文件系統(tǒng)的命名空間中新建一個文件车份,此時還沒有相應(yīng)的數(shù)據(jù)塊(步驟2)。namedata接收到這個RPC調(diào)用后牡彻,會進(jìn)行一系列的檢查扫沼,確保這個文件不存在,并且這個客戶端有新建文件的權(quán)限庄吼,如果檢查通過缎除,namenode會為該文件創(chuàng)建一個新的記錄,否則的話文件創(chuàng)建失敗总寻,客戶端得到一個IOException異常伴找,最后DistributedFileSystem返回一個FSDataOutputStream以供客戶端寫入數(shù)據(jù),與FSDataInputStream類似废菱,F(xiàn)SDataOutputStream封裝了一個DFSOutputStream用于處理namenode與datanode之間的通信技矮;
- 當(dāng)客戶端開始寫數(shù)據(jù)時(DFSOutputStream把寫入的塊數(shù)據(jù)時會將其拆分成64k的數(shù)據(jù)包(packet), 放入一個中間隊列——數(shù)據(jù)隊列(data queue)中去。DataStreamer從數(shù)據(jù)隊列中取數(shù)據(jù)殊轴,同時向namenode申請一個新的block來存放它已經(jīng)取得的數(shù)據(jù)衰倦。namenode選擇一系列合適的datanode(個數(shù)由文件的replica數(shù)決定)構(gòu)成一個管道線(pipeline),這里我們假設(shè)replica為3旁理,所以管道線中就有三個datanode樊零;
- DataSteamer把數(shù)據(jù)流式的寫入到管道線中的第一個datanode中,第一個datanode再把接收到的數(shù)據(jù)轉(zhuǎn)到第二個datanode中孽文,以此類推驻襟;
- DFSOutputStream同時也維護(hù)著另一個中間隊列——確認(rèn)隊列(ack queue),確認(rèn)隊列中的包只有在得到管道線中所有的datanode的確認(rèn)以后才會被移出確認(rèn)隊列芋哭。上面的DataStreamer 線程會從 dataQueue 隊列中取出 Packet 對象沉衣,通過底層 IO 流發(fā)送到 pipeline 中的第一個 DataNode,然后繼續(xù)將所有的包轉(zhuǎn)到第二個 DataNode 中减牺,以此類推豌习。發(fā)送完畢后,這個 Packet 會被移出 dataQueue拔疚,放入 DFSOutputStream 維護(hù)的確認(rèn)隊列 ackQueue 中肥隆,該隊列等待下游 DataNode 的寫入確認(rèn)。當(dāng)一個包已經(jīng)被 pipeline 中所有的 DataNode 確認(rèn)了寫入磁盤成功稚失,這個數(shù)據(jù)包才會從確認(rèn)隊列中移除栋艳;
如果某個datanode在寫數(shù)據(jù)的時候掛掉了,下面這些對用戶透明的步驟會被執(zhí)行:
- 管道線關(guān)閉句各,所有確認(rèn)隊列上的數(shù)據(jù)會被挪到數(shù)據(jù)隊列的首部重新發(fā)送吸占,這樣可以確保管道線中掛掉的datanode下游的datanode不會因為掛掉的datanode而丟失數(shù)據(jù)包;
- 在還在正常運行的datanode上的當(dāng)前block上做一個標(biāo)志诫钓,這樣當(dāng)掛掉的datanode重新啟動以后namenode就會知道該datanode上哪個block是剛才宕機(jī)時殘留下的局部損壞block旬昭,從而可以把它刪掉;
- 已經(jīng)掛掉的datanode從管道線中被移除菌湃,未寫完的block的其他數(shù)據(jù)繼續(xù)被寫入到其他兩個還在正常運行的datanode中去问拘,namenode知道這個block還處在under-replicated狀態(tài)(也即備份數(shù)不足的狀態(tài))下,然后他會安排一個新的replica從而達(dá)到要求的備份數(shù)惧所,后續(xù)的block寫入方法同前面正常時候一樣骤坐。有可能管道線中的多個datanode掛掉(雖然不太經(jīng)常發(fā)生),但只要dfs.replication.min(默認(rèn)為1)個replica被創(chuàng)建下愈,我們就認(rèn)為該創(chuàng)建成功了纽绍。剩余的replica會在以后異步創(chuàng)建以達(dá)到指定的replica數(shù);
當(dāng)客戶端完成寫數(shù)據(jù)后势似,它會調(diào)用close()方法拌夏。這個操作會沖洗(flush)所有剩下的package到pipeline中僧著。
等待這些package確認(rèn)成功,然后通知namenode寫入文件成功障簿。這時候namenode就知道該文件由哪些block組成(因為DataStreamer向namenode請求分配新block盹愚,namenode當(dāng)然會知道它分配過哪些blcok給給定文件),它會等待最少的replica數(shù)被創(chuàng)建站故,然后成功返回皆怕。
注意:hdfs在寫入的過程中,有一點與hdfs讀取的時候非常相似西篓,就是"DataStreamer在寫入數(shù)據(jù)的時候愈腾,每寫完一個datanode的數(shù)據(jù)塊,都會重新向nameNode申請合適的datanode列表。這是為了保證系統(tǒng)中datanode數(shù)據(jù)存儲的均衡性"岂津。
hdfs寫入過程中虱黄,datanode管線的確認(rèn)應(yīng)答包并不是每寫完一個datanode,就返回一個確認(rèn)應(yīng)答寸爆,而是一直寫入礁鲁,直到最后一個datanode寫入完畢后,統(tǒng)一返回應(yīng)答包赁豆。如果中間的一個datanode出現(xiàn)故障仅醇,那么返回的應(yīng)答就是前面完好的datanode確認(rèn)應(yīng)答,和故障datanode的故障異常魔种。這樣我們也就可以理解析二,在寫入數(shù)據(jù)的過程中,為什么數(shù)據(jù)包的校驗是在最后一個datanode完成节预。