• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            posts - 200, comments - 8, trackbacks - 0, articles - 0

            NameNode中幾個(gè)關(guān)鍵的數(shù)據(jù)結(jié)構(gòu)

            FSImage

            Namenode會(huì)將HDFS的文件和目錄元數(shù)據(jù)存儲(chǔ)在一個(gè)叫fsimage的二進(jìn)制文件中,每次保存fsimage之后到下次保存之間的所有hdfs操作,將會(huì)記錄在editlog文件中,當(dāng)editlog達(dá)到一定的大小(bytes,由fs.checkpoint.size參數(shù)定義)或從上次保存過后一定時(shí)間段過后(sec,由fs.checkpoint.period參數(shù)定義),namenode會(huì)重新將內(nèi)存中對整個(gè)HDFS的目錄樹和文件元數(shù)據(jù)刷到fsimage文件中。Namenode就是通過這種方式來保證HDFS中元數(shù)據(jù)信息的安全性。

            Fsimage是一個(gè)二進(jìn)制文件,當(dāng)中記錄了HDFS中所有文件和目錄的元數(shù)據(jù)信息,在我的hadoop的HDFS版中,該文件的中保存文件和目錄的格式如下:

             

            當(dāng)namenode重啟加載fsimage時(shí),就是按照如下格式協(xié)議從文件流中加載元數(shù)據(jù)信息。從fsimag的存儲(chǔ)格式可以看出,fsimage保存有如下信息:

            1.         首先是一個(gè)image head,其中包含:

            a)         imgVersion(int):當(dāng)前image的版本信息

            b)        namespaceID(int):用來確保別的HDFS instance中的datanode不會(huì)誤連上當(dāng)前NN。

            c)         numFiles(long):整個(gè)文件系統(tǒng)中包含有多少文件和目錄

            d)        genStamp(long):生成該image時(shí)的時(shí)間戳信息。

            2.         接下來便是對每個(gè)文件或目錄的源數(shù)據(jù)信息,如果是目錄,則包含以下信息:

            a)         path(String):該目錄的路徑,如”/user/build/build-index”

            b)        replications(short):副本數(shù)(目錄雖然沒有副本,但這里記錄的目錄副本數(shù)也為3)

            c)         mtime(long):該目錄的修改時(shí)間的時(shí)間戳信息

            d)        atime(long):該目錄的訪問時(shí)間的時(shí)間戳信息

            e)         blocksize(long):目錄的blocksize都為0

            f)         numBlocks(int):實(shí)際有多少個(gè)文件塊,目錄的該值都為-1,表示該item為目錄

            g)        nsQuota(long):namespace Quota值,若沒加Quota限制則為-1

            h)        dsQuota(long):disk Quota值,若沒加限制則也為-1

            i)          username(String):該目錄的所屬用戶名

            j)          group(String):該目錄的所屬組

            k)        permission(short):該目錄的permission信息,如644等,有一個(gè)short來記錄。

            3.         若從fsimage中讀到的item是一個(gè)文件,則還會(huì)額外包含如下信息:

            a)         blockid(long):屬于該文件的block的blockid,

            b)        numBytes(long):該block的大小

            c)         genStamp(long):該block的時(shí)間戳

            當(dāng)該文件對應(yīng)的numBlocks數(shù)不為1,而是大于1時(shí),表示該文件對應(yīng)有多個(gè)block信息,此時(shí)緊接在該fsimage之后的就會(huì)有多個(gè)blockid,numBytes和genStamp信息。

            因此,在namenode啟動(dòng)時(shí),就需要對fsimage按照如下格式進(jìn)行順序的加載,以將fsimage中記錄的HDFS元數(shù)據(jù)信息加載到內(nèi)存中。

            BlockMap

            從以上fsimage中加載如namenode內(nèi)存中的信息中可以很明顯的看出,在fsimage中,并沒有記錄每一個(gè)block對應(yīng)到哪幾個(gè)datanodes的對應(yīng)表信息,而只是存儲(chǔ)了所有的關(guān)于namespace的相關(guān)信息。而真正每個(gè)block對應(yīng)到datanodes列表的信息在hadoop中并沒有進(jìn)行持久化存儲(chǔ),而是在所有datanode啟動(dòng)時(shí),每個(gè)datanode對本地磁盤進(jìn)行掃描,將本datanode上保存的block信息匯報(bào)給namenode,namenode在接收到每個(gè)datanode的塊信息匯報(bào)后,將接收到的塊信息,以及其所在的datanode信息等保存在內(nèi)存中。HDFS就是通過這種塊信息匯報(bào)的方式來完成 block -> datanodes list的對應(yīng)表構(gòu)建。Datanode向namenode匯報(bào)塊信息的過程叫做blockReport,而namenode將block -> datanodes list的對應(yīng)表信息保存在一個(gè)叫BlocksMap的數(shù)據(jù)結(jié)構(gòu)中。

            BlocksMap的內(nèi)部數(shù)據(jù)結(jié)構(gòu)如下:   

                          

             

            如上圖顯示,BlocksMap實(shí)際上就是一個(gè)Block對象對BlockInfo對象的一個(gè)Map表,其中Block對象中只記錄了blockid,block大小以及時(shí)間戳信息,這些信息在fsimage中都有記錄。而BlockInfo是從Block對象繼承而來,因此除了Block對象中保存的信息外,還包括代表該block所屬的HDFS文件的INodeFile對象引用以及該block所屬datanodes列表的信息(即上圖中的DN1,DN2,DN3,該數(shù)據(jù)結(jié)構(gòu)會(huì)在下文詳述)。

            因此在namenode啟動(dòng)并加載fsimage完成之后,實(shí)際上BlocksMap中的key,也就是Block對象都已經(jīng)加載到BlocksMap中,每個(gè)key對應(yīng)的value(BlockInfo)中,除了表示其所屬的datanodes列表的數(shù)組為空外,其他信息也都已經(jīng)成功加載。所以可以說:fsimage加載完畢后,BlocksMap中僅缺少每個(gè)塊對應(yīng)到其所屬的datanodes list的對應(yīng)關(guān)系信息。所缺這些信息,就是通過上文提到的從各datanode接收blockReport來構(gòu)建。當(dāng)所有的datanode匯報(bào)給namenode的blockReport處理完畢后,BlocksMap整個(gè)結(jié)構(gòu)也就構(gòu)建完成。

            BlockMap中datanode列表數(shù)據(jù)結(jié)構(gòu)

            在BlockInfo中,將該block所屬的datanodes列表保存在一個(gè)Object[]數(shù)組中,但該數(shù)組不僅僅保存了datanodes列表,還包含了額外的信息。實(shí)際上該數(shù)組保存了如下信息:

             

            上圖表示一個(gè)block包含有三個(gè)副本,分別放置在DN1,DN2和DN3三個(gè)datanode上,每個(gè)datanode對應(yīng)一個(gè)三元組,該三元組中的第二個(gè)元素,即上圖中prev block所指的是該block在該datanode上的前一個(gè)BlockInfo引用。第三個(gè)元素,也就是上圖中next Block所指的是該block在該datanode上的下一個(gè)BlockInfo引用。每個(gè)block有多少個(gè)副本,其對應(yīng)的BlockInfo對象中就會(huì)有多少個(gè)這種三元組。

                   Namenode采用這種結(jié)構(gòu)來保存block->datanode list的目的在于節(jié)約namenode內(nèi)存。由于namenode將block->datanodes的對應(yīng)關(guān)系保存在了內(nèi)存當(dāng)中,隨著HDFS中文件數(shù)的增加,block數(shù)也會(huì)相應(yīng)的增加,namenode為了保存block->datanodes的信息已經(jīng)耗費(fèi)了相當(dāng)多的內(nèi)存,如果還像這種方式一樣的保存datanode->block list的對應(yīng)表,勢必耗費(fèi)更多的內(nèi)存,而且在實(shí)際應(yīng)用中,要查一個(gè)datanode上保存的block list的應(yīng)用實(shí)際上非常的少,大部分情況下是要根據(jù)block來查datanode列表,所以namenode中通過上圖的方式來保存block->datanode list的對應(yīng)關(guān)系,當(dāng)需要查詢datanode->block list的對應(yīng)關(guān)系時(shí),只需要沿著該數(shù)據(jù)結(jié)構(gòu)中next Block的指向關(guān)系,就能得出結(jié)果,而又無需保存datanode->block list在內(nèi)存中。

            NameNode啟動(dòng)過程

            fsimage加載過程

            Fsimage加載過程完成的操作主要是為了:

            1.         從fsimage中讀取該HDFS中保存的每一個(gè)目錄和每一個(gè)文件

            2.         初始化每個(gè)目錄和文件的元數(shù)據(jù)信息

            3.         根據(jù)目錄和文件的路徑,構(gòu)造出整個(gè)namespace在內(nèi)存中的鏡像

            4.         如果是文件,則讀取出該文件包含的所有blockid,并插入到BlocksMap中。

            整個(gè)加載流程如下圖所示:

             

            如上圖所示,namenode在加載fsimage過程其實(shí)非常簡單,就是從fsimage中不停的順序讀取文件和目錄的元數(shù)據(jù)信息,并在內(nèi)存中構(gòu)建整個(gè)namespace,同時(shí)將每個(gè)文件對應(yīng)的blockid保存入BlocksMap中,此時(shí)BlocksMap中每個(gè)block對應(yīng)的datanodes列表暫時(shí)為空。當(dāng)fsimage加載完畢后,整個(gè)HDFS的目錄結(jié)構(gòu)在內(nèi)存中就已經(jīng)初始化完畢,所缺的就是每個(gè)文件對應(yīng)的block對應(yīng)的datanode列表信息。這些信息需要從datanode的blockReport中獲取,所以加載fsimage完畢后,namenode進(jìn)程進(jìn)入rpc等待狀態(tài),等待所有的datanodes發(fā)送blockReports。

            blockReport階段

            每個(gè)datanode在啟動(dòng)時(shí)都會(huì)掃描其機(jī)器上對應(yīng)保存hdfs block的目錄下(dfs.data.dir)所保存的所有文件塊,然后通過namenode的rpc調(diào)用將這些block信息以一個(gè)long數(shù)組的方式發(fā)送給namenode,namenode在接收到一個(gè)datanode的blockReport rpc調(diào)用后,從rpc中解析出block數(shù)組,并將這些接收到的blocks插入到BlocksMap表中,由于此時(shí)BlocksMap缺少的僅僅是每個(gè)block對應(yīng)的datanode信息,而namenoe能從report中獲知當(dāng)前report上來的是哪個(gè)datanode的塊信息,所以,blockReport過程實(shí)際上就是namenode在接收到塊信息匯報(bào)后,填充BlocksMap中每個(gè)block對應(yīng)的datanodes列表的三元組信息的過程。其流程如下圖所示:

             

            當(dāng)所有的datanode匯報(bào)完block,namenode針對每個(gè)datanode的匯報(bào)進(jìn)行過處理后,namenode的啟動(dòng)過程到此結(jié)束。此時(shí)BlocksMap中block->datanodes的對應(yīng)關(guān)系已經(jīng)初始化完畢。如果此時(shí)已經(jīng)達(dá)到安全模式的推出閾值,則hdfs主動(dòng)退出安全模式,開始提供服務(wù)。

            啟動(dòng)過程數(shù)據(jù)采集和瓶頸分析

            對namenode的整個(gè)啟動(dòng)過程有了詳細(xì)了解之后,就可以對其啟動(dòng)過程中各階段各函數(shù)的調(diào)用耗時(shí)進(jìn)行profiling的采集,數(shù)據(jù)的profiling仍然分為兩個(gè)階段,即fsimage加載階段和blockReport階段。

            fsimage加載階段性能數(shù)據(jù)采集和瓶頸分析

            以下是對建庫集群真實(shí)的fsimage加載過程的的性能采集數(shù)據(jù):

             

            從上圖可以看出,fsimage的加載過程那個(gè)中,主要耗時(shí)的操作分別分布在FSDirectory.addToParentFSImage.readString,以及PermissionStatus.read三個(gè)操作,這三個(gè)操作分別占用了加載過程的73%,15%以及8%,加起來總共消耗了整個(gè)加載過程的96%。而其中FSImage.readStringPermissionStatus.read操作都是從fsimage的文件流中讀取數(shù)據(jù)(分別是讀取String和short)的操作,這種操作優(yōu)化的空間不大,但是通過調(diào)整該文件流的Buffer大小來提高少許性能。而FSDirectory.addToParent的調(diào)用卻占用了整個(gè)加載過程的73%,所以該調(diào)用中的優(yōu)化空間比較大。

                   以下是addToParent調(diào)用中的profiling數(shù)據(jù):

             

            從以上數(shù)據(jù)可以看出addToParent調(diào)用占用的73%的耗時(shí)中,有66%都耗在了INode.getPathComponents調(diào)用上,而這66%分別有36%消耗在INode.getPathNames調(diào)用,30%消耗在INode.getPathComponents調(diào)用。這兩個(gè)耗時(shí)操作的具體分布如以下數(shù)據(jù)所示:

             

            可以看出,消耗了36%的處理時(shí)間的INode.getPathNames操作,全部都是在通過String.split函數(shù)調(diào)用來對文件或目錄路徑進(jìn)行切分。另外消耗了30%左右的處理時(shí)間在INode.getPathComponents中,該函數(shù)中最終耗時(shí)都耗在獲取字符串的byte數(shù)組的java原生操作中。

            blockReport階段性能數(shù)據(jù)采集和瓶頸分析

            由于blockReport的調(diào)用是通過datanode調(diào)用namenode的rpc調(diào)用,所以在namenode進(jìn)入到等待blockreport階段后,會(huì)分別開啟rpc調(diào)用的監(jiān)聽線程和rpc調(diào)用的處理線程。其中rpc處理和rpc鑒定的調(diào)用耗時(shí)分布如下圖所示:

             

            而其中rpc的監(jiān)聽線程的優(yōu)化是另外一個(gè)話題,在其他的issue中再詳細(xì)討論,且由于blockReport的操作實(shí)際上是觸發(fā)的rpc處理線程,所以這里只關(guān)心rpc處理線程的性能數(shù)據(jù)。

                   在namenode處理blockReport過程中的調(diào)用耗時(shí)性能數(shù)據(jù)如下:

             

            可以看出,在namenode啟動(dòng)階段,處理從各個(gè)datanode匯報(bào)上來的blockReport耗費(fèi)了整個(gè)rpc處理過程中的絕大部分時(shí)間(48/49),blockReport處理邏輯中的耗時(shí)分布如下圖:

             

             

            從上圖數(shù)據(jù)中可以發(fā)現(xiàn),blockReport階段中耗時(shí)分布主要耗時(shí)在FSNamesystem.addStoredBlock調(diào)用以及DatanodeDescriptor.reportDiff過程中,分別耗時(shí)37/48和10/48,其中FSNamesystem.addStoredBlock所進(jìn)行的操作時(shí)對每一個(gè)匯報(bào)上來的block,將其于匯報(bào)上來的datanode的對應(yīng)關(guān)系初始化到namenode內(nèi)存中的BlocksMap表中。所以對于每一個(gè)block就會(huì)調(diào)用一次該方法。所以可以看到該方法在整個(gè)過程中調(diào)用了774819次,而另一個(gè)耗時(shí)的操作,即DatanodeDescriptor.reportDiff,該操作的過程在上文中有詳細(xì)介紹,主要是為了將該datanode匯報(bào)上來的blocks跟namenode內(nèi)存中的BlocksMap中進(jìn)行對比,以決定那個(gè)哪些是需要添加到BlocksMap中的block,哪些是需要添加到toRemove隊(duì)列中的block,以及哪些是添加到toValidate隊(duì)列中的block。由于這個(gè)操作需要針對每一個(gè)匯報(bào)上來的block去查詢BlocksMap,以及namenode中的其他幾個(gè)map,所以該過程也非常的耗時(shí)。而且從調(diào)用次數(shù)上可以看出,reportDiff調(diào)用在啟動(dòng)過程中僅調(diào)用了14次(有14個(gè)datanode進(jìn)行塊匯報(bào)),卻耗費(fèi)了10/48的時(shí)間。所以reportDiff也是整個(gè)blockReport過程中非常耗時(shí)的瓶頸所在。

                   同時(shí)可以看到,出了reportDiff,addStoredBlock的調(diào)用耗費(fèi)了37%的時(shí)間,也就是耗費(fèi)了整個(gè)blockReport時(shí)間的37/48,該方法的調(diào)用目的是為了將從datanode匯報(bào)上來的每一個(gè)block插入到BlocksMap中的操作。從該方法調(diào)用的運(yùn)行數(shù)據(jù)如下圖所示:

             

            從上圖可以看出,addStoredBlock中,主要耗時(shí)的兩個(gè)階段分別是FSNamesystem.countNode和DatanodeDescriptor.addBlock,后者是java中的插表操作,而FSNamesystem.countNode調(diào)用的目的是為了統(tǒng)計(jì)在BlocksMap中,每一個(gè)block對應(yīng)的各副本中,有幾個(gè)是live狀態(tài),幾個(gè)是decommission狀態(tài),幾個(gè)是Corrupt狀態(tài)。而在namenode的啟動(dòng)初始化階段,用來保存corrput狀態(tài)和decommission狀態(tài)的block的map都還是空狀態(tài),并且程序邏輯中要得到的僅僅是出于live狀態(tài)的block數(shù),所以,這里的countNoes調(diào)用在namenode啟動(dòng)初始化階段并無需統(tǒng)計(jì)每個(gè)block對應(yīng)的副本中的corrrput數(shù)和decommission數(shù),而僅僅需要統(tǒng)計(jì)live狀態(tài)的block副本數(shù)即可,這樣countNodes能夠在namenode啟動(dòng)階段變得更輕量,以節(jié)省啟動(dòng)時(shí)間。

            瓶頸分析總結(jié)

            從profiling數(shù)據(jù)和瓶頸分歧情況來看,fsimage加載階段的瓶頸除了在分切路徑的過程中不夠優(yōu)以外,其他耗時(shí)的地方幾乎都是在java原生接口的調(diào)用中,如從字節(jié)流讀數(shù)據(jù),以及從String對象中獲取byte[]數(shù)組的操作。

                   而blockReport階段的耗時(shí)其實(shí)很大的原因是跟當(dāng)前的namenode設(shè)計(jì)以及內(nèi)存結(jié)構(gòu)有關(guān),比較明顯的不優(yōu)之處就是在namenode啟動(dòng)階段的countNode和reportDiff的必要性,這兩處在namenode初始化時(shí)的blockReport階段有一些不必要的操作浪費(fèi)了時(shí)間??梢葬槍amenode啟動(dòng)階段將必要的操作抽取出來,定制成namenode啟動(dòng)階段才調(diào)用的方式,以優(yōu)化namenode啟動(dòng)性能。


            Ref: http://blog.csdn.net/ae86_fc/article/details/5842020

            大香伊人久久精品一区二区| Xx性欧美肥妇精品久久久久久 | A狠狠久久蜜臀婷色中文网| 亚洲国产精品无码久久九九| 久久精品国产精品亚洲人人 | 久久777国产线看观看精品| 777久久精品一区二区三区无码| 国产ww久久久久久久久久| 性做久久久久久久久| 久久综合精品国产二区无码| 国产精品伦理久久久久久| 亚洲av日韩精品久久久久久a| 国产精品99久久久久久董美香| 亚洲午夜无码久久久久小说| 久久久精品国产sm调教网站| 久久精品国产WWW456C0M| 色妞色综合久久夜夜| 久久亚洲电影| 岛国搬运www久久| AV无码久久久久不卡网站下载| 无码精品久久一区二区三区| 国产一级持黄大片99久久| 狠狠色婷婷久久综合频道日韩| 很黄很污的网站久久mimi色| 色偷偷久久一区二区三区| 久久婷婷午色综合夜啪| 国产日韩久久免费影院| 国产Av激情久久无码天堂| 久久精品国产亚洲AV不卡| 色99久久久久高潮综合影院 | 亚洲AV日韩AV永久无码久久| 久久最新免费视频| 久久久久久久国产免费看| 精品久久久久久国产| a高清免费毛片久久| 久久精品中文字幕无码绿巨人 | 久久er99热精品一区二区| 国产成人久久精品一区二区三区| 三级片免费观看久久| 久久人妻无码中文字幕| 中文精品久久久久人妻不卡|