前言
到目前为止,我们已经对HDFS、MapReduce和YARN有了一定的认识,能够使用HDFS客户端与HDFS集群进行交互,例如上传、下载、追加文件,并且了解了HDFS中NameNode、DataNode的基本作用;也能够编写一些常见的MapReduce程序,提交到YARN上运行,并根据实际情况进行一些调优。可以这样说,我们已经达到了基本能够熟练使用框架的阶段了,但是这远远不够。要掌握一个框架,不仅仅是会用,还需要了解其底层的运行原理。在接下来的一些文章中,我们将会从底层对HDFS框架进行深入学习,掌握其底层运行机制,并在必要时进行源码剖析。
NameNode核心功能
在Hadoop介绍时,我们简单介绍过HDFS框架中分为NameNode和DataNode两个角色。一个HDFS集群由一个NameNode和多个DataNode节点组成,是一个典型的Master/Slave架构。那么NameNode作用是什么呢?
简单来说,NameNode有两大核心功能:
- 文件系统目录树管理
- 数据块管理
文件系统目录树管理
我们保存在HDFS上的数据文件,其元数据是在NameNode的内存中以树状
结构维护的(真正的数据块保存在DataNode节点),整个目录树以”/“作为根节点,数据文件的元数据存放在根节点或根节点的子目录下。什么意思呢?文件系统都是有层次结构的,因此这种结构使用树状的数据结构来维护最合适不过了。实际上,在HDFS中,无论是文件还是目录,都可以被看做是一个INode
对象,我们创建的目录以及上传的文件,其元数据都会由一个具体的INode对象来保存其信息,并且将INode对象存放在目录树的合适位置,下面我们用画图的方式来说明。
刚创建集群的时候,目录树只有一个根节点,没有任何数据。
下面创建一个目录,名称为user,其父目录是根节点。
在根目录下上传一个文件a.txt。
很容易理解吧,有了这样的数据结构之后,可以很方便的对节点进行管理,例如,要查看某个节点下的所有子节点,只需要找到该节点,遍历其子节点就可以了(INode类中的children
字段就是其子节点的引用)。
不知道大家是否注意到了,NameNode是在内存中维护这个数据结构的,如果NameNode节点宕机了,内存中的数据不就全丢失了么,没有了这些元数据,那集群就没法儿使用了。
为什么不把这些信息保存到磁盘上呢?成本太高了,每次对目录树的操作,都要持久化到磁盘上,NameNode的性能就跟不上了,而且目录树的操作不是顺序执行的,比如将某个目录下数据移动到另外一个目录,在根目录创建其他目录等,在磁盘上维护对应的数据结构太麻烦了。
考虑一种折中的方式,不要每次目录树发生变化了,就持久化到磁盘,可以隔一段时间,将目录树的信息全部刷到磁盘上。实际上NameNode就是这样做的,HDFS会将目录树的信息保存到本地磁盘上一个叫做fsimage
的文件中,有了这个文件后,NameNode每次重启时就能够将命名空间恢复了。
还有一个问题,既然是隔一段时间去持久化一次,那么在两次持久化之间,如果节点宕机了,岂不是就会丢失一部分数据?NameNode还有一个操作日志的概念,对于目录树的修改操作(创建、删除、移动等),都会先将操作信息写到一个editlog
文件中,这个文件是直接持久化到磁盘的,写完editlog后,再对目录树进行相应的内存中数据结构变化。有了editlog后,NameNode在启动时,通过fsimage + editlog的文件就可以做到恢复整个文件目录树结构了。
还记得我们在core-site.xml
文件的配置信息吗?
1 | <property> |
该目录就是Hadoop存放数据文件的目录,我们进入这个目录看一眼。
1 | [hadoop@hadoop01 hadoop]$ pwd |
其中dfs
目录是HDFS的数据存放目录,nm-local-dir
是NodeNamager的数据存放目录。
进入dfs目录。
1 | [hadoop@hadoop01 dfs]$ pwd |
其中data
目录是DataNode存放数据块的目录,name
目录是NameNode存放editlog和fsimage的目录。
进入name目录,查看信息。
1 | [hadoop@hadoop01 name]$ tree |
我们先来看editlog文件。在对NameNode命名空间进行修改操作时,每个操作都会赋予一个唯一递增的编号,作为操作ID(也叫做事务ID)。一个editlog的文件名称有两种形式,一种是edits_xxxx-xxxx
,另外一种是edits_inprogress_xxx
。操作记录会写入到editlog文件中,但是并不是只向一个文件中写吧?每次刷新fsimage到磁盘时,都会生成一个新的editlog,或者当editlog达到一定大小时,就会创建一个新的editlog文件。我们举例说,假如刚创建了一个集群,此时操作ID为0,那么会在磁盘上创建一个edits_inprogress_0
的文件(省略了一些0),所有的操作日志都会写入到该文件中。假如现在已经写入了1000条日志了,fsimage开始刷新,此时就会创建一个新的editlog文件,名称为edits_inprogress_000***1000
,并将之前的文件命名为edits_0-999
。
fsimage文件在刷新到磁盘上时,会根据操作ID的编号进行命名,还是上面的例子,写入了1000条日志,那么刷新的fsimage文件的名称就是fsimage_999
。在重启恢复时,先读取fsimage文件,读取之后,再根据后缀找到大于后缀编号的editlog文件,再加载到内存中还原对目录树的操作,就可以重建目录树了。
数据块管理
上传到HDFS上的数据文件,在内部都会转换为数据块(block)存放到DataNode节点。那么NameNode还需要维护一个信息,即数据文件对应有哪些数据块,这些数据块存放在哪些DataNode上。有了这些信息后,客户端才能够去对应的数据节点上获取真正的数据内容。
NameNode也会在内存中维护数据块的信息,但是并不会将数据块与DataNode节点的对应关系持久化到fsimage文件中,而这些信息是由DataNode主动上报给NameNode的,NameNode收到这些上报的信息后,会在内存中构建数据块与数据节点的对应关系。
思考一下,为什么不能将这些信息也持久化到fsimage中呢?
没有必要,如果持久化,还需要将数据块的变动信息(移动,增加数据块副本,删除多余的数据块等)也存储到editlog中,并且这些信息可能由于数据节点的变动造成更多的不一致。因此在NameNode启动时,直接从数据节点的汇报信息中构建就行了。
Secondary NameNode
在上一节我们介绍了fsimage和editlog文件。考虑一个场景,假设在上一次将fsimage文件写入到了磁盘后,随后发生了大量的操作,产生了很多庞大的editlog文件,虽然这样并不会对NameNode运行造成影响,但是在NameNode重启的时候回导致需要非常长的时间进行恢复。想象看, 要将editlog中的操作再次进行回放,代价还是挺高的。
一个解决方案是运行Secondary NameNode,该组件会定时将NameNode上面的fsimage和editlog文件下载到本地,然后进行合并,最后将合并后的fsimage文件再传回NameNode,以加快NameNode在启动时的速度。
这里我们就不过多介绍这个组件了,因为在实际场景中,NameNode都是以HA的方式进行配置的,Seconary NameNode以及很少使用了。
安全模式
安全模式是NameNode的一种状态,处于该模式下的NameNode不接受任何对文件系统目录树的修改操作。NameNode在启动时会自动进入到安全模式,等待DataNode的数据块汇报,在内存中构建数据块与数据节点的对应关系。只有当NameNode收集到的阈值比例满足最低副本系数的数据块时才可以离开安全模式。
最低副本系数指的是一个数据块应该拥有的最少副本数量,默认值是1,由dfs.namenode.replication.min
进行配置。阈值比例就是说已经收集到的满足最低副本系数的数据块数量与HDFS文件系统所有数据块数量的比例。只有当达到了99.9%时,NameNode才会退出安全模式。
当然,我们也可以通过命令行操作NameNode进入或离开安全模式。
1 | $ bin/hdfs dfsadmin -safe mode get|enter|leave |
小结
NameNode最终要的两个核心功能就是维护系统文件目录树结构,以及维护数据块与数据节点之间的对应关系。对于系统目录树,NameNode以fsimage和editlog文件进行持久化,以便在重启时恢复目录树。对于数据块关系,是在NameNode启动时等待数据节点的数据块汇报动态构建的。在重启加载文件以及等待汇报时,NameNode将处于安全模式中,只有当数据块汇报达到一定阈值后,才会退出安全模式,正常提供服务。