作为一个大型的分布式系统,Hadoop内部实现了一套升级机制(
http://wiki.apache.org/hadoop/Hadoop_Upgrade)。upgrade参数就是为了这个目的而存在的,当然,升级可能成功,也可能失败。如果失败了,那就用rollback进行回滚;如果过了一段时间,系统运行正常,那就可以通过finalize,正式提交这次升级(跟数据库有点像啊)。
importCheckpoint选项用于NameNode发生故障后,从某个检查点恢复。
有了上面的描述,我们得到下面左边的状态图:
大家应该注意到,上面的升级/回滚/提交都不可能一下就搞定,就是说,系统故障时,它可能处于上面右边状态中的某一个。特别是分布式的各个节点上,甚至可能出现某些节点已经升级成功,但有些节点可能处于中间状态的情况,所以Hadoop采用类似于数据库事务的升级机制也就不是很奇怪。
大家先理解一下上面的状态图,它是下面我们要介绍DataNode存储的基础。
我们来看一下升级/回滚/提交时的DataNode上会发生什么(在类DataStorage中实现)。
前面我们提到过VERSION文件,它保存了一些文件系统的元信息,这个文件在系统升级时,会发生对应的变化。
升级时,NameNode会将新的版本号,通过DataNode的登录应答返回。DataNode收到以后,会将当前的数据块文件目录改名,从current改名为
previous.tmp,建立一个snapshot,然后重建current目录。重建包括重建VERSION文件,重建对应的子目录,然后建立数据块文件和数据块元数据文件到
previous.tmp的硬连接。建立
硬连接意味着在系统中只保留一份数据块文件和数据块元数据文件,current和
previous.tmp中的相应文件,在存储中,只保留一份。当所有的这些工作完成以后,会在current里写入新的VERSION文件,并将
previous.tmp目录改名为previous,完成升级。
了解了升级的过程以后,回滚就相对简单。因为说有的旧版本信息都保存在
previous目录里。回滚首先将current目录改名为
removed.tmp,然后将previous目录改名为current,最后删除
removed.tmp目录。
提交的过程,就是将上面的
previous目录改名为finalized.tmp,然后启动一个线程,将该目录删除。
下图给出了上面的过程:
需要注意的是,HDFS的升级,往往只是支持从某一个特点的老版本升级到当前版本。回滚时能够恢复到的版本,也是previous中记录的版本。
下面我们继续分析DataNode。
文字分析完DataNode存储在文件上的数据以后,我们来看一下运行时对应的数据结构。从大到小,Hadoop中最大的结构是Storage,最小的结构,在DataNode上是block。
类Storage保存了和存储相关的信息,它继承了StorageInfo,应用于DataNode的DataStorage,则继承了Storage,总体类图如下:
StorageInfo包含了3个字段,分别是layoutVersion:版本号,如果Hadoop调整文件结构布局,版本号就会修改,这样可以保证文件结构和应用一致。namespaceID是Storage的ID,cTime,creation time。
和StorageInfo相比,Storage就是个大家伙了。
Storage可以包含多个根(参考配置项dfs.data.dir的说明),这些根通过Storage的内部类StorageDirectory来表示。StorageDirectory中最重要的方法是analyzeStorage,它将根据系统启动时的参数和我们上面提到的一些判断条件,返回系统现在的状态。StorageDirectory可能处于以下的某一个状态(与系统的工作状态一定的对应):
NON_EXISTENT:指定的目录不存在;
NOT_FORMATTED:指定的目录存在但未被格式化;
COMPLETE_UPGRADE:previous.tmp存在,current也存在
RECOVER_UPGRADE:previous.tmp存在,current不存在
COMPLETE_FINALIZE:finalized.tmp存在,current也存在
COMPLETE_ROLLBACK:removed.tmp存在,current也存在,previous不存在
RECOVER_ROLLBACK:removed.tmp存在,current不存在,previous存在
COMPLETE_CHECKPOINT:lastcheckpoint.tmp存在,current也存在
RECOVER_CHECKPOINT:lastcheckpoint.tmp存在,current不存在
NORMAL:普通工作模式。
StorageDirectory处于某些状态是通过发生对应状态改变需要的工作文件夹和正常工作的current夹来进行判断。状态改变需要的工作文件夹包括:
previous:用于升级后保存以前版本的文件
previous.tmp:用于升级过程中保存以前版本的文件
removed.tmp:用于回滚过程中保存文件
finalized.tmp:用于提交过程中保存文件
lastcheckpoint.tmp:应用于从NameNode中,导入一个检查点
previous.checkpoint:应用于从NameNode中,结束导入一个检查点
有了这些状态,就可以对系统进行恢复(通过方法doRecover)。恢复的动作如下(结合上面的状态转移图):
COMPLETE_UPGRADE:mvprevious.tmp -> previous
RECOVER_UPGRADE:mv previous.tmp -> current
COMPLETE_FINALIZE:rm finalized.tmp
COMPLETE_ROLLBACK:rm removed.tmp
RECOVER_ROLLBACK:mv removed.tmp -> current
COMPLETE_CHECKPOINT:mv lastcheckpoint.tmp -> previous.checkpoint
RECOVER_CHECKPOINT:mv lastcheckpoint.tmp -> current
我们以RECOVER_UPGRADE为例,分析一下。根据升级的过程,
1. current->previous.tmp
2. 重建current
3. previous.tmp->previous
当我们发现previous.tmp存在,current不存在,我们知道只需要将previous.tmp改为current,就能恢复到未升级时的状态。
StorageDirectory还管理着文件系统的元信息,就是我们上面提过StorageInfo信息,当然,StorageDirectory还保存每个具体用途自己的信息。这些信息,其实都存储在VERSION文件中,StorageDirectory中的read/write方法,就是用于对这个文件进行读/写。下面是某一个DataNode的VERSION文件的例子:
配置文件代码
- #Fri Nov 14 10:27:35 CST 2008
- namespaceID=1950997968
- storageID=DS-697414267-127.0.0.1-50010-1226629655026
- cTime=0
- storageType=DATA_NODE
- layoutVersion=-16
复制代码
#Fri Nov 14 10:27:35 CST 2008namespaceID=1950997968storageID=DS-697414267-127.0.0.1-50010-1226629655026cTime=0storageType=DATA_NODElayoutVersion=-16
对StorageDirectory的排他操作需要锁,还记得我们在分析系统目录时提到的in_use.lock文件吗?它就是用来给整个系统加/解锁用的。StorageDirectory提供了对应的lock和unlock方法。
分析完StorageDirectory以后,Storage类就很简单了。基本上都是对一系列StorageDirectory的操作,同时Storage提供一些辅助方法。
DataStorage是Storage的子类,专门应用于DataNode。上面我们对DataNode的升级/回滚/提交过程,就是对DataStorage的doUpgrade/doRollback/doFinalize分析得到的。
DataStorage提供了format方法,用于创建DataNode上的Storage,同时,利用StorageDirectory,DataStorage管理存储系统的状态。