Hadoop-0.20.0源代码分析(19)

这里,对重要的FSImage类进行阅读分析。

该类的继承层次关系如下所示: 

  1. ◦org.apache.hadoop.hdfs.server.common.StorageInfo  
  2.      ◦org.apache.hadoop.hdfs.server.common.Storage  
  3.           ◦org.apache.hadoop.hdfs.server.namenode.FSImage  

我们一个一个地分析:

  • StorageInfo类

该类是一个存储信息的通用实体类, 该类定义了如下三个属性:

  1. public int   layoutVersion;  // 从存储的文件中读取版本信息.   
  2. public int   namespaceID;    // 文件系统命名空间的存储ID   
  3. public long  cTime;          // 存储(文件或目录)访问时间  

其中,namespaceID表示的是文件系统命名空间的存储ID,它的生成基于Hash算法的,根据address + port来进行散列得到的。

  • Storage类

该类继承自StorageInfo,是一个抽象类。

本地存储信息被存储在一个单独的文件VERSION中,它包含结点的类型、存储布局(Layout)版本、 文件系统命名空间ID、文件系统状态的创建时间。本地存储可以分布在多个目录中,每个目录中应该保存着相同的VERSION文件,当Hadoop servers (name-node and data-nodes)启动的时候从这些VERSION文件中读取本地的存储信息。当Hadoop servers启动的时候,会对每个存储目录都持有一把锁,这是为了使得其他的结点不能因为共享同一个存储目录而启动。当Hadoop servers停止的时候,会释放掉它们所持有的锁,以便其它结点来使用该存储目录。

首先,看存储状态的枚举定义:

  1. public enum StorageState {  
  2.   NON_EXISTENT, // 不存在   
  3.   NOT_FORMATTED, // 未格式化   
  4.   COMPLETE_UPGRADE, // 完成升级   
  5.   RECOVER_UPGRADE, // 恢复升级   
  6.   COMPLETE_FINALIZE, // 完成确认   
  7.   COMPLETE_ROLLBACK, // 完成回滚   
  8.   RECOVER_ROLLBACK, // 恢复回滚   
  9.   COMPLETE_CHECKPOINT, // 完成检查点   
  10.   RECOVER_CHECKPOINT, // 恢复检查点   
  11.   NORMAL; // 正常   
  12. }  

下面是与存储相关的几个文件或目录:

  1. private   static final String STORAGE_FILE_LOCK     = "in_use.lock";  
  2. protected static final String STORAGE_FILE_VERSION  = "VERSION";  
  3. public static final String STORAGE_DIR_CURRENT   = "current";  
  4. private   static final String STORAGE_DIR_PREVIOUS  = "previous";  
  5. private   static final String STORAGE_TMP_REMOVED   = "removed.tmp";  
  6. private   static final String STORAGE_TMP_PREVIOUS  = "previous.tmp";  
  7. private   static final String STORAGE_TMP_FINALIZED = "finalized.tmp";  
  8. private   static final String STORAGE_TMP_LAST_CKPT = "lastcheckpoint.tmp";  
  9. private   static final String STORAGE_PREVIOUS_CKPT = "previous.checkpoint";  

我们再看一下,该类中定义的3个与存储相关的内部类:

1、StorageDirType接口类

该接口定义了与存储目录的类型相关的方法:

  1. public interface StorageDirType {  
  2.   public StorageDirType getStorageDirType(); // 获取存储目录类型   
  3.   public boolean isOfType(StorageDirType type); // 指定存储目录是否是指定的存储目录类型type   
  4. }  

Storage类中,并没有实现该存储目录类型的内部类。

2、StorageDirectory类: 

该类表示一个单独的存储目录的实体类,它定义了如下三个属性:

  1. File              root; // 根目录   
  2. FileLock          lock; // 存储锁   
  3. StorageDirType dirType; // 存储目录类型  

前面提到,每个存储对应一个VERSION文件,也就是在每个存储目录中保存。

StorageDirectory类提供了对VERSION文件的读写操作,而且还给出了对一个存储目录进行一致性检查的操作analyzeStorage:根据Hadoop servers启动的选项StartupOption,来返回不正常的存储状态,如果正常启动则检查上面与存储相关的文件或目录是否存在,并返回对应的存储状态。

该类中定义的doRecover方法,能够从上一个失败的事务中来完成或恢复存储状态,主要包括对下述状态进行恢复操作:

COMPLETE_UPGRADE:  将previous.tmp重命名为previous

RECOVER_UPGRADE:    将previous.tmp重命名为current

COMPLETE_ROLLBACK:   删除removed.tmp

RECOVER_ROLLBACK:    将removed.tmp重命名为current

COMPLETE_CHECKPOINT:  将lastcheckpoint.tmp重命名为 previous.checkpoint

RECOVER_CHECKPOINT:   将lastcheckpoint.tmp重命名为current

另外,StorageDirectory类能够控制对当前存储进行加锁lock、解锁unlock、尝试获取锁tryLock的操作。

3、DirIterator类

该类是StorageDirectory的迭代器类,用来方便地迭代出一个StorageDirectory列表中的StorageDirectory存储目录实例。

 

下面,继续看Storage类的实现。

用来描述一个存储的信息,应该包括使用该存储的结点(通过结点类型来标识) 、该存储包含的目录,如下所示:

  1. private NodeType storageType;    // 使用该存储的结点的类型(包括NodeType.NAME_NODE与NodeTypeDATA_NODE这两种)    
  2. protected List<StorageDirectory> storageDirs = new ArrayList<StorageDirectory>();  

基本上,在Storage类中实现的操作都是与存储目录相关的,比如,获取到一个StorageDirectory列表的迭代器实例、删除目录等等,可以查看该类的具体实现。

  • FsImage类

首先,看该类中定义的三个枚举类:

  1. /** 
  2.  * 存储映像的文件名称  
  3.  */  
  4. enum NameNodeFile {  
  5.   IMAGE     ("fsimage"),  
  6.   TIME      ("fstime"),  
  7.   EDITS     ("edits"),  
  8.   IMAGE_NEW ("fsimage.ckpt"),  
  9.   EDITS_NEW ("edits.new");  
  10.     
  11.   private String fileName = null;  
  12.   private NameNodeFile(String name) {this.fileName = name;}  
  13.   String getName() {return fileName;}  
  14. }  
  15.   
  16. /** 
  17.  * 检查点状态 
  18.  */  
  19. enum CheckpointStates{START, ROLLED_EDITS, UPLOAD_START, UPLOAD_DONE; }  
  20.   
  21. /** 
  22.  * 实现了StorageDirType接口,并指定为namenode存储 
  23.  * 一个存储目录的类型应该是仅仅存储fsimage的IMAGE类型,或者是存储edits的EDITS类型,或者是存储fsimage与edits的IMAGE_AND_EDITS类型  
  24.  */  
  25. static enum NameNodeDirType implements StorageDirType {  
  26.   UNDEFINED,  
  27.   IMAGE,  
  28.   EDITS,  
  29.   IMAGE_AND_EDITS;  
  30.     
  31.   public StorageDirType getStorageDirType() {  
  32.     return this;  
  33.   }  
  34.     
  35.   public boolean isOfType(StorageDirType type) {  
  36.     if ((this == IMAGE_AND_EDITS) && (type == IMAGE || type == EDITS))  
  37.       return true;  
  38.     return this == type;  
  39.   }  
  40. }  

再看一个内部类DatanodeImage,该类用于将Datanode的持久化信息存储到FsImage映像中,实现如下所示:

  1. static class DatanodeImage implements Writable { // 实现了Writable接口,因此是可序列化的   
  2.   DatanodeDescriptor node = new DatanodeDescriptor();  
  3.   /** 
  4.    * 序列化Datanode的信息,存储到fsImage中 
  5.    */  
  6.   public void write(DataOutput out) throws IOException {  
  7.     new DatanodeID(node).write(out);  
  8.     out.writeLong(node.getCapacity());  
  9.     out.writeLong(node.getRemaining());  
  10.     out.writeLong(node.getLastUpdate());  
  11.     out.writeInt(node.getXceiverCount());  
  12.   }  
  13.   
  14.   /** 
  15.    * 从fsImage中读取Datanode的信息,即反序列化 
  16.    */  
  17.   public void readFields(DataInput in) throws IOException {  
  18.     DatanodeID id = new DatanodeID();  
  19.     id.readFields(in);  
  20.     long capacity = in.readLong();  
  21.     long remaining = in.readLong();  
  22.     long lastUpdate = in.readLong();  
  23.     int xceiverCount = in.readInt();  
  24.   
  25.     // 根据读取到的Datanode信息,更新Datanode结点   
  26.     node.updateRegInfo(id);  
  27.     node.setStorageID(id.getStorageID());  
  28.     node.setCapacity(capacity);  
  29.     node.setRemaining(remaining);  
  30.     node.setLastUpdate(lastUpdate);  
  31.     node.setXceiverCount(xceiverCount);  
  32.   }  
  33. }  

对FSImage类源代码的阅读,我们从构造一个FSImage实例的方法来看,如下所示:

  1. FSImage() {  
  2.   super(NodeType.NAME_NODE); // 为Namenode创建一个新的存储   
  3.   this.editLog = new FSEditLog(this); // 构造FSEditLog实例   
  4. }  
  5.   
  6. FSImage(Collection<File> fsDirs, Collection<File> fsEditsDirs) throws IOException {  
  7.   this();  
  8.   setStorageDirectories(fsDirs, fsEditsDirs); // 将文件系统目录集合与EditLog日志文件目录集合,加入到创建的存储中   
  9. }  
  10.   
  11. public FSImage(StorageInfo storageInfo) {  
  12.   super(NodeType.NAME_NODE, storageInfo); // 将StorageInfo指定为Namenode的存储   
  13. }  
  14.   
  15. /** 
  16.  * 表示一个映像(image and edit file) 
  17.  */  
  18. public FSImage(File imageDir) throws IOException {  
  19.   this();  
  20.   ArrayList<File> dirs = new ArrayList<File>(1);  
  21.   ArrayList<File> editsDirs = new ArrayList<File>(1);  
  22.   dirs.add(imageDir);  
  23.   editsDirs.add(imageDir);  
  24.   setStorageDirectories(dirs, editsDirs);  
  25. }  

可见,FSImage实例对应着一个特定的为Namenode而创建的存储实例,该存储实例的内容,实际上维护着一个目录列表,在构造FSImage实例的过程中,全部加载并初始化。

接着,介绍FSImage类的重要方法。

1、加载FsImage映像文件

对应的实现方法为loadFSImage,该类实现了两个重载的加载映像文件的方法。

首先看第一个带参数的loadFSImage方法,从一个文件中加载映像到文件系统中,实现如下所示:

  1. boolean loadFSImage(File curFile) throws IOException {  
  2.   assert this.getLayoutVersion() < 0 : "Negative layout version is expected.";  
  3.   assert curFile != null : "curFile is null";  
  4.   
  5.   FSNamesystem fsNamesys = FSNamesystem.getFSNamesystem(); // 获取到FSNamesystem实例   
  6.   FSDirectory fsDir = fsNamesys.dir; // 获取到FSNamesystem实例的dir引用   
  7.   
  8.   boolean needToSave = true;  
  9.   DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(curFile))); // 打开映像文件的输入流   
  10.   try {  
  11.     int imgVersion = in.readInt(); // 读取映像文件版本号(第一次出现为-1)   
  12.     this.namespaceID = in.readInt(); // 读取文件系统命名空间ID(第一次出现为-2)   
  13.     long numFiles; // 映像文件数变量   
  14.     if (imgVersion <= -16) {  
  15.       numFiles = in.readLong(); // 读取文件数量   
  16.     } else {  
  17.       numFiles = in.readInt();  
  18.     }  
  19.     this.layoutVersion = imgVersion;  
  20.     if (imgVersion <= -12) {  
  21.       long genstamp = in.readLong(); // 读取最后设置的时间戳   
  22.       fsNamesys.setGenerationStamp(genstamp); // 同步到FSNamesystem实例上   
  23.     }  
  24.     needToSave = (imgVersion != FSConstants.LAYOUT_VERSION);  
  25.     short replication = FSNamesystem.getFSNamesystem().getDefaultReplication(); // 获取默认副本数   
  26.     LOG.info("Number of files = " + numFiles);  
  27.   
  28.     String path;  
  29.     String parentPath = "";  
  30.     INodeDirectory parentINode = fsDir.rootDir; // 获取到根目录   
  31.     for (long i = 0; i < numFiles; i++) {  
  32.       long modificationTime = 0;  
  33.       long atime = 0;  
  34.       long blockSize = 0;  
  35.       path = readString(in); // 读取文件   
  36.       replication = in.readShort(); // 读取副本数   
  37.       replication = FSEditLog.adjustReplication(replication); // 必要时调整副本数   
  38.       modificationTime = in.readLong(); // 读取最后修改时间   
  39.       if (imgVersion <= -17) {  
  40.         atime = in.readLong(); // 读取访问时间   
  41.       }  
  42.       if (imgVersion <= -8) {  
  43.         blockSize = in.readLong(); // 读取块大小   
  44.       }  
  45.       int numBlocks = in.readInt(); // 读取块列表大小   
  46.       Block blocks[] = null;  
  47.   
  48.       if ((-9 <= imgVersion && numBlocks > 0) || (imgVersion < -9 && numBlocks >= 0)) {  
  49.         blocks = new Block[numBlocks];  
  50.         for (int j = 0; j < numBlocks; j++) {  
  51.           blocks[j] = new Block();  
  52.           if (-14 < imgVersion) {  
  53.             blocks[j].set(in.readLong(), in.readLong(), Block.GRANDFATHER_GENERATION_STAMP);  
  54.           } else {  
  55.             blocks[j].readFields(in); // 读取块   
  56.           }  
  57.         }  
  58.       }  
  59.   
  60.       if (-8 <= imgVersion && blockSize == 0) {  
  61.         if (numBlocks > 1) {  
  62.           blockSize = blocks[0].getNumBytes();  
  63.         } else {  
  64.           long first = ((numBlocks == 1) ? blocks[0].getNumBytes(): 0);  
  65.           blockSize = Math.max(fsNamesys.getDefaultBlockSize(), first);  
  66.         }  
  67.       }  
  68.         
  69.       long nsQuota = -1L;  
  70.       if (imgVersion <= -16 && blocks == null) {  
  71.         nsQuota = in.readLong(); // 如果是目录,读取namespace配额信息   
  72.       }  
  73.       long dsQuota = -1L;  
  74.       if (imgVersion <= -18 && blocks == null) {  
  75.         dsQuota = in.readLong(); // 读取磁盘配额信息   
  76.       }  
  77.         
  78.       PermissionStatus permissions = fsNamesys.getUpgradePermission();  
  79.       if (imgVersion <= -11) {  
  80.         permissions = PermissionStatus.read(in); // 读取权限   
  81.       }  
  82.       if (path.length() == 0) { // 如果是根目录,设置其属性值   
  83.         if (nsQuota != -1 || dsQuota != -1) {  
  84.           fsDir.rootDir.setQuota(nsQuota, dsQuota);   
  85.         }  
  86.         fsDir.rootDir.setModificationTime(modificationTime);  
  87.         fsDir.rootDir.setPermissionStatus(permissions);  
  88.         continue;  
  89.       }  
  90.       // 检查新的inode是否属于相同的父目录   
  91.       if(!isParent(path, parentPath)) {  
  92.         parentINode = null;  
  93.         parentPath = getParent(path);  
  94.       }  
  95.       // 添加一个新的inode   
  96.       parentINode = fsDir.addToParent(path, parentINode, permissions, blocks, replication, modificationTime, atime, nsQuota, dsQuota, blockSize);  
  97.     }  
  98.     this.loadDatanodes(imgVersion, in); // 加载Datanode信息   
  99.     this.loadFilesUnderConstruction(imgVersion, in, fsNamesys); // 加载待创建的文件   
  100.       
  101.   } finally {  
  102.     in.close();  
  103.   }      
  104.   return needToSave;  
  105. }  

再看第二个不带参数的loadFSImage方法,它从一个目录中选择最新的映像文件并加载到内存中,同时与这个目录中的EditLog日志文件合并如下所示:

  1. boolean loadFSImage() throws IOException {  
  2.   // 首先检查FSImage对应的存储中的目录列表,找到最新的   
  3.   long latestNameCheckpointTime = Long.MIN_VALUE, latestEditsCheckpointTime = Long.MIN_VALUE;  
  4.   StorageDirectory latestNameSD = null, latestEditsSD = null;  
  5.   boolean needToSave = false, isUpgradeFinalized = true;  
  6.   Collection<String> imageDirs = new ArrayList<String>();  
  7.   Collection<String> editsDirs = new ArrayList<String>();  
  8.   for (Iterator<StorageDirectory> it = dirIterator(); it.hasNext();) {  
  9.     StorageDirectory sd = it.next();  
  10.     if (!sd.getVersionFile().exists()) {  
  11.       needToSave |= true;  
  12.       continue// 如果某个sd目录的版本文件不存在,说明该目录还没有格式化   
  13.     }  
  14.     boolean imageExists = false, editsExists = false;  
  15.     if (sd.getStorageDirType().isOfType(NameNodeDirType.IMAGE)) { // 判断指定的sd的类型是否是Namenode的IMAGE类型   
  16.       imageExists = getImageFile(sd, NameNodeFile.IMAGE).exists();  
  17.       imageDirs.add(sd.getRoot().getCanonicalPath());  
  18.     }  
  19.     if (sd.getStorageDirType().isOfType(NameNodeDirType.EDITS)) { // 判断指定的sd的类型是否是Namenode的EDITS类型   
  20.       editsExists = getImageFile(sd, NameNodeFile.EDITS).exists();  
  21.       editsDirs.add(sd.getRoot().getCanonicalPath());  
  22.     }  
  23.       
  24.     checkpointTime = readCheckpointTime(sd); // 获取sd检查点时间   
  25.     if ((checkpointTime != Long.MIN_VALUE) && ((checkpointTime != latestNameCheckpointTime) || (checkpointTime != latestEditsCheckpointTime))) {  
  26.       needToSave |= true// 根据sd的检查点时间,如果多个不同的时间则强制保存一个新的映像文件   
  27.     }  
  28.     if (sd.getStorageDirType().isOfType(NameNodeDirType.IMAGE) && (latestNameCheckpointTime < checkpointTime) && imageExists) {  
  29.       latestNameCheckpointTime = checkpointTime; // 得到最新检查点时间   
  30.       latestNameSD = sd; // 找到最新映像文件目录   
  31.     }  
  32.     if (sd.getStorageDirType().isOfType(NameNodeDirType.EDITS) && (latestEditsCheckpointTime < checkpointTime) && editsExists) {  
  33.       latestEditsCheckpointTime = checkpointTime;  
  34.       latestEditsSD = sd; // 找到最新EditLog日志文件目录   
  35.     }  
  36.     if (checkpointTime <= 0L)  
  37.       needToSave |= true;  
  38.     isUpgradeFinalized = isUpgradeFinalized && !sd.getPreviousDir().exists(); // 设置升级完成标志   
  39.   }  
  40.   
  41.   // 至少应该存在一个FSImage映像文件目录和一个EditLog日志文件目录   
  42.   if (latestNameSD == null)  
  43.     throw new IOException("Image file is not found in " + imageDirs);  
  44.   if (latestEditsSD == null)  
  45.     throw new IOException("Edits file is not found in " + editsDirs);  
  46.   
  47.   // 保证从同一个检查点加载映像文件与日志文件   
  48.   if (latestNameCheckpointTime != latestEditsCheckpointTime)  
  49.     throw new IOException("Inconsitent storage detected, " + "name and edits storage do not match");  
  50.     
  51.   // 从上一个中断的检查点执行恢复操作   
  52.   needToSave |= recoverInterruptedCheckpoint(latestNameSD, latestEditsSD);  
  53.   
  54.   long startTime = FSNamesystem.now(); // 开始加载FSImage文件时间   
  55.   long imageSize = getImageFile(latestNameSD, NameNodeFile.IMAGE).length(); // 获取映像文件大小   
  56.   
  57.   latestNameSD.read(); // 读取版本文件VERSION   
  58.   needToSave |= loadFSImage(getImageFile(latestNameSD, NameNodeFile.IMAGE)); // 调用重载的loadFSImage执行加载   
  59.   LOG.info("Image file of size " + imageSize + " loaded in " + (FSNamesystem.now() - startTime)/1000 + " seconds.");      
  60.   needToSave |= (loadFSEdits(latestEditsSD) > 0); // 加载最新的EditLog日志文件       
  61.   return needToSave;  
  62. }  

2、从一个中断的检查点恢复加载映像文件

该方法recoverInterruptedCheckpoint的实现如下所示:

  1. boolean recoverInterruptedCheckpoint(StorageDirectory nameSD, StorageDirectory editsSD) throws IOException {  
  2.   boolean needToSave = false;  
  3.   File curFile = getImageFile(nameSD, NameNodeFile.IMAGE); // 获取到fsimage文件   
  4.   File ckptFile = getImageFile(nameSD, NameNodeFile.IMAGE_NEW); // 获取到fsimage.ckpt文件   
  5.   
  6.   if (ckptFile.exists()) { // 如果正在一个检查点过程当中,fsimage.ckpt文件存在   
  7.     needToSave = true;  
  8.     if (getImageFile(editsSD, NameNodeFile.EDITS_NEW).exists()) { // 获取到edits.new文件,如果存在   
  9.       // 检查点进程执行时会上载一个新的合并的映像文件,但是在Namenode服务器挂机之前不能保证该映像文件完成上载,故需要保证它是被删除掉的   
  10.       if (!ckptFile.delete()) {  
  11.         throw new IOException("Unable to delete " + ckptFile);  
  12.       }  
  13.     } else { // 正常情况   
  14.       // 当Namenode服务器关闭时检查点进程仍在执行,fsimage.ckpt被创建,edits.new文件被重命名为edits   
  15.       // 为了完成该次检查点执行,将fsimage.new重命名为fsimage,而fstime不需要更新   
  16.       if (!ckptFile.renameTo(curFile)) {  
  17.         if (!curFile.delete())  
  18.           LOG.warn("Unable to delete dir " + curFile + " before rename");  
  19.         if (!ckptFile.renameTo(curFile)) {  
  20.           throw new IOException("Unable to rename " + ckptFile + " to " + curFile);  
  21.         }  
  22.       }  
  23.     }  
  24.   }  
  25.   return needToSave;  
  26. }  

通过上面可以看出,检查点进程所执行的操作是对fsimage映像文件进行检查,从而生成一个fsimage.ckpt文件。而很可能会(只要fsimage.ckpt文件存在)从一个fsimage.ckpt文件来加载fsimage映像文件,在Namenode停止的时候,将fsimage.ckpt重命名为fsimage完成此次检查点进程的执行,并把edits.new重命名为edits。

3、切换映像

实现方法rollFSImage,能够将fsimage.ckpt重命名为fsImage,将edits.new重命名为edits,并打开一个空的edits文件。实现如下所示:

  1. void rollFSImage() throws IOException {  
  2.   if (ckptState != CheckpointStates.UPLOAD_DONE) { // 如果检查点fsimage.ckpt文件没有上载完成,不能执行回滚操作   
  3.     throw new IOException("Cannot roll fsImage before rolling edits log.");  
  4.   }  
  5.   // 验证edits.new与fsimage.ckpt文件存在于所有的检查点目录中   
  6.   if (!editLog.existsNew()) {  
  7.     throw new IOException("New Edits file does not exist");  
  8.   }  
  9.   for (Iterator<StorageDirectory> it = dirIterator(NameNodeDirType.IMAGE); it.hasNext();) { // 迭代   
  10.     StorageDirectory sd = it.next();  
  11.     File ckpt = getImageFile(sd, NameNodeFile.IMAGE_NEW); // 获取fsimage.ckpt文件   
  12.     if (!ckpt.exists()) {  
  13.       throw new IOException("Checkpoint file " + ckpt + " does not exist");  
  14.     }  
  15.   }  
  16.   editLog.purgeEditLog(); // 重命名edits.new为edits   
  17.   
  18.   for (Iterator<StorageDirectory> it = (NameNodeDirType.IMAGE); it.hasNext();) {  
  19.     StorageDirectory sd = it.next();  
  20.     File ckpt = getImageFile(sd, NameNodeFile.IMAGE_NEW); // 获取到fsimage.ckpt文件   
  21.     File curFile = getImageFile(sd, NameNodeFile.IMAGE); // 获取到fsimage文件   
  22.     if (!ckpt.renameTo(curFile)) { // 将fsimage.ckpt重命名为fsimage   
  23.       curFile.delete(); // 如果fsimage已经存在则删除它   
  24.       if (!ckpt.renameTo(curFile)) { // 再次尝试将fsimage.ckpt重命名为fsimage   
  25.         if (sd.getStorageDirType().isOfType(NameNodeDirType.EDITS))  
  26.           editLog.processIOError(sd);  
  27.         removedStorageDirs.add(sd); // 显然,sd存储目录中无法将fsimage.ckpt重命名为fsimage,该sd已经失效,需要被删除掉   
  28.         it.remove();  
  29.       }  
  30.     }  
  31.   }  
  32.   
  33.   // 在fsimage与edits对应的所有的目录中,更新fstime文件,并写VERSION文件   
  34.   
  35.   this.layoutVersion = FSConstants.LAYOUT_VERSION;  
  36.   this.checkpointTime = FSNamesystem.now();  
  37.   for (Iterator<StorageDirectory> it = dirIterator(); it.hasNext();) { // 迭代   
  38.     StorageDirectory sd = it.next();  
  39.     if (!sd.getStorageDirType().isOfType(NameNodeDirType.EDITS)) {  
  40.       File editsFile = getImageFile(sd, NameNodeFile.EDITS);  
  41.       editsFile.delete(); // 删除旧的edits文件   
  42.     }  
  43.     if (!sd.getStorageDirType().isOfType(NameNodeDirType.IMAGE)) {  
  44.       File imageFile = getImageFile(sd, NameNodeFile.IMAGE);  
  45.       imageFile.delete(); // 删除旧的fsimage文件   
  46.     }  
  47.     try {  
  48.       sd.write(); // 向sd目录中写版本文件VERSION   
  49.     } catch (IOException e) {  
  50.       LOG.error("Cannot write file " + sd.getRoot(), e);  
  51.       if (sd.getStorageDirType().isOfType(NameNodeDirType.EDITS))  
  52.         editLog.processIOError(sd);  
  53.       removedStorageDirs.add(sd);  
  54.       it.remove(); // 删除sd   
  55.     }  
  56.   }  
  57.   ckptState = FSImage.CheckpointStates.START; // 修改检查点状态为开始,准备切换   
  58. }  

4、保存映像文件

实现方法为saveFSImage,存在两个重载的方法。

第一个是带参数的方法,将fsimage.new或fsimage文件的内容进行保存。如下所示:

  1. void saveFSImage(File newFile) throws IOException {  
  2.   FSNamesystem fsNamesys = FSNamesystem.getFSNamesystem();  
  3.   FSDirectory fsDir = fsNamesys.dir;  
  4.   long startTime = FSNamesystem.now();  
  5.   
  6.   DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(newFile))); // 创建指定文件的输出流,准备写入   
  7.   try {  
  8.     out.writeInt(FSConstants.LAYOUT_VERSION); // 写入版本号   
  9.     out.writeInt(namespaceID); // 写入namespace的ID   
  10.     out.writeLong(fsDir.rootDir.numItemsInTree()); // 写入fsDir目录的根目录树中INode的数量   
  11.     out.writeLong(fsNamesys.getGenerationStamp()); // 写入时间戳   
  12.     byte[] byteStore = new byte[4*FSConstants.MAX_PATH_LENGTH];   
  13.     ByteBuffer strbuf = ByteBuffer.wrap(byteStore);  
  14.     saveINode2Image(strbuf, fsDir.rootDir, out); // 将fsDir.rootDir的属性都写入到指定文件中   
  15.     saveImage(strbuf, 0, fsDir.rootDir, out); // 将fsDir.rootDir的目录树属性写入到指定文件中   
  16.     fsNamesys.saveFilesUnderConstruction(out); // 将INodeUnderConstruction写入到指定文件中   
  17.     strbuf = null;  
  18.   } finally {  
  19.     out.close();  
  20.   }  
  21.   
  22.   LOG.info("Image file of size " + newFile.length() + " saved in " + (FSNamesystem.now() - startTime)/1000 + " seconds.");  
  23. }  

上面调用saveImage方法,实际上方法循环调用了saveINode2Image来写入fsDir.rootDir目录树的属性信息的,可以参考这两个方法的实现。

上面方法是将一些必要的信息都序列化到了指定的映像文件中,作为Namenode掌握当前HDFS集群中重要信息的内存映像。

第二个是不带参数的该方法,保存fsimage.ckpt文件的内容,并创建一个新的edits文件。实现如下所示:

  1. public void saveFSImage() throws IOException {  
  2.   editLog.createNewIfMissing(); // 如果edits.new不存在,则创建一个新的   
  3.   for (Iterator<StorageDirectory> it = dirIterator(); it.hasNext();) { // 迭代   
  4.     StorageDirectory sd = it.next();  
  5.     NameNodeDirType dirType = (NameNodeDirType)sd.getStorageDirType();  
  6.     if (dirType.isOfType(NameNodeDirType.IMAGE))  
  7.       saveFSImage(getImageFile(sd, NameNodeFile.IMAGE_NEW)); // 调用,保存fsimage.ckpt文件的内容   
  8.     if (dirType.isOfType(NameNodeDirType.EDITS)) {      
  9.       editLog.createEditLogFile(getImageFile(sd, NameNodeFile.EDITS));  
  10.       File editsNew = getImageFile(sd, NameNodeFile.EDITS_NEW);  
  11.       if (editsNew.exists())   
  12.         editLog.createEditLogFile(editsNew); // 创建一个edits.new文件   
  13.     }  
  14.   }  
  15.   ckptState = CheckpointStates.UPLOAD_DONE; // 设置检查点状态为上载完成   
  16.   rollFSImage(); // 然后执行映像的切换操作:将fsimage.ckpt改成fsimage,将edits.new改成edits   
  17. }  

5、 从检查点目录加载fsimage映像文件

实现的方法为doImportCheckpoint,如下所示:

  1. void doImportCheckpoint() throws IOException {  
  2.   FSImage ckptImage = new FSImage(); // 创建一个FSImage实例   
  3.   FSNamesystem fsNamesys = FSNamesystem.getFSNamesystem();  
  4.   FSImage realImage = fsNamesys.getFSImage(); // 当前FSNamesystem中的当前fsimage映像   
  5.   assert realImage == this;  
  6.   fsNamesys.dir.fsImage = ckptImage; // 切换   
  7.   
  8.   try {  
  9.     ckptImage.recoverTransitionRead(checkpointDirs, checkpointEditsDirs,StartupOption.REGULAR); // 从检查点目录加载fsimage映像   
  10.   } finally {  
  11.     ckptImage.close();  
  12.   }  
  13.   realImage.setStorageInfo(ckptImage); // 更新layoutVersion、namespaceID、cTime属性值   
  14.   fsNamesys.dir.fsImage = realImage; // 更新fsNamesys.dir.fsImage   
  15.   saveFSImage(); // 保存:将fsimage.ckpt改成fsimage,将edits.new改成edits   
  16. }  

6、初始化分布式升级

主要通过获取到UpgradeManagerNamenode 升级管理器,判断是否进行了升级之前的初始化工作,只有做好初始化工作,才开始对fsimage映像文件进行初始化。

实现方法initializeDistributedUpgrade如下所示:

  1. private void initializeDistributedUpgrade() throws IOException {  
  2.   UpgradeManagerNamenode um = FSNamesystem.getFSNamesystem().upgradeManager;  
  3.   if(! um.initializeUpgrade())  
  4.     return;  
  5.   FSNamesystem.getFSNamesystem().getFSImage().writeAll(); // 将与fsimage相关的数据都写入到存储中   
  6.   NameNode.LOG.info("/n   Distributed upgrade for NameNode version "   
  7.       + um.getUpgradeVersion() + " to current LV "   
  8.       + FSConstants.LAYOUT_VERSION + " is initialized.");  
  9. }  

7、执行升级

实现方法为doUpgrade,如下所示:

  1. private void doUpgrade() throws IOException {  
  2.   if(getDistributedUpgradeState()) { // 通过升级管理器,获取当前升级状态   
  3.     // 只有分布式升级才执行,并且不对版本升级   
  4.     this.loadFSImage(); // 加载fsimage映像文件   
  5.     initializeDistributedUpgrade(); // 初始化升级准备工作   
  6.     return;  
  7.   }  
  8.   
  9.   // 如果当前不存在文件系统的任何信息,可以向下执行升级过程   
  10.   for (Iterator<StorageDirectory> it = dirIterator(); it.hasNext();) {  
  11.     StorageDirectory sd = it.next();  
  12.     if (sd.getPreviousDir().exists())  
  13.       throw new InconsistentFSStateException(sd.getRoot(),  
  14.                                              "previous fs state should not exist during upgrade. "  
  15.                                              + "Finalize or rollback first.");  
  16.   }  
  17.   
  18.   this.loadFSImage(); // 加载最新的映像文件   
  19.   
  20.   long oldCTime = this.getCTime();  
  21.   this.cTime = FSNamesystem.now();  // 设置当前时间   
  22.   int oldLV = this.getLayoutVersion();  
  23.   this.layoutVersion = FSConstants.LAYOUT_VERSION;  
  24.   this.checkpointTime = FSNamesystem.now();  
  25.   for (Iterator<StorageDirectory> it = dirIterator(); it.hasNext();) { // 迭代:对每个目录分别进行升级准备操作   
  26.     StorageDirectory sd = it.next();  
  27.     LOG.info("Upgrading image directory " + sd.getRoot()  
  28.              + "./n   old LV = " + oldLV  
  29.              + "; old CTime = " + oldCTime  
  30.              + "./n   new LV = " + this.getLayoutVersion()  
  31.              + "; new CTime = " + this.getCTime());  
  32.     File curDir = sd.getCurrentDir(); // 获取到current目录   
  33.     File prevDir = sd.getPreviousDir(); // 获取到previous目录   
  34.     File tmpDir = sd.getPreviousTmp(); // 获取到previous.tmp目录   
  35.     assert curDir.exists() : "Current directory must exist.";  
  36.     assert !prevDir.exists() : "prvious directory must not exist.";  
  37.     assert !tmpDir.exists() : "prvious.tmp directory must not exist.";  
  38.   
  39.     rename(curDir, tmpDir); // 将current目录重命名为previous.tmp   
  40.     if (!curDir.mkdir()) // 创建current目录   
  41.       throw new IOException("Cannot create directory " + curDir);  
  42.     saveFSImage(getImageFile(sd, NameNodeFile.IMAGE)); // 保存最新的映像   
  43.     editLog.createEditLogFile(getImageFile(sd, NameNodeFile.EDITS)); // 创建一个新的edits文件   
  44.     sd.write(); // 写版本文件   
  45.   
  46.     rename(tmpDir, prevDir); // 将previous.tmp重命名为previous   
  47.     isUpgradeFinalized = false// 等待升级进程确认   
  48.     LOG.info("Upgrade of " + sd.getRoot() + " is complete.");  
  49.   }  
  50.   initializeDistributedUpgrade(); // 初始化分布式升级的工作   
  51.   editLog.open(); // 打开edits日志文件   
  52. }  

8、执行升级后的后继清理

当执行升级的时候,可能需要执行目录的重命名,或者删除一些临时文件目录,对指定的StorageDirectory目录执行清理工作的方法为doFinalize,如下所示:

  1. private void doFinalize(StorageDirectory sd) throws IOException {  
  2.   File prevDir = sd.getPreviousDir();  
  3.   if (!prevDir.exists()) { // 该临时目录previous.tmp已经被清理了   
  4.     LOG.info("Directory " + prevDir + " does not exist.");  
  5.     LOG.info("Finalize upgrade for " + sd.getRoot()+ " is not required.");  
  6.     return;  
  7.   }  
  8.   LOG.info("Finalizing upgrade for storage directory " + sd.getRoot() + "." + (getLayoutVersion()==0 ? "" : "/n   cur LV = "   
  9.            + this.getLayoutVersion() + "; cur CTime = " + this.getCTime()));  
  10.   assert sd.getCurrentDir().exists() : "Current directory must exist.";  
  11.   final File tmpDir = sd.getFinalizedTmp(); // 获取全部finalized.tmp临时目录   
  12.   rename(prevDir, tmpDir); // 将previous.tmp重命名为finalized.tmp   
  13.   deleteDir(tmpDir); // 删除finalized.tmp   
  14.   isUpgradeFinalized = true// 升级之后的清理工作已经完成   
  15.   LOG.info("Finalize upgrade for " + sd.getRoot()+ " is complete.");  
  16. }  

上面的方法比较容易。如果想要执行批量清理,调用方法finalizeUpgrade可以实现,如下所示:

  1. void finalizeUpgrade() throws IOException {  
  2.   for (Iterator<StorageDirectory> it = dirIterator(); it.hasNext();) {  
  3.     doFinalize(it.next());  
  4.   }  
  5. }  

该方法对全部的目录执行清理工作。

9、回滚操作

执行回滚操作时有条件的,如果文件系统不存在一个先前的状态,是不能够执行回滚操作的,因为根本无法将当前的fsimag改变成上一个状态。另外,文件系统先前的一个状态的信息保存在一个或多个存储目录中,获取到这些先前的状态信息才能够执行回滚操作。而对于不存在文件系统状态信息的存储目录是不需要回滚的。

实现回滚操作的方法为doRollback,如下所示:

  1. private void doRollback() throws IOException {  
  2.   boolean canRollback = false;  
  3.   FSImage prevState = new FSImage(); // 获取先前文件系统的映像   
  4.   prevState.layoutVersion = FSConstants.LAYOUT_VERSION;  
  5.   for (Iterator<StorageDirectory> it = dirIterator(); it.hasNext();) {  
  6.     StorageDirectory sd = it.next();  
  7.     File prevDir = sd.getPreviousDir();  
  8.     if (!prevDir.exists()) {  // use current directory then   
  9.       LOG.info("Storage directory " + sd.getRoot() + " does not contain previous fs state.");  
  10.       sd.read(); // 读取版本VERSION文件,验证与其他目录的一致性   
  11.       continue;  
  12.     }  
  13.     StorageDirectory sdPrev = prevState.new StorageDirectory(sd.getRoot());  
  14.     sdPrev.read(sdPrev.getPreviousVersionFile());  // 读取版本VERSION文件,验证与先前目录的一致性   
  15.     canRollback = true// 可以执行回滚操作标志   
  16.   }  
  17.   if (!canRollback)  
  18.     throw new IOException("Cannot rollback. " + "None of the storage directories contain previous fs state.");  
  19.   
  20.   // 所有目录的一致性检查通过,执行回滚,使得每个目录都包含先前的状态   
  21.   for (Iterator<StorageDirectory> it = dirIterator(); it.hasNext();) {  
  22.     StorageDirectory sd = it.next();  
  23.     File prevDir = sd.getPreviousDir(); // 获取到先前的previous目录   
  24.     if (!prevDir.exists())  
  25.       continue;  
  26.   
  27.     LOG.info("Rolling back storage directory " + sd.getRoot() + "./n   new LV = " + prevState.getLayoutVersion() + "; new CTime = " + prevState.getCTime());  
  28.     File tmpDir = sd.getRemovedTmp(); // 获取到removed.tmp目录   
  29.     assert !tmpDir.exists() : "removed.tmp directory must not exist.";  
  30.   
  31.     File curDir = sd.getCurrentDir(); // 获取到current目录   
  32.     assert curDir.exists() : "Current directory must exist.";  
  33.     rename(curDir, tmpDir); // 将current重命名为removed.tmp   
  34.     rename(prevDir, curDir); // 将previous重命名为current   
  35.     deleteDir(tmpDir); // 删除临时removed.tmp目录   
  36.     LOG.info("Rollback of " + sd.getRoot()+ " is complete.");  
  37.   }  
  38.   isUpgradeFinalized = true// 清理完成   
  39.   verifyDistributedUpgradeProgress(StartupOption.REGULAR); // 检查Namenode是否能够以REGULAR模式启动   
  40. }  

可见,回滚操作就是,在保证全部目录一致性检查通过的情况下,将当前存在的previous目录重命名为current目录,表示将previous目录状态覆盖到current目录上,然后删除被覆盖掉的current目录(通过先重命名current为removed.tmp,然后执行删除)。

10、格式化操作

对文件系统中的StorageDirectory存储目录进行格式化操作,存在两个重载的方法,分别介绍如下。

第一个带参数的方法,是对指定的存储目录进行格式化,如下所示:

  1. void format(StorageDirectory sd) throws IOException {  
  2.   sd.clearDirectory(); // 创建currrent目录,如果该目录存在,则会删除存在的current目录树   
  3.   sd.lock(); // 加锁,对应的文件为in_use.lock   
  4.   try {  
  5.     NameNodeDirType dirType = (NameNodeDirType)sd.getStorageDirType(); // 获取到存储目录类型   
  6.     if (dirType.isOfType(NameNodeDirType.IMAGE)) // 如果是fsimage目录   
  7.       saveFSImage(getImageFile(sd, NameNodeFile.IMAGE)); // 保存fsimage映像   
  8.     if (dirType.isOfType(NameNodeDirType.EDITS)) // 如果是edits日志文件目录   
  9.       editLog.createEditLogFile(getImageFile(sd, NameNodeFile.EDITS)); // 创建一个新的edits文件   
  10.     sd.write(); // 写版本文件VERSION   
  11.   } finally {  
  12.     sd.unlock(); // 释放锁,清除in_use.lock文件   
  13.   }  
  14.   LOG.info("Storage directory " + sd.getRoot() + " has been successfully formatted.");  
  15. }  

比较容易理解。

第二个不带参数的方法,是批量进行格式化,如下所示:

  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); // 对每一个sd执行格式化操作   
  9.   }  
  10. }  

11、加载Datanode信息

事实上,对于新版本的Hadoop已经不再使用Datanode的映像了,该方法是为了兼容旧版本的,如下所示:

  1. void loadDatanodes(int version, DataInputStream in) throws IOException {  
  2.   if (version > -3// 之前版本不存在Datanode信息的映像   
  3.     return;  
  4.   if (version <= -12) {  
  5.     return// 新版本不需要存储Datanode映像   
  6.   }  
  7.   int size = in.readInt();  
  8.   for(int i = 0; i < size; i++) {  
  9.     DatanodeImage nodeImage = new DatanodeImage(); // 通过DatanodeImage来加载Datanode的映像   
  10.     nodeImage.readFields(in);  
  11.   }  
  12. }  

 

简单做个总结:

FSImage类是与fsimage相关的,主要保存了文件系统的状态信息。其中,在加载该映像到内存中的时候,相关的日志文件是edits,edits总是保存当前对文件系统执行操作的最新记录,根据需要启动检查点进程来将edits事务日志记录作用于fsimage映像上,从而系统通过fsimage将发生的事务作用在文件系统实例所维护的存储目录上。

在对fsimage操作的过程中,相关的文件主要包括fsimage、fsimage.ckpt、edits、edits.new这四个,而且在适当的时候,启动检查点进程对内存中映像fsimage执行同步操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值