您好,欢迎访问一九零五行业门户网

HDFS格式化过程分析

我们知道,namenode启动时可以指定不同的选项,当指定-format选项时,就是格式化namenode,可以在namenode类中看到格式化的方法,方法签名如下所示: private static boolean format(configuration conf, boolean isconfirmationneeded, boolean isinteracti
我们知道,namenode启动时可以指定不同的选项,当指定-format选项时,就是格式化namenode,可以在namenode类中看到格式化的方法,方法签名如下所示:
private static boolean format(configuration conf, boolean isconfirmationneeded, boolean isinteractive) throws ioexception
在该方法中,首先调用fsnamesystem类的方法,获取到待格式化的name目录和edit目录:
collection editdirstoformat = collection dirstoformat = fsnamesystem.getnamespacedirs(conf);fsnamesystem.getnamespaceeditsdirs(conf);
跟踪调用fsnamesystem类的方法,可以看到,实际上获取到的目录为:
name目录:是根据配置的dfs.name.dir属性,如果没有配置,默认使用目录/tmp/hadoop/dfs/name。edit目录:是根据配置的dfs.name.edits.dir属性,如果没有配置,默认使用目录/tmp/hadoop/dfs/name。在上面format方法中,创建对应的name目录和edit目录,对应如下代码行:
fsnamesystem nsys = new fsnamesystem(new fsimage(dirstoformat, editdirstoformat), conf);
实际上是调用fsimage对象的format方法格式化hdfs文件系统,调用代码如下所示:
nsys.dir.fsimage.format();
下面,对上面提到的关键操作进行详细说明:
fsimage对象初始化
从上面用到的fsimage的构造方法,我们可以看到,在创建namenode的目录对象时,主要是按照name和edit目录分别进行处理的:对于name目录,对应的存储目录类型可能是image或者image_and_edits,当配置的name目录和edit目录相同时,类型为image_and_edits,不同时类型为image;对于edit目录,类型就是edits。
name和edit目录实际上就是fsimage对象所包含的内容,这个fsimage对象包含一个storagedirectory对象列表,而fsimage继承自抽象类org.apache.hadoop.hdfs.server.common.storage,在该抽象类中定义如下所示:
protected list storagedirs = new arraylist();
这个列表中每个存储目录包含如下信息,如下storage.storagedirectory类图所示:
从类图中可以看到,主要包含如下三个信息:
root:配置的根目录路径lock:一个filelock文件锁对象,控制root下的写操作dirtype:表示storagedirectory对象所使用目录的类型一个dirtype,它是storage.storagedirtype类型的,storage.storagedirtype是一个接口,定义如下所示:
public interface storagedirtype { public storagedirtype getstoragedirtype(); public boolean isoftype(storagedirtype type); }
那么,对于namenode节点的目录的storage.storagedirectory对象,它对应的dirtype的定义,是实现了storage.storagedirtype接口的枚举类,定义如下所示:fsimage.namenodedirtype
static enum namenodedirtype implements storagedirtype { undefined, image, edits, image_and_edits; public storagedirtype getstoragedirtype() { return this; } public boolean isoftype(storagedirtype type) { if ((this == image_and_edits) && (type == image || type == edits)) return true; return this == type; } }
上述枚举类中定义的dirtype恰好是前面我们提到的fsimage对象,所包含的实际storage.storagedirectory对象的类型,初始化fsimage对象时,就是确定了fsimage对象所包含的storage.storagedirectory对象列表及其它们的类型信息。
fsnamesystem对象初始化
fsnamesystem是个非常关键的类,它用来保存与datanode相关的一些信息,如block到datanode的映射信息、storageid到datanode的映射信息等等。
前面调用的fsnamesystem的构造方法,如下所示:
fsnamesystem(fsimage fsimage, configuration conf) throws ioexception { setconfigurationparameters(conf); this.dir = new fsdirectory(fsimage, this, conf); dtsecretmanager = createdelegationtokensecretmanager(conf); }
初始化主要包括如下信息:
方法setconfigurationparameters根据传递的conf对象来设置fsnamesystem使用的一些参数值;创建一个fsdirectory对象dir,该对象包含了一组用来维护hadoop文件系统目录状态的操作,专门用来控制对目录的实际操作,如写操作、加载操作等,同时,它能够保持“文件->block列表”的映射始终是最新的状态,并将变更记录到日志。创建了一个delegationtokensecretmanager对象,用来管理hdfs的安全访问。在fsnamesystem中,创建的fsdirectory对象dir,是整个hdfs文件系统的根目录。对应的fsdirectory dir内部有一个inode表示,它是带配额的inodedirectorywithquota rootdir,详细可见下面分析。
fsdirectory对象初始化
fsdirectory对象是很关键的,该类内部定义了如下字段:
final fsnamesystem namesystem; final inodedirectorywithquota rootdir; fsimage fsimage; private boolean ready = false; private final int lslimit; // max list limit private final namecache namecache;
其中,rootdir表示一个带有配额限制的inode对象。下面我们看一下fsdirectory的构造方法:
fsdirectory(fsimage fsimage, fsnamesystem ns, configuration conf) { rootdir = new inodedirectorywithquota(inodedirectory.root_name, ns.createfsownerpermissions(new fspermission((short)0755)), integer.max_value, -1); this.fsimage = fsimage; fsimage.setrestoreremoveddirs(conf.getboolean(dfsconfigkeys.dfs_namenode_name_dir_restore_key, dfsconfigkeys.dfs_namenode_name_dir_restore_default)); fsimage.seteditstolerationlength(conf.getint(dfsconfigkeys.dfs_namenode_edits_toleration_length_key, dfsconfigkeys.dfs_namenode_edits_toleration_length_default)); namesystem = ns; int configuredlimit = conf.getint(dfsconfigkeys.dfs_list_limit, dfsconfigkeys.dfs_list_limit_default); this.lslimit = configuredlimit>0 ? configuredlimit : dfsconfigkeys.dfs_list_limit_default; int threshold = conf.getint(dfsconfigkeys.dfs_namenode_name_cache_threshold_key, dfsconfigkeys.dfs_namenode_name_cache_threshold_default); namenode.log.info(caching file names occuring more than + threshold + times ); namecache = new namecache(threshold); }
这里创建了一个rootdir对象,如果我们调试跟踪该处代码,用户名为shirdrn,它的值可以表示如下:
:shirdrn:supergroup:rwxr-xr-x
可见,对于fsnamesystem对象所维护的namespace中,inode对象包含目录名称、所属用户、所属用户组、操作权限信息。
上面构造方法中初始化了一个namecache缓存对象,用来缓存经常用到的文件,这里提供了一个threshold值,默认为10。也就是如果当一个文件被访问的次数超过threshold指定的值,就会将该文件名称放进namecache缓存中,实际上是该文件名称的字节码的bytearray表示形式作为key,它唯一表示了一个文件的inode节点。在namecache内部,实际是将放到了其内部的hashmap集合中,key是文件名称的bytearray表示形式,value封装了文件被访问的计数信息。
格式化hdfs
调用fsimage对象的format方法,该方法实现代码,如下所示:
public void format() throws ioexception { this.layoutversion = fsconstants.layout_version; this.namespaceid = newnamespaceid(); this.ctime = 0l; this.checkpointtime = fsnamesystem.now(); for (iterator it = diriterator(); it.hasnext();) { storagedirectory sd = it.next(); format(sd); } }
根据上面代码逻辑,详细说明如下:
layoutversionlayoutversion定义了hdfs持久化数据结构的版本号,它的值是负值。当hdfs的持久化数据结构发生了变化,如增加了一些其他的操作或者字段信息,则版本号会在原来的基础上减1。hadoop 1.2.1版本中,layoutversion的值是-41,它与hadoop的发行版本号是两回事,如果layoutversion的值变化了(通过减1变化,实际layoutversion的值更小了),则如果能够读取原来旧版本的数据,必须执行一个升级(upgrade)过程。layoutversion主要在fsimage和edit日志文件、数据存储文件中使用。
namespaceidnamespaceid唯一标识了hdfs,在格式化hdfs的时候指定了它的值。在hdfs集群启动以后,使用namespaceid来识别集群中的datanode节点,也就是说,在hdfs集群启动的时候,各个datanode会自动向namenode注册获取到namespaceid的值,然后在该值存储在datanode节点的version文件中。
ctimectime表示namenode存储对象(即fsimage对象)创建的时间,但是在初始化时它的值为0。如果由于layoutversion发生变化触发了一次升级过程,则会更新该事件字段的值。
checkpointtimecheckpointtime用来控制检查点(checkpoint)的执行,为了在集群中获取到同步的时间,使用通过调用fsnamesystem对象的的now方法来生成时间戳。hadoop使用检查点技术来实现namenode存储数据的可靠性,如果因为namenode节点宕机而无法恢复数据,则整个集群将无法工作。
格式化storagedirectory对象我们知道,每一个storage对象都包含一个storagedirectory列表,fsimage就是namenode用来存储数据的对象的实现,上面代码中通过for循环分别格式化每一个storagedirectory对象,对应的format方法代码,如下所示:
void format(storagedirectory sd) throws ioexception { sd.cleardirectory(); // create currrent dir sd.lock(); try { savecurrent(sd); } finally { sd.unlock(); } log.info(storage directory + sd.getroot() + has been successfully formatted.); }
上面调用sd.lock()会创建一个${dfs.name.dir}/in_use.lock锁文件,用来保证当前只有同一个进程能够执行格式化操作。格式化的关键逻辑,都在savecurrent方法中,代码如下所示:
protected void savecurrent(storagedirectory sd) throws ioexception { file curdir = sd.getcurrentdir(); namenodedirtype dirtype = (namenodedirtype)sd.getstoragedirtype(); // save new image or new edits if (!curdir.exists() && !curdir.mkdir()) throw new ioexception(cannot create directory + curdir); if (dirtype.isoftype(namenodedirtype.image)) savefsimage(getimagefile(sd, namenodefile.image)); if (dirtype.isoftype(namenodedirtype.edits)) editlog.createeditlogfile(getimagefile(sd, namenodefile.edits)); // write version and time files sd.write(); }
每一个storagedirectory对象代表一个存储目录的抽象,包含root、lock、和dirtype三个属性,在格式化过程中,如果已经存在则要首先删除,然后创建对应的目录。该目录实际的绝对路径为:
${dfs.name.dir}/current/
指定了根目录,就要创建对应的文件,这里面会生成文件fsimage、edits两个重要的文件,我们分别详细说明这两个文件中保存的内容:
初始化fsimage文件数据对应代码行如下:
if (dirtype.isoftype(namenodedirtype.image)) savefsimage(getimagefile(sd, namenodefile.image));
如果storagedirectory对象的dirtype为image,则会在上面的current目录下创建一个文件:
${dfs.name.dir}/current/fsimage
可以通过savefsimage方法看到,主要执行的操作,将数据存储到fsimage文件中,代码如下所示:
try { out.writeint(fsconstants.layout_version); out.writeint(namespaceid); out.writelong(fsdir.rootdir.numitemsintree()); out.writelong(fsnamesys.getgenerationstamp()); byte[] bytestore = new byte[4*fsconstants.max_path_length]; bytebuffer strbuf = bytebuffer.wrap(bytestore); // save the root saveinode2image(strbuf, fsdir.rootdir, out); // save the rest of the nodes saveimage(strbuf, 0, fsdir.rootdir, out); fsnamesys.savefilesunderconstruction(out); fsnamesys.savesecretmanagerstate(out); strbuf = null; } finally { out.close(); }
首先,保存了文件系统的一些基本信息,如下表所示:
序号 字段 类型 说明
1 layoutversion int -47,hadoop-1.2.1对应的layoutversion=-41
2 namespaceid int 标识hdfs的namespaceid
3 numitemsintree long 1,当前只有文件系统root目录,对应于nscount的值(namespace count)
4 generationstamp long fsnamesystem文件系统生成的时间戳
其次,调用saveinode2image方法中,保存了文件系统的root目录名称、长度,以及inode信息,如下表所示:
序号 字段 类型 说明
1 namelen short 0,文件系统的root目录名为””,长度为0
2 name byte[] 文件系统的root目录名的字节数组,实际上一个空字节数组
3 replication short 0
4 modificationtime long root目录inode修改时间
5 accesstime long 0
6 preferredblocksize long 0
7 blocks int -1
8 nsquota long 2147483647,即integer.max_value
9 dsquota long -1
10 username string 用户名
11 groupname string 用户组名
12 permission short 493,可以跟踪代码计算得到
然后,调用saveimage方法,保存了从root目录开始的剩余其他目录节点的信息。saveimage方法是一个递归方法,它能够根据给定的root目录来保存该目录下所有目录或文件的信息。我们知道,到目前为止,只是创建一个文件系统的root目录,并没有对应的孩子inode节点,所以这一步实际上没有存储任何inode信息。
接着,fsnamesys.savefilesunderconstruction(out)保存root目录的租约信息(lease),代码如下所示:
void savefilesunderconstruction(dataoutputstream out) throws ioexception { synchronized (leasemanager) { out.writeint(leasemanager.countpath()); // write the size for (lease lease : leasemanager.getsortedleases()) { for(string path : lease.getpaths()) { // verify that path exists in namespace inode node = dir.getfileinode(path); if (node == null) { throw new ioexception(saveleases found path + path + but no matching entry in namespace.); } if (!node.isunderconstruction()) { throw new ioexception(saveleases found path + path + but is not under construction.); } inodefileunderconstruction cons = (inodefileunderconstruction) node; fsimage.writeinodeunderconstruction(out, cons, path); } } } }
这里,leasemanager.countpath()的值为0,此时还没有任何文件的租约信息,所以for循环没有执行,此处只是写入了一个0值,表示leasemanager对象所管理的path的数量为0,如下表所示:
序号 字段 类型 说明
1 countpath int 0,leasemanager管理的path总数
调用fsnamesys.savesecretmanagerstate(out)保存secretmanager的状态信息,跟踪代码可以看到在delegationtokensecretmanager类中的savesecretmanagerstate,如下所示:
public synchronized void savesecretmanagerstate(dataoutputstream out) throws ioexception { out.writeint(currentid); saveallkeys(out); out.writeint(delegationtokensequencenumber); savecurrenttokens(out); }
顺序写入的字段数据,如下表所示:
序号 字段 类型 说明
1 currentid int 0
2 allkeysize int 0,所有的delegationkey数量。(如不为0,后面会序列化每个delegationkey对象)
3 delegationtokensequencenumber int 0
4 currenttokens int 0,所有delegationtokeninformation数量。(如不为0,后面会序列化每个)delegationtokeninformation对象)
上面的内容,都是fsimage文件保存的数据内容。
初始化edits文件数据对应代码行如下所示:
if (dirtype.isoftype(namenodedirtype.edits)) editlog.createeditlogfile(getimagefile(sd, namenodefile.edits));
首先获取到edits文件名称,亦即文件:
${dfs.name.dir}/current/edits
然后调用editlog对象的createeditlogfile方法真正创建该文件,方法实现如下所示:
public synchronized void createeditlogfile(file name) throws ioexception { editlogoutputstream estream = new editlogfileoutputstream(name); estream.create(); estream.close(); }
创建了一个流对象editlogoutputstream estream,并初始化一些基本信息以用来操作edits文件,通过create方法可以很清楚地看到,如下所示:
@override void create() throws ioexception { fc.truncate(0); fc.position(0); bufcurrent.writeint(fsconstants.layout_version); setreadytoflush(); flush(); }
序列化写入了layoutversion的值,这里是-41。
在editlogoutputstream内部维护了2个buffer,一个是bufcurrent,另一个是bufready,当有数据要写入时首先写入bufcurrent,然后将bufcurrent与bufready交换,这时bufcurrent空闲了,可以继续写入新的数据,而bufready中的数据会在调用flush()方法时被持久化写入到edits文件中。其中,上面的setreadytoflush()方法就是用来交换2个buffer的。flush()方法调用了fseditlog类的flushandsync()方法最终写入到文件中,可以简单看一下对应的代码实现:
@override protected void flushandsync() throws ioexception { preallocate(); // preallocate file if necessary bufready.writeto(fp); // write data to file bufready.reset(); // erase all data in the buffer fc.force(false); // metadata updates not needed because of preallocation }
这样,edits文件已经完成初始化。
初始化version文件数据上面sd.write()完成了version文件的初始化,实现代码在storage.storagedirectory.write()方法中,代码如下所示:
public void write() throws ioexception { corruptpreupgradestorage(root); write(getversionfile()); }
调用corruptpreupgradestorage方法检查是否是hdfs需要升级,如果需要升级,格式化过程失败(此时如果遗留的image目录存在),方法的实现如下所示:
protected void corruptpreupgradestorage(file rootdir) throws ioexception { file oldimagedir = new file(rootdir, image); if (!oldimagedir.exists()) if (!oldimagedir.mkdir()) throw new ioexception(cannot create directory + oldimagedir); file oldimage = new file(oldimagedir, fsimage); if (!oldimage.exists()) // recreate old image file to let pre-upgrade versions fail if (!oldimage.createnewfile()) throw new ioexception(cannot create file + oldimage); randomaccessfile oldfile = new randomaccessfile(oldimage, rws); // write new version into old image file try { writecorrupteddata(oldfile); } finally { oldfile.close(); } }
首先,如果在${dfs.name.dir}下面不存在image目录,则创建该目录,然后在image目录下面创建文件fsimage,写入该文件的数据内容,如下表所示:
序号 字段 类型 说明
1 layoutversion int -47,hadoop-1.2.1对应的layoutversion=-41
2 “”.length() short 0,写入一个空字符””的长度,即0
3 “” char 空字符,显然,实际并没有写入该值
4 messageforpreupgradeversion string 写入如下预升级提示消息:“\nthis file is intentionally corrupted so that versions\nof hadoop prior to 0.13 (which are incompatible\nwith this directory layout) will fail to start.\n”。
如果执行corruptpreupgradestorage方法没有抛出异常,则这时开始初始化version文件,该文件路径为${dfs.name.dir}/current/version,调用write(getversionfile())来实现,主要是通过一个properties props对象,将对应的属性信息写入version文件,可以通过setfields方法看到:
protected void setfields(properties props, storagedirectory sd) throws ioexception { super.setfields(props, sd); boolean ustate = getdistributedupgradestate(); int uversion = getdistributedupgradeversion(); if(ustate && uversion != getlayoutversion()) { props.setproperty(distributedupgradestate, boolean.tostring(ustate)); props.setproperty(distributedupgradeversion, integer.tostring(uversion)); } writecheckpointtime(sd); }
调用基类的super.setfields(props, sd);方法,实现如下所示:
protected void setfields(properties props, storagedirectory sd) throws ioexception { props.setproperty(layoutversion, string.valueof(layoutversion)); props.setproperty(storagetype, storagetype.tostring()); props.setproperty(namespaceid, string.valueof(namespaceid)); props.setproperty(ctime, string.valueof(ctime)); }
综合上面分析,可以看到,对应写入到version文件的内容如下所示:
序号 字段 类型 说明
1 layoutversion string -47,hadoop-1.2.1对应的layoutversion=-41
2 storagetype string name_node
3 namespaceid string 对应的namespaceid值
4 ctime string 0,初始化为0
上面代码中ustate=false,uversion=0,getlayoutversion()=-41,所以属性distributedupgradestate和distributedupgradeversion没有添加到properties中,例如,properties中的属性数据类似如下内容:
{namespaceid=64614865, ctime=0, storagetype=name_node, layoutversion=-41}
数据并没直接写入version,而是等到初始化fstime文件完成之后,延迟初始化version文件,以及,写入fstime文件先于写入version文件。
初始化fstime文件数据在初始化version文件时,调用了writecheckpointtime(sd)方法,写入checkpointtime到文件${dfs.name.dir}/current/fstime中,代码如下所示:
void writecheckpointtime(storagedirectory sd) throws ioexception { if (checkpointtime 实际上写入fstime文件的只是检查点的时间,如下表所示:

序号 字段 类型 说明
1 checkpointtime long 检查点时间戳,例如:1398180263639
格式化实例分析
下面,我们通过配置hadoop-1.2.1,并执行hdfs的格式化操作,观察对应的目录的结构和数据。
首先配置hadoop,各个配置文件如下所示:
配置项 配置值 配置文件
fs.default.name hdfs://localhost:9000 core-site.xml
dfs.replication 1 hdfs-site.xml
dfs.name.dir /home/shirdrn/programs/hadoop/dfs/name hdfs-site.xml
dfs.data.dir /home/shirdrn/programs/hadoop/dfs/data hdfs-site.xml
格式化后,在/home/shirdrn/programs/hadoop/dfs/name/current目录下生成如下4个文件:
editsfsimagefstimeversion
上面4个文件中,version文件实际上是一个properties文件,它的内容是可读的字符串信息,内容如下所示:
#thu apr 10 21:49:18 pdt 2014namespaceid=1858400315ctime=0storagetype=name_nodelayoutversion=-41
第一次进行格式化,ctime=0。
对于其它几个文件,使用了java的序列化方式进行存储,不是字符串可读格式的,可以参考源代码中实际序列化写入的内容,见上面给出的表格中列出的字段信息。
原文地址:hdfs格式化过程分析, 感谢原作者分享。
其它类似信息

推荐信息