說起hadoop這個東西,只能說真是個偉大的發(fā)明哄酝,而本人對cutting大神也是無比的崇拜友存,記得剛接觸hadoop的時(shí)候,還覺得這個東西挺多余的陶衅,但是現(xiàn)在想想屡立,這個想法略傻逼......
2006-2016,hadoop至今已經(jīng)走過了10個年頭搀军,版本也已經(jīng)發(fā)展到2.7了膨俐,現(xiàn)在hadoop3.0也快出來了,雖然spark罩句,flink這些優(yōu)秀的框架近幾年的勢頭非常的強(qiáng)勁焚刺,但是我認(rèn)為,近幾年內(nèi)并不會有哪個框架會取代hadoop门烂,所以其實(shí)還是挺值得研究的乳愉。
那么我這系列的文章呢,主要是想講講Hadoop的核心組件HDFS屯远,這個文件系統(tǒng)現(xiàn)在應(yīng)用真是非常的廣泛蔓姚,特別是hadoop由1.x升到2.x之后,hdfs不論是從容錯性慨丐、可靠性坡脐、可擴(kuò)展性都有了非常大的提升,體系結(jié)構(gòu)也有了很大的變化咖气。正巧最近忙里偷閑閱讀了一下hdfs的源代碼和看了一本介紹hdfs源代碼的書挨措,希望分享下我自己的一些理解吧。
首先簡單說說HDFS的基本結(jié)構(gòu)吧崩溪,主從式架構(gòu)相信大家都非常熟悉,hadoop也是采用這一個斩松,由namenode和datanode組成伶唯,但是在1.x版本中已經(jīng)證實(shí)了僅僅依靠secondary namenode來保證hadoop集群的可靠性是遠(yuǎn)遠(yuǎn)不夠的,因?yàn)閟econdary namenode不是熱備份惧盹,而只是幫助namenode恢復(fù)數(shù)據(jù)而已乳幸,并且數(shù)據(jù)也不是恢復(fù)到最新的數(shù)據(jù)(有關(guān)secondary namenode的幫助恢復(fù)的工作原理有興趣的朋友可以留留言,我可以簡要介紹下钧椰,要是大家都有這個需求我就另發(fā)一篇講講這個secondary namenode粹断,不過這個都是1.x的事了,大家應(yīng)該不感興趣吧哈哈~)嫡霞,因此瓶埋,要保證hadoop集群的高可用,在2.x中引入了HA機(jī)制,有關(guān)這個HA機(jī)制养筒,主要是以來zookeeper來實(shí)現(xiàn)的曾撤,這里先不細(xì)講,后面我會專門寫文章講這個的晕粪,這里主要HDFS結(jié)構(gòu)中幾種節(jié)點(diǎn)的通信挤悉。
那么HDFS通信協(xié)議呢,有兩種巫湘,一種是Hadoop RPC接口装悲,一種是流式接口,那么這兩種接口各自有各自的分工尚氛,前者主要是負(fù)責(zé)一些連接的管理诀诊、節(jié)點(diǎn)的管理以及一些數(shù)據(jù)的管理,而后者主要是數(shù)據(jù)的讀寫傳輸怠褐。
1.Hadoop RPC 接口
首先畏梆,不同于流式接口,Hadoop RPC接口是基于protobuf實(shí)現(xiàn)的奈懒,protobuf是google的一種數(shù)據(jù)格式奠涌,這里不做細(xì)究。那么Hadoop RPC的接口主要有那么幾個磷杏,包括ClientProtocol溜畅,ClientDatanodeProtocol,DatanodeProtocol极祸,InterDatanodeProtocol慈格,NamenodeProtocol這幾個,這幾個接口都是節(jié)點(diǎn)間的主要通信接口遥金,其他的一些涉及安全浴捆、HA的接口我們以后在討論。
首先是重中之重的ClentProtocol稿械,為什么是重中之重呢选泻?我們需要對數(shù)據(jù)文件做的操作基本上都是靠這個接口來實(shí)現(xiàn)的,我看的源代碼是2.6.4美莫,大致數(shù)了下页眯,這個接口有89十個方法,醉了......這里主要的一些方法有g(shù)etBlockLocations()厢呵、create()窝撵、append()、addBlock()襟铭、comlete()等等碌奉,具體這些方法怎么我家下來介紹完集中接口之后短曾,結(jié)合HDFS讀文件和寫文件的流程來介紹這些方法的使用。
然后是ClientDatanodeProtocol接口道批,這個接口是Client端和Datanode端通信使用的错英,主要有g(shù)etReplicationVisibleLength()、getBlockLocalPathInfo()隆豹、refreshNamenodes()椭岩、deleteBlockPool()、getHdfsBlocksMetadata()璃赡、shutdownDatanode()這么些方法判哥,我們從這些方法名可以看到,這些方法基本上都是與數(shù)據(jù)塊的管理相關(guān)碉考,很顯然嘛塌计,Datanode主要的用途就是存儲數(shù)據(jù)嘛,他又不能自己管理數(shù)據(jù)侯谁。
那么接下來就是datanode和namenode通信的接口锌仅,DatanodeProtocol,這個接口也是非常重用墙贱,解決了很多的問題热芹,datanode的注冊、心跳應(yīng)答數(shù)據(jù)塊匯報(bào)都是靠這個接口完成的惨撇。這個接口里伊脓,有datanode啟動相關(guān)的,心跳相關(guān)的和數(shù)據(jù)塊讀寫相關(guān)的方法魁衙。啟動相關(guān)的方法报腔,其實(shí)主要是四個,versionRequest()剖淀、registerDatanode()纯蛾、blockReport()和cacheReport(),按流程來說就是先是versionRequest()纵隔,確認(rèn)namenode和datanode的版本信息是否一致茅撞,如果一直,則建立連接巨朦,然后是registerDatanode(),從名字也能看得出剑令,這個方法是拿來注冊這個datanode節(jié)點(diǎn)的糊啡,注冊了之后namenode中才會有這個節(jié)點(diǎn)相關(guān)的信息,然后是blockReport()和cacheReport()吁津,datanode匯報(bào)自己節(jié)點(diǎn)上的數(shù)據(jù)塊信息(有人很疑問棚蓄,為啥一個新的節(jié)點(diǎn)要匯報(bào)數(shù)據(jù)塊信息堕扶?我認(rèn)為應(yīng)該是有些節(jié)點(diǎn)是因?yàn)槭Я擞种匦录尤爰褐校岳锩姹緛砭陀袛?shù)據(jù))梭依。通過這四步稍算,datanode就成功啟動加入集群了。心跳相關(guān)的方法其實(shí)主要就一個sendHeartbeat()役拴,這個方法就是用來發(fā)送心跳的糊探,心跳是默認(rèn)3秒鐘一次。最后是數(shù)據(jù)塊讀寫相關(guān)的方法河闰,有reportBadBlocks()科平、blockReceivedAndDeleted()和commitBlockSynchronization()方法,這些方法其實(shí)都是拿來管理數(shù)據(jù)塊的姜性,比如出現(xiàn)無效的數(shù)據(jù)塊或者寫數(shù)據(jù)過程中節(jié)點(diǎn)故障數(shù)據(jù)沒寫完等等瞪慧。
然后是InterDatanodeProtocol接口,這個接口很簡單部念,就是datanode之間相互通信的接口弃酌,雖然這個接口簡單,但是其實(shí)很有用儡炼,因?yàn)槲覀兯f的副本就是通過datanode之間的通信來實(shí)現(xiàn)復(fù)制的而不是通過namenode同時(shí)將文件數(shù)據(jù)寫到三個副本中妓湘。
最后就是NamenodeProtocol了,這個接口就不說了吧射赛,在2.x都沒什么用了多柑,這個是namenode和secondary namenode通信的接口。
2.流式接口
流式接口有兩種楣责,一種是基于TCP的DataTransferProtocol竣灌,一種是HA機(jī)制的active namenode和standby namenode間的HTTP接口,第二種先不說秆麸,因?yàn)樯婕癏A機(jī)制節(jié)點(diǎn)的切換以及fsimage和editlog的合并方式等等初嘹,這個今后另起一篇來說。
那么就是DataTransferProtocol了沮趣,這個接口最主要的方法就是readBlock()屯烦、writeBlock()和transferBlock()了。讀數(shù)據(jù)塊房铭、寫數(shù)據(jù)塊以及數(shù)據(jù)塊額復(fù)制就是靠這些方法來實(shí)現(xiàn)驻龟。
介紹完兩類接口之后,我們應(yīng)該是還有一個問題沒有解決吧缸匪,嘿嘿翁狐,hdfs的讀寫文件問題。二話不說凌蔬,先來兩張圖:
第一張圖是讀數(shù)據(jù)的露懒,第二張圖是寫數(shù)據(jù)的闯冷,這兩張圖是官方給的圖。
先說說讀文件懈词,首先是HDFS客戶端會調(diào)用DistributedFileSystem.open()打開跟集群的連接蛇耀,并且打開文件,這個方法底層來說會調(diào)用ClientProtocol接口的open()方法坎弯,然后返回一個數(shù)據(jù)流給客戶端纺涤,此時(shí)客戶端會在調(diào)用接口的getBlockLocations()方法得到文件的一個數(shù)據(jù)塊的位置等等信息,然后客戶端就會通過數(shù)據(jù)流調(diào)用read()方法從這些位置信息里面選出一個最有的節(jié)點(diǎn)來進(jìn)行數(shù)據(jù)讀溶衽(一般三個副本位置會選取網(wǎng)絡(luò)開銷最少的那個節(jié)點(diǎn)洒琢,本地節(jié)點(diǎn)就好了),傳輸完畢之后褐桌,客戶端會再調(diào)用getBlockLocations()方法得到下一個數(shù)據(jù)塊的位置信息衰抑,然后開始讀,知道數(shù)據(jù)讀取結(jié)束荧嵌,客戶端調(diào)用close()方法呛踊,關(guān)閉數(shù)據(jù)流。
說完讀文件啦撮,就是寫文件了谭网,寫文件就稍微比讀文件要復(fù)雜一些。首先客戶端會調(diào)用DistributedFileSystem.create()方法在hdfs中創(chuàng)建一個新的空文件赃春,這個方法會在底層調(diào)用ClientProtocol.create()方法愉择,namenode會在文件目錄樹下添加一個新的文件,并且將操作更新到editlog中织中,此舉之后锥涕,集群會返回一個數(shù)據(jù)輸出流,然后客戶端就可以開始通過調(diào)用write()方法寫數(shù)據(jù)到數(shù)據(jù)流中了狭吼,但是此時(shí)namenode中并沒有任何這個數(shù)據(jù)的數(shù)據(jù)塊元數(shù)據(jù)映射层坠,所以數(shù)據(jù)流會調(diào)用addBlock()方法獲取要寫入datanode節(jié)點(diǎn)的信息,然后就是write()方法的調(diào)用寫數(shù)據(jù)了刁笙,寫到一個節(jié)點(diǎn)上之后破花,這個節(jié)點(diǎn)就開始建立與另外的datanode的連接,然后將數(shù)據(jù)復(fù)制到其他datanode疲吸,從而實(shí)現(xiàn)副本座每。當(dāng)副本寫完之后,第一個datanode就會返回一個確認(rèn)包摘悴,確認(rèn)數(shù)據(jù)已經(jīng)寫入完畢尺栖,并且調(diào)用blockReceivedAndDeleted()方法告訴namenode要更新內(nèi)存元數(shù)據(jù)的數(shù)據(jù),然后開始下一個數(shù)據(jù)塊的寫入烦租,當(dāng)數(shù)據(jù)寫入完畢之后延赌,調(diào)用close()方法關(guān)閉數(shù)據(jù)流。
追加寫文件其實(shí)流程上跟寫文件差不多叉橱,這里就不多做贅述了挫以。
好了,寫了那么多窃祝,主要是一個warming up掐松,大致介紹下hdfs的一些基本原理和流程,接下來的文章里粪小,希望能夠更大家分享一些更細(xì)節(jié)的東西和hdfs內(nèi)部的一些實(shí)現(xiàn)大磺。共勉~