namenode作为hdfs中的元数据的管理模块,免不了会提到元数据包括哪些?在内存中又是如何存储管理的,本文就来聊聊nn内存中的元数据信息。
【整体概况】
在HDFS中,NN的主要作用是元数据管理,包括文件系统目录树的管理、block块的管理、以及dn结点管理。
在具体实现中,FSDirectory对应文件系统目录树的管理,包括文件系统中各个目录、文件信息记录,以及彼此之间的层级关系;
DatanodeManager管理所有注册的dn结点信息,同时根据dn的心跳信息、块汇报更新相关的信息记录。
BlockManager则是nn中最庞大的一部分,内部又拆分成多个类来存储管理块的不同信息和状态。例如使用blocksmap存储所有的块信息,使用underReplicatedBlocks、PendingReplicationBlocks来保证块的副本数始终满足指定个数。
虽然这几个部分分别管理不同的元数据信息,但彼此并不是孤立的,而是相互联系在一起的,例如:
下面就这几部分内容分别展开说明。
【文件系统目录树】
在HDFS文件系统中,一个目录或者一个文件,都以一个INode来表示,并且都具有一些相同的属性,例如权限、所属用户/用户组、最后修改时间、最后访问时间等;但两者又有一些不同的地方,例如目录下可以有子目录或文件,而文件则没有这些东西,但文件是有实际存储的数据的,这些数据存储在一个或多个块中。
在具体实现中:
对相关类有了了解后,文件系统目录树层级结构和内存中相关类的对应关系就能比较清晰的描述出来了。
【dn信息的存储】
dn节点信息的几个基础类(如下图所示):
【block的存储】
块信息的存储主要包括几个基础类(如下图所示):
上面几个类的作用主要就是用来描述nn中存储的块信息,在这个基础上,还定义了一些类用于块的存储和管理:
1)BlocksMap
内部以一个LightWeightGSet(本质上是一个哈希表)存储所有的块信息。
2)CorruptReplicasMap
内部以嵌套map的方式存放损坏的块信息,其中key为Block,表示是哪个块;
value同样为一个map,嵌套map中的key为块所属dn节点,value为损坏的原因,例如缺少时间戳、缺少长度、无效的状态、客户端或dn上报等。
3)InvalidateBlocks
存放无效的、待删除的块信息。内部同样定义了一个map,其中key为DatanodeInfo,value为存储在该dn节点上的BlockInfo集合。
4)UnderReplicatedBlocks
存放不满足副本数的块信息,内部一个数组加链表的方式来存储块信息。
5)PendingReplicationBlocks
当前正在进行块复制的块信息、以及块复制任务超时的块信息。
【block的状态变化】
上面提到了块的状态,一个块的生命周期中会有如下几个状态:
初始状态为underConstruction,然后依次经过committed,最终变为complete。
underRecovery则用于块恢复。
一个块由一个或多个副本组成,每个副本也有自己的状态,副本的状态包括:
对于正常写流程,副本的状态只会从RBW转换为FINALIZED。
在完整的写流程中,一个块的状态及其副本的状态的可能变化如下图所示:
1. 客户端请求一个新的块时,nn在内部为其分配一个块,块的初始状态为uc,此后为该block近一步选择副本存储的dn结点,最后在内存中依次将块的副本状态初始化为RBW。
2. 此后客户端向dn建立连接,并开始写block数据,在写的过程中,dn会通过增量块汇报向nn上报块的状态,包括正在接收块(receiving)和已经接收完成(received),对于已经接收完成的块,nn在处理时,会找到块对应的副本,将其状态置为finalized。
3. 客户端写完一个块后(收到dn结点的ack响应),会继续向nn申请新的块,在申请新的块请求时,请求中会携带前一个块的信息。nn在处理请求时,默认对前一个块执行commit动作,即将块的状态设置为committed。此后,再近一步判断,当前块副本的状态为finalized的个数是否达到创建时指定的副本数,如果是,则将块的状态设置为complete。
4. 随着更多副本存储的dn结点进增量块汇报后,该块的副本数都处于finalized状态,这时,如果块的状态为committed,nn会将块的状态置为complete。也就是说,只有客户端对隐式对块进行committed后,你能在处理dn的谮量块汇报过程中也会触发状态的切换。
5. 上面讲到了nn的最终状态为complete,但图中箭头最后指向的blockInfo中状态却是null。这就是代码中的一个小技巧了,直接将对象置为null来表示块的complete状态,因为complete已经是最终状态了,在内存中再开辟内存空间记录其状态没有意义,索性直接置为null。这对于需要存储几千万到亿级别的块信息来说,无疑节省了不少内存。
【block与dn的关联】
在上面提到的DatanodeStorageInfo类中,有一个blockList的字段,该字段是BlockInfo的一个实例对象,表示在该卷上存储的一个块信息。
而BlockInfo中的triplets字段是一个对象数组,数组长度为块副本数✖️3,即每个副本占用3个位置,分别记录该副本所在的卷信息(DatanodeStroageInfo),以及前一个块信息,后一个块信息。
如上图所示,DatanodeStorageInfo中的blockList作为链表的表头,然后通过triplets中每个副本占用的两个位置(prev,next),将存储在该卷中的块串联起来,形成一个双向链表。
这样在nn内部不仅可以快速知道一个块存储在哪个dn的哪个卷目录下,还可以快速获取一个dn的一个卷目录下存储了哪些块。
这对于dn节点的上下线,或者dn节点的卷目录异常后的块恢复很重要,方便快速定位哪些块需要进行块复制或者块恢复。
【总结】
本文主要讲解了nn中几个元数据信息在内存中如何进行存储的,包括文件系统的目录树结构、datanode节点信息、块信息。
当然还有也很多没有展开的,例如,nn内部如何保证块的副本数始终维持在指定个数的,即块副本数的监测,块复制、块删除任务的触发执行,以及块是如何恢复的,还有一些知识点没有提到,例如dn节点的网络拓扑,机架感知等。后续再单独进行讲解。。