分享

Hadoop必看:HDFS格式化过程分析

问题导读:
1、如何跟踪HDFS格式化 ?



我们知道,Namenode启动时可以指定不同的选项,当指定-format选项时,就是格式化Namenode,可以在Namenode类中看到格式化的方法,方法签名如下所示:
  1. private static boolean format(Configuration conf,
  2.       boolean isConfirmationNeeded, boolean isInteractive) throws IOException
复制代码

在该方法中,首先调用FSNamesystem类的方法,获取到待格式化的name目录和edit目录:
  1. Collection<File> editDirsToFormat = Collection<File>
复制代码

跟踪调用FSNamesystem类的方法,可以看到,实际上获取到的目录为:

  • name目录:是根据配置的dfs.name.dir属性,如果没有配置,默认使用目录/tmp/hadoop/dfs/name。
  • edit目录:是根据配置的dfs.name.edits.dir属性,如果没有配置,默认使用目录/tmp/hadoop/dfs/name。

在上面format方法中,创建对应的name目录和edit目录,对应如下代码行:


  1. FSNamesystem nsys = new FSNamesystem(new FSImage(dirsToFormat, editDirsToFormat), conf);
复制代码


实际上是调用FSImage对象的format方法格式化HDFS文件系统,调用代码如下所示:


  1. 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,在该抽象类中定义如下所示:


  1. protected List<StorageDirectory> storageDirs = new ArrayList<StorageDirectory>();
复制代码
这个列表中每个存储目录包含如下信息,如下Storage.StorageDirectory类图所示:

StorageDirectory.png

从类图中可以看到,主要包含如下三个信息:

  • root:配置的根目录路径
  • lock:一个FileLock文件锁对象,控制root下的写操作
  • dirType:表示StorageDirectory对象所使用目录的类型

一个dirType,它是Storage.StorageDirType类型的,Storage.StorageDirType是一个接口,定义如下所示:


  1. public interface StorageDirType {
  2.   public StorageDirType getStorageDirType();
  3.   public boolean isOfType(StorageDirType type);
  4. }
复制代码
那么,对于Namenode节点的目录的Storage.StorageDirectory对象,它对应的dirType的定义,是实现了Storage.StorageDirType接口的枚举类,定义如下所示:FSImage.NameNodeDirType
  1. static enum NameNodeDirType implements StorageDirType {
  2.   UNDEFINED,
  3.   IMAGE,
  4.   EDITS,
  5.   IMAGE_AND_EDITS;
  6.   public StorageDirType getStorageDirType() {
  7.     return this;
  8.   }
  9.   public boolean isOfType(StorageDirType type) {
  10.     if ((this == IMAGE_AND_EDITS) && (type == IMAGE || type == EDITS))
  11.       return true;
  12.     return this == type;
  13.   }
  14. }
复制代码


上述枚举类中定义的dirType恰好是前面我们提到的FSImage对象,所包含的实际Storage.StorageDirectory对象的类型,初始化FSImage对象时,就是确定了FSImage对象所包含的Storage.StorageDirectory对象列表及其它们的类型信息。




FSNamesystem对象初始化

FSNamesystem是个非常关键的类,它用来保存与Datanode相关的一些信息,如Block到Datanode的映射信息、StorageID到Datanode的映射信息等等。
前面调用的FSNamesystem的构造方法,如下所示:

  1. FSNamesystem(FSImage fsImage, Configuration conf) throws IOException {
  2.   setConfigurationParameters(conf);
  3.   this.dir = new FSDirectory(fsImage, this, conf);
  4.   dtSecretManager = createDelegationTokenSecretManager(conf);
  5. }
复制代码

初始化主要包括如下信息:

  • 方法setConfigurationParameters根据传递的conf对象来设置FSNamesystem使用的一些参数值;
  • 创建一个FSDirectory对象dir,该对象包含了一组用来维护Hadoop文件系统目录状态的操作,专门用来控制对目录的实际操作,如写操作、加载操作等,同时,它能够保持“文件->Block列表”的映射始终是最新的状态,并将变更记录到日志。
  • 创建了一个DelegationTokenSecretManager对象,用来管理HDFS的安全访问。

在FSNamesystem中,创建的FSDirectory对象dir,是整个HDFS文件系统的根目录。对应的FSDirectory dir内部有一个inode表示,它是带配额的INodeDirectoryWithQuota rootDir,详细可见下面分析。




FSDirectory对象初始化

FSDirectory对象是很关键的,该类内部定义了如下字段:

  1. final FSNamesystem namesystem;
  2. final INodeDirectoryWithQuota rootDir;
  3. FSImage fsImage;
  4. private boolean ready = false;
  5. private final int lsLimit;  // max list limit
  6. private final NameCache<ByteArray> nameCache;
复制代码


其中,rootDir表示一个带有配额限制的inode对象。下面我们看一下FSDirectory的构造方法:


  1. FSDirectory(FSImage fsImage, FSNamesystem ns, Configuration conf) {
  2.   rootDir = new INodeDirectoryWithQuota(INodeDirectory.ROOT_NAME,
  3.       ns.createFsOwnerPermissions(new FsPermission((short)0755)), Integer.MAX_VALUE, -1);
  4.   this.fsImage = fsImage;
  5.   fsImage.setRestoreRemovedDirs(conf.getBoolean(DFSConfigKeys.DFS_NAMENODE_NAME_DIR_RESTORE_KEY,
  6.       DFSConfigKeys.DFS_NAMENODE_NAME_DIR_RESTORE_DEFAULT));
  7.   fsImage.setEditsTolerationLength(conf.getInt(DFSConfigKeys.DFS_NAMENODE_EDITS_TOLERATION_LENGTH_KEY,
  8.       DFSConfigKeys.DFS_NAMENODE_EDITS_TOLERATION_LENGTH_DEFAULT));
  9.   namesystem = ns;
  10.   int configuredLimit = conf.getInt(DFSConfigKeys.DFS_LIST_LIMIT, DFSConfigKeys.DFS_LIST_LIMIT_DEFAULT);
  11.   this.lsLimit = configuredLimit>0 ?
  12.       configuredLimit : DFSConfigKeys.DFS_LIST_LIMIT_DEFAULT;
  13.   int threshold = conf.getInt(DFSConfigKeys.DFS_NAMENODE_NAME_CACHE_THRESHOLD_KEY,
  14.       DFSConfigKeys.DFS_NAMENODE_NAME_CACHE_THRESHOLD_DEFAULT);
  15.   NameNode.LOG.info("Caching file names occuring more than " + threshold + " times ");
  16.   nameCache = new NameCache<ByteArray>(threshold);
  17. }
复制代码

这里创建了一个rootDir对象,如果我们调试跟踪该处代码,用户名为shirdrn,它的值可以表示如下:


  1. "":shirdrn:supergroup:rwxr-xr-x
复制代码


可见,对于FSNamesystem对象所维护的namespace中,inode对象包含目录名称、所属用户、所属用户组、操作权限信息。
上面构造方法中初始化了一个NameCache缓存对象,用来缓存经常用到的文件,这里提供了一个threshold值,默认为10。也就是如果当一个文件被访问的次数超过threshold指定的值,就会将该文件名称放进NameCache缓存中,实际上是该文件名称的字节码的ByteArray表示形式作为Key,它唯一表示了一个文件的inode节点。在NameCache内部,实际是将放到了其内部的HashMap集合中,Key是文件名称的ByteArray表示形式,Value封装了文件被访问的计数信息。





格式化HDFS

调用FSImage对象的format方法,该方法实现代码,如下所示:


  1. public void format() throws IOException {
  2.   this.layoutVersion = FSConstants.LAYOUT_VERSION;
  3.   this.namespaceID = newNamespaceID();
  4.   this.cTime = 0L;
  5.   this.checkpointTime = FSNamesystem.now();
  6.   for (Iterator<StorageDirectory> it = dirIterator(); it.hasNext();) {
  7.     StorageDirectory sd = it.next();
  8.     format(sd);
  9.   }
  10. }
复制代码


根据上面代码逻辑,详细说明如下:

  • layoutVersion

layoutVersion定义了HDFS持久化数据结构的版本号,它的值是负值。当HDFS的持久化数据结构发生了变化,如增加了一些其他的操作或者字段信息,则版本号会在原来的基础上减1。Hadoop 1.2.1版本中,layoutVersion的值是-41,它与Hadoop的发行版本号是两回事,如果layoutVersion的值变化了(通过减1变化,实际layoutVersion的值更小了),则如果能够读取原来旧版本的数据,必须执行一个升级(Upgrade)过程。layoutVersion主要在fsimage和edit日志文件、数据存储文件中使用。


  • namespaceID

namespaceID唯一标识了HDFS,在格式化HDFS的时候指定了它的值。在HDFS集群启动以后,使用namespaceID来识别集群中的Datanode节点,也就是说,在HDFS集群启动的时候,各个Datanode会自动向Namenode注册获取到namespaceID的值,然后在该值存储在Datanode节点的VERSION文件中。


  • cTime

cTime表示Namenode存储对象(即FSImage对象)创建的时间,但是在初始化时它的值为0。如果由于layoutVersion发生变化触发了一次升级过程,则会更新该事件字段的值。


  • checkpointTime

checkpointTime用来控制检查点(Checkpoint)的执行,为了在集群中获取到同步的时间,使用通过调用FSNamesystem对象的的now方法来生成时间戳。Hadoop使用检查点技术来实现Namenode存储数据的可靠性,如果因为Namenode节点宕机而无法恢复数据,则整个集群将无法工作。


  • 格式化StorageDirectory对象

我们知道,每一个Storage对象都包含一个StorageDirectory列表,FSImage就是Namenode用来存储数据的对象的实现,上面代码中通过for循环分别格式化每一个StorageDirectory对象,对应的format方法代码,如下所示:


  1. void format(StorageDirectory sd) throws IOException {
  2.   sd.clearDirectory(); // create currrent dir
  3. sd.lock();
  4. try {
  5.     saveCurrent(sd);
  6.   } finally {
  7.     sd.unlock();
  8.   }
  9.   LOG.info("Storage directory " + sd.getRoot() + " has been successfully formatted.");
  10. }
复制代码
上面调用sd.lock()会创建一个${dfs.name.dir}/in_use.lock锁文件,用来保证当前只有同一个进程能够执行格式化操作。格式化的关键逻辑,都在saveCurrent方法中,代码如下所示:
  1. protected void saveCurrent(StorageDirectory sd) throws IOException {
  2.   File curDir = sd.getCurrentDir();
  3.   NameNodeDirType dirType = (NameNodeDirType)sd.getStorageDirType();
  4.   // save new image or new edits
  5.   if (!curDir.exists() && !curDir.mkdir())
  6.     throw new IOException("Cannot create directory " + curDir);
  7.   if (dirType.isOfType(NameNodeDirType.IMAGE))
  8.     saveFSImage(getImageFile(sd, NameNodeFile.IMAGE));
  9.   if (dirType.isOfType(NameNodeDirType.EDITS))
  10.     editLog.createEditLogFile(getImageFile(sd, NameNodeFile.EDITS));
  11.   // write version and time files
  12.   sd.write();
  13. }
复制代码

每一个StorageDirectory对象代表一个存储目录的抽象,包含root、lock、和dirType三个属性,在格式化过程中,如果已经存在则要首先删除,然后创建对应的目录。该目录实际的绝对路径为:
  1. ${dfs.name.dir}/current/
复制代码

指定了根目录,就要创建对应的文件,这里面会生成文件fsimage、edits两个重要的文件,我们分别详细说明这两个文件中保存的内容:

  • 初始化fsimage文件数据

对应代码行如下:


  1. if (dirType.isOfType(NameNodeDirType.IMAGE))
  2.   saveFSImage(getImageFile(sd, NameNodeFile.IMAGE));
复制代码
如果StorageDirectory对象的dirType为IMAGE,则会在上面的current目录下创建一个文件:

  1. ${dfs.name.dir}/current/fsimage
复制代码

可以通过saveFSImage方法看到,主要执行的操作,将数据存储到fsimage文件中,代码如下所示:


  1. try {
  2.   out.writeInt(FSConstants.LAYOUT_VERSION);
  3.   out.writeInt(namespaceID);
  4.   out.writeLong(fsDir.rootDir.numItemsInTree());
  5.   out.writeLong(fsNamesys.getGenerationStamp());
  6.   byte[] byteStore = new byte[4*FSConstants.MAX_PATH_LENGTH];
  7.   ByteBuffer strbuf = ByteBuffer.wrap(byteStore);
  8.   // save the root
  9.   saveINode2Image(strbuf, fsDir.rootDir, out);
  10.   // save the rest of the nodes
  11.   saveImage(strbuf, 0, fsDir.rootDir, out);
  12.   fsNamesys.saveFilesUnderConstruction(out);
  13.   fsNamesys.saveSecretManagerState(out);
  14.   strbuf = null;
  15. } finally {
  16.   out.close();
  17. }
复制代码


首先,保存了文件系统的一些基本信息,如下表所示:

序号
字段
类型
说明
1
layoutVersionint-47,Hadoop-1.2.1对应的layoutVersion=-41
2
namespaceIDint标识HDFS的namespaceID
3
numItemsInTreelong1,当前只有文件系统root目录,对应于nsCount的值(Namespace Count)
4
generationStamplongFSNamesystem文件系统生成的时间戳


其次,调用saveINode2Image方法中,保存了文件系统的root目录名称、长度,以及inode信息,如下表所示:


序号
字段
类型
说明
1
nameLenshort0,文件系统的root目录名为”",长度为0
2
namebyte[]文件系统的root目录名的字节数组,实际上一个空字节数组
3
replicationshort0
4
modificationTimelongroot目录inode修改时间
5
accessTimelong0
6
preferredBlockSizelong0
7
blocksint-1
8
nsQuotalong2147483647,即Integer.MAX_VALUE
9
dsQuotalong-1
10
usernameString用户名
11
groupnameString用户组名
12
permissionshort493,可以跟踪代码计算得到

然后,调用saveImage方法,保存了从root目录开始的剩余其他目录节点的信息。saveImage方法是一个递归方法,它能够根据给定的root目录来保存该目录下所有目录或文件的信息。我们知道,到目前为止,只是创建一个文件系统的root目录,并没有对应的孩子inode节点,所以这一步实际上没有存储任何inode信息。
接着,fsNamesys.saveFilesUnderConstruction(out)保存root目录的租约信息(Lease),代码如下所示:
  1. void saveFilesUnderConstruction(DataOutputStream out) throws IOException {
  2.   synchronized (leaseManager) {
  3.     out.writeInt(leaseManager.countPath()); // write the size
  4. for (Lease lease : leaseManager.getSortedLeases()) {
  5.       for(String path : lease.getPaths()) {
  6.         // verify that path exists in namespace
  7.         INode node = dir.getFileINode(path);
  8.         if (node == null) {
  9.           throw new IOException("saveLeases found path " + path + " but no matching entry in namespace.");
  10.         }
  11.         if (!node.isUnderConstruction()) {
  12.           throw new IOException("saveLeases found path " + path + " but is not under construction.");
  13.         }
  14.         INodeFileUnderConstruction cons = (INodeFileUnderConstruction) node;
  15.         FSImage.writeINodeUnderConstruction(out, cons, path);
  16.       }
  17.     }
  18.   }
  19. }
复制代码

这里,leaseManager.countPath()的值为0,此时还没有任何文件的租约信息,所以for循环没有执行,此处只是写入了一个0值,表示leaseManager对象所管理的path的数量为0,如下表所示:

序号
字段
类型
说明
1
countPathint0,leaseManager管理的path总数








已有(3)人评论

跳转到指定楼层
wanghua_zwh 发表于 2014-5-15 08:53:49
分析的挺详细的。。。。。
回复

使用道具 举报

l290781453 发表于 2017-2-6 22:41:56
很详细,多谢楼主分享
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条