• HDFS源码分析之FSImage文件内容(一)总体格式


    FSImage文件是HDFS中名字节点NameNode上文件/目录元数据在特定某一时刻的持久化存储文件。它的作用不言而喻,在HA出现之前,NameNode因为各种原因宕机后,若要恢复或在其他机器上重启NameNode,重新组织元数据,就需要加载对应的FSImage文件、FSEditLog文件,并在内存中重做FSEditLog文件中的事务条目。本节,我们先来看下FSImage文件格式,及其内部数据是如何组织的。

            通过翻看HDFS中加载FSImage文件的代码,从FSNamesystem的loadFSImage()方法开始,我将HDFS集群上的一个FSImage文件放到本地Windows系统中的F盘下,并写了如下方法解析文件,并打印关键内容,如下:

    [java] view plain copy
     
    1. import java.io.IOException;  
    2. import java.io.File;  
    3. import java.util.List;  
    4. import org.junit.Test;  
    5. import java.io.ByteArrayInputStream;  
    6. import java.io.RandomAccessFile;  
    7. import org.apache.hadoop.hdfs.server.namenode.FsImageProto.FileSummary;  
    8. import org.apache.hadoop.hdfs.server.namenode.FsImageProto.FileSummary.Section;  
    9.   
    10. public class TestImageUtil {  
    11.   
    12.     @Test  
    13.     public void testImage() {  
    14.   
    15.         // 文件头字符串HDFSIMG1对应byte[]  
    16.         byte[] fileHead = "HDFSIMG1".getBytes();  
    17.   
    18.         RandomAccessFile raFile = null;  
    19.         try {  
    20.               
    21.             // 创建文件file,对应为f盘下FSImage文件fsimage_0000000000002311798  
    22.             File file = new File("f:/fsimage_0000000000002311798");  
    23.   
    24.             raFile = new RandomAccessFile(file, "r");  
    25.   
    26.             // 文件summary长度域所占大小为4  
    27.             final int FILE_LENGTH_FIELD_SIZE = 4;  
    28.             System.out.println("文件summary长度域大小:FILE_LENGTH_FIELD_SIZE=" + FILE_LENGTH_FIELD_SIZE);  
    29.               
    30.             // 获取FSImage文件长度  
    31.             long fileLength = raFile.length();  
    32.             System.out.println("获取FSImage文件长度:fileLength=" + fileLength);  
    33.   
    34.             // 创建文件头byte[]数组fileHeadTmp,用于存储文件头byte[]数组,大小为上述fileHead数组大小  
    35.             byte[] fileHeadTmp = new byte[fileHead.length];  
    36.               
    37.             // 读入文件头至byte[]数组fileHeadTmp  
    38.             System.out.println("文件从头开始读取" + fileHeadTmp.length + "个byte至byte[]数组fileHeadTmp");  
    39.             raFile.readFully(fileHeadTmp);  
    40.   
    41.             // 获取文件头长度  
    42.             System.out.println("获取文件头长度:fileHeadLength=" + fileHead.length);  
    43.   
    44.             // 将byte[]数组fileHeadTmp转换成字符串fileHeadString  
    45.             String fileHeadString = new String(fileHeadTmp);  
    46.             // 验证文件头字符串  
    47.             System.out.println("fileHeadString=" + fileHeadString);  
    48.   
    49.             // 文件file通过raFile.seek()方法定位到文件summary长度字段起始处,即文件大小减去文件summary长度域所占字节数4  
    50.             raFile.seek(fileLength - FILE_LENGTH_FIELD_SIZE);  
    51.             System.out.println("文件定位到文件summary长度开始处:" + (fileLength - FILE_LENGTH_FIELD_SIZE));  
    52.   
    53.             // 读入一个int,即文件长度summaryLength  
    54.             int summaryLength = raFile.readInt();  
    55.             System.out.println("获取文件summary部分长度:summaryLength=" + summaryLength);  
    56.   
    57.             // 文件file通过raFile.seek()方法定位到文件summary部分开始处,即文件大小减去文件长度所占字节数4,再减去文件内容总长度  
    58.             raFile.seek(fileLength - FILE_LENGTH_FIELD_SIZE - summaryLength);  
    59.             System.out.println("文件定位到文件summary部分开始处:" + (fileLength - FILE_LENGTH_FIELD_SIZE - summaryLength));  
    60.   
    61.             // 再从当前位置开始读入文件summary部分内容  
    62.             // 构造文件长度summaryLength大小的byte[]数组  
    63.             byte[] summaryBytes = new byte[summaryLength];  
    64.   
    65.             // 读取文件内容至数组summaryBytes  
    66.             raFile.readFully(summaryBytes);  
    67.             System.out.println("从当前位置开始读入文件summary部分内容至summaryBytes数组");  
    68.   
    69.             FileSummary summary = FileSummary  
    70.                     .parseDelimitedFrom(new ByteArrayInputStream(summaryBytes));  
    71.   
    72.             System.out.println("解析文件summary部分内容如下:");  
    73.             System.out.println("1、ondiskVersion=" + summary.getOndiskVersion());  
    74.             System.out.println("2、layoutVersion=" + summary.getLayoutVersion());  
    75.             System.out.println("3、codec=" + summary.getCodec());  
    76.   
    77.             System.out.println("4、section");   
    78.             List<Section> sectionsList = summary.getSectionsList();  
    79.             for (Section section : sectionsList) {  
    80.                 System.out.println(" ");  
    81.                 System.out.println("name=" + section.getName());  
    82.                 System.out.println("length=" + section.getLength());  
    83.                 System.out.println("offset=" + section.getOffset());  
    84.             }  
    85.         } catch (Exception e) {  
    86.             e.printStackTrace();  
    87.         } finally {  
    88.             if (raFile != null) {  
    89.                 try {  
    90.                     raFile.close();  
    91.                 } catch (IOException e) {  
    92.                     e.printStackTrace();  
    93.                 }  
    94.             }  
    95.         }  
    96.   
    97.     }  
    98.   
    99.     /** 
    100.      * Supported section name. The order of the enum determines the order of 
    101.      * loading. 
    102.      */  
    103.     public enum SectionName {  
    104.         NS_INFO("NS_INFO"), STRING_TABLE("STRING_TABLE"), EXTENDED_ACL(  
    105.                 "EXTENDED_ACL"), INODE("INODE"), INODE_REFERENCE(  
    106.                 "INODE_REFERENCE"), SNAPSHOT("SNAPSHOT"), INODE_DIR("INODE_DIR"), FILES_UNDERCONSTRUCTION(  
    107.                 "FILES_UNDERCONSTRUCTION"), SNAPSHOT_DIFF("SNAPSHOT_DIFF"), SECRET_MANAGER(  
    108.                 "SECRET_MANAGER"), CACHE_MANAGER("CACHE_MANAGER");  
    109.   
    110.         private static final SectionName[] values = SectionName.values();  
    111.   
    112.         public static SectionName fromString(String name) {  
    113.             for (SectionName n : values) {  
    114.                 if (n.name.equals(name))  
    115.                     return n;  
    116.             }  
    117.             return null;  
    118.         }  
    119.   
    120.         private final String name;  
    121.   
    122.         private SectionName(String name) {  
    123.             this.name = name;  
    124.         }  
    125.     }  
    126. }  

            关于代码解释,我们会在专门的FSImage文件加载源码分析相关文章中进行详细介绍,本文只关注FSImage文件的总体格式。

            执行上述方法,打印内容输出如下:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. 文件summary长度域大小:FILE_LENGTH_FIELD_SIZE=4  
    2. 获取FSImage文件长度:fileLength=1154156  
    3. 文件从头开始读取8个byte至byte[]数组fileHeadTmp  
    4. 获取文件头长度:fileHeadLength=8  
    5. fileHeadString=HDFSIMG1  
    6. 文件定位到文件summary长度开始处:1154152  
    7. 获取文件summary部分长度:summaryLength=231  
    8. 文件定位到文件summary部分开始处:1153921  
    9. 从当前位置开始读入文件summary部分内容至summaryBytes数组  
    10. 解析文件summary部分内容如下:  
    11. 1、ondiskVersion=1  
    12. 2、layoutVersion=-60  
    13. 3、codec=  
    14. 4、section  
    15.    
    16. name=NS_INFO  
    17. length=27  
    18. offset=8  
    19.    
    20. name=INODE  
    21. length=1093067  
    22. offset=35  
    23.    
    24. name=INODE_DIR  
    25. length=60225  
    26. offset=1093102  
    27.    
    28. name=FILES_UNDERCONSTRUCTION  
    29. length=345  
    30. offset=1153327  
    31.    
    32. name=SNAPSHOT  
    33. length=68  
    34. offset=1153672  
    35.    
    36. name=SNAPSHOT_DIFF  
    37. length=36  
    38. offset=1153740  
    39.    
    40. name=INODE_REFERENCE  
    41. length=0  
    42. offset=1153776  
    43.    
    44. name=SECRET_MANAGER  
    45. length=9  
    46. offset=1153776  
    47.    
    48. name=CACHE_MANAGER  
    49. length=7  
    50. offset=1153785  
    51.    
    52. name=STRING_TABLE  
    53. length=129  
    54. offset=1153792  

            不难看出,文件的总长度为1154156,这与我通过windows系统下右击-属性的方式查看结果是一致的,如下:


            (一)文件的起始位置(下标我们从0开始),0-7处为文件头信息,占8个byte的"HDFSIMG1";

            (二)然后是接下来是10个section区域,这部分在FSImage文件中所占起止位置为8-1153920,这些是根据下面的summary区域的分析得到的结论,section分别如下:

            1、8-34:占27个byte的section--NS_INFO,命名系统NameSystem信息section区域,具体内容后续文章再讲;

            2、35-1093101:占1093067个byte的section--INODE,HDFS中INODE节点section区域,具体内容后续文章再讲;

            3、1093102-1153326:占60225个byte的section--INODE_DIR,HDFS中INODE目录节点section区域,具体内容后续文章再讲;

            4、1153327-1153671:占345个byte的section--FILES_UNDERCONSTRUCTION,HDFS中FILES_UNDERCONSTRUCTION处于构建状态文件部分section区域,具体内容后续文章再讲;

            5、1153672-1153739:占68个byte的section--SNAPSHOT,HDFS中SNAPSHOT快照部分section区域,具体内容后续文章再讲;

            6、1153740-1153775:占36个byte的section--SNAPSHOT_DIFF,HDFS中SNAPSHOT_DIFF部分section区域,具体内容后续文章再讲;

            7、1153776-?:占0个byte的section--INODE_REFERENCE,HDFS中INODE_REFERENCE节点引用部分section区域,具体内容后续文章再讲,实际上本文件中没有这部分,为了体现FSImage文件的完整性,还是增加这部分的描述;

            8、1153776-1153784:占9个byte的section--SECRET_MANAGER,HDFS中SECRET_MANAGER部分section区域,具体内容后续文章再讲;

            9、1153785-1153791:占7个byte的section--CACHE_MANAGER,HDFS中CACHE_MANAGER部分section区域,具体内容后续文章再讲;

            10、1153792-1153920:占129个byte的section--STRING_TABLE,HDFS中STRING_TABLE部分section区域,具体内容后续文章再讲;

            (三)再接下来是文件summary区域,这部分在FSImage文件中所占起止位置为1153921-1154151,长度为231,它主要标识了上述各section区域的区域名name、在FSImage文件所占长度length及其起始位置offset,另外还有三个十分总要的变量,FSImage文件在磁盘上的版本号ondiskVersion、布局layout版本号layoutVersion及其解压/压缩器codec,前面两个会在load文件时与HDFS中NameNode进程内存中的版本号分别进行校验,防止错误版本的FSImage文件被加载,而codec则用于如何加载各个section区域,为空默认不做任何解压/压缩处理;

            (四)最后为文件summary部分所占长度区域,这部分在FSImage文件中所占起止位置为1154152-1154155,正好是文件的最后一部分内容。

            

            或许通过图的方式你会看的更直观,但是请原谅我拙劣的画图技巧:

            实际上,FSImage文件中各个区域包含的内容,采用的是Google的protobuf编码格式,而protobuf不单单是一种消息传输格式,你也可以把它理解为一种数据编码格式,所以各个区域数据格式,在HDFS内的fsimage.proto文件中也有所阐述,比如FileSummary:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. message FileSummary {  
    2.   // The version of the above EBNF grammars.  
    3.   required uint32 ondiskVersion = 1;  
    4.   // layoutVersion describes which features are available in the  
    5.   // FSImage.  
    6.   required uint32 layoutVersion = 2;  
    7.   optional string codec         = 3;  
    8.   // index for each section  
    9.   message Section {  
    10.     optional string name = 1;  
    11.     optional uint64 length = 2;  
    12.     optional uint64 offset = 3;  
    13.   }  
    14.   repeated Section sections = 4;  
    15. }  

            它就包含我们上面所描述的ondiskVersion、layoutVersion、codec、sections五部分,最后的sections是可以重复的,即repeated,而每个section又是一个message,包含name、length、offset三部分,正和我们上面解析的结果一致。

            又如StringTableSection:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. /** 
    2.  * This section maps string to id 
    3.  * NAME: STRING_TABLE 
    4.  */  
    5. message StringTableSection {  
    6.   message Entry {  
    7.     optional uint32 id = 1;  
    8.     optional string str = 2;  
    9.   }  
    10.   optional uint32 numEntry = 1;  
    11.   // repeated Entry  
    12. }  

            包含两部分,Entry数量:numEntry,和重复的Entry,每个Entry又是一个Message,包含id和str两部分。

            以上就是FSImage文件的主体信息,至于文件中的详细内容,特别是每个不同section区域中都有哪些内容,尤其是复杂的INodeSection等,我们后续再讲!

  • 相关阅读:
    关于flume配置加载
    ListMultimap 容器
    HotSpotOverview.pdf
    芝麻西瓜
    念念不忘必有回响
    phpstrom代码格式化
    小总结
    Redis支持的数据类型
    如何通过phpstorm查看某一行代码的变更记录
    mysql自动添加时间
  • 原文地址:https://www.cnblogs.com/jirimutu01/p/5556289.html
Copyright © 2020-2023  润新知