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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介:         FSImage文件是HDFS中名字节点NameNode上文件/目录元数据在特定某一时刻的持久化存储文件。它的作用不言而喻,在HA出现之前,NameNode因为各种原因宕机后,若要恢复或在其他机器上重启NameNode,重新组织元数据,就需要加载对应的FSImage文件、FSEditLog文件,并在内存中重做FSEditLog文件中的事务条目。

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

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

import java.io.IOException;
import java.io.File;
import java.util.List;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.RandomAccessFile;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto.FileSummary;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto.FileSummary.Section;

public class TestImageUtil {

	@Test
	public void testImage() {

	    // 文件头字符串HDFSIMG1对应byte[]
		byte[] fileHead = "HDFSIMG1".getBytes();

		RandomAccessFile raFile = null;
		try {
			
			// 创建文件file,对应为f盘下FSImage文件fsimage_0000000000002311798
			File file = new File("f:/fsimage_0000000000002311798");

			raFile = new RandomAccessFile(file, "r");

			// 文件summary长度域所占大小为4
			final int FILE_LENGTH_FIELD_SIZE = 4;
			System.out.println("文件summary长度域大小:FILE_LENGTH_FIELD_SIZE=" + FILE_LENGTH_FIELD_SIZE);
			
			// 获取FSImage文件长度
			long fileLength = raFile.length();
			System.out.println("获取FSImage文件长度:fileLength=" + fileLength);

			// 创建文件头byte[]数组fileHeadTmp,用于存储文件头byte[]数组,大小为上述fileHead数组大小
			byte[] fileHeadTmp = new byte[fileHead.length];
			
			// 读入文件头至byte[]数组fileHeadTmp
			System.out.println("文件从头开始读取" + fileHeadTmp.length + "个byte至byte[]数组fileHeadTmp");
			raFile.readFully(fileHeadTmp);

			// 获取文件头长度
			System.out.println("获取文件头长度:fileHeadLength=" + fileHead.length);

			// 将byte[]数组fileHeadTmp转换成字符串fileHeadString
			String fileHeadString = new String(fileHeadTmp);
			// 验证文件头字符串
			System.out.println("fileHeadString=" + fileHeadString);

			// 文件file通过raFile.seek()方法定位到文件summary长度字段起始处,即文件大小减去文件summary长度域所占字节数4
			raFile.seek(fileLength - FILE_LENGTH_FIELD_SIZE);
			System.out.println("文件定位到文件summary长度开始处:" + (fileLength - FILE_LENGTH_FIELD_SIZE));

			// 读入一个int,即文件长度summaryLength
			int summaryLength = raFile.readInt();
			System.out.println("获取文件summary部分长度:summaryLength=" + summaryLength);

			// 文件file通过raFile.seek()方法定位到文件summary部分开始处,即文件大小减去文件长度所占字节数4,再减去文件内容总长度
			raFile.seek(fileLength - FILE_LENGTH_FIELD_SIZE - summaryLength);
			System.out.println("文件定位到文件summary部分开始处:" + (fileLength - FILE_LENGTH_FIELD_SIZE - summaryLength));

			// 再从当前位置开始读入文件summary部分内容
			// 构造文件长度summaryLength大小的byte[]数组
			byte[] summaryBytes = new byte[summaryLength];

			// 读取文件内容至数组summaryBytes
			raFile.readFully(summaryBytes);
			System.out.println("从当前位置开始读入文件summary部分内容至summaryBytes数组");

			FileSummary summary = FileSummary
					.parseDelimitedFrom(new ByteArrayInputStream(summaryBytes));

			System.out.println("解析文件summary部分内容如下:");
			System.out.println("1、ondiskVersion=" + summary.getOndiskVersion());
			System.out.println("2、layoutVersion=" + summary.getLayoutVersion());
			System.out.println("3、codec=" + summary.getCodec());

			System.out.println("4、section"); 
			List<Section> sectionsList = summary.getSectionsList();
			for (Section section : sectionsList) {
				System.out.println(" ");
				System.out.println("name=" + section.getName());
				System.out.println("length=" + section.getLength());
				System.out.println("offset=" + section.getOffset());
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (raFile != null) {
				try {
					raFile.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

	}

	/**
	 * Supported section name. The order of the enum determines the order of
	 * loading.
	 */
	public enum SectionName {
		NS_INFO("NS_INFO"), STRING_TABLE("STRING_TABLE"), EXTENDED_ACL(
				"EXTENDED_ACL"), INODE("INODE"), INODE_REFERENCE(
				"INODE_REFERENCE"), SNAPSHOT("SNAPSHOT"), INODE_DIR("INODE_DIR"), FILES_UNDERCONSTRUCTION(
				"FILES_UNDERCONSTRUCTION"), SNAPSHOT_DIFF("SNAPSHOT_DIFF"), SECRET_MANAGER(
				"SECRET_MANAGER"), CACHE_MANAGER("CACHE_MANAGER");

		private static final SectionName[] values = SectionName.values();

		public static SectionName fromString(String name) {
			for (SectionName n : values) {
				if (n.name.equals(name))
					return n;
			}
			return null;
		}

		private final String name;

		private SectionName(String name) {
			this.name = name;
		}
	}
}

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

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

文件summary长度域大小:FILE_LENGTH_FIELD_SIZE=4
获取FSImage文件长度:fileLength=1154156
文件从头开始读取8个byte至byte[]数组fileHeadTmp
获取文件头长度:fileHeadLength=8
fileHeadString=HDFSIMG1
文件定位到文件summary长度开始处:1154152
获取文件summary部分长度:summaryLength=231
文件定位到文件summary部分开始处:1153921
从当前位置开始读入文件summary部分内容至summaryBytes数组
解析文件summary部分内容如下:
1、ondiskVersion=1
2、layoutVersion=-60
3、codec=
4、section
 
name=NS_INFO
length=27
offset=8
 
name=INODE
length=1093067
offset=35
 
name=INODE_DIR
length=60225
offset=1093102
 
name=FILES_UNDERCONSTRUCTION
length=345
offset=1153327
 
name=SNAPSHOT
length=68
offset=1153672
 
name=SNAPSHOT_DIFF
length=36
offset=1153740
 
name=INODE_REFERENCE
length=0
offset=1153776
 
name=SECRET_MANAGER
length=9
offset=1153776
 
name=CACHE_MANAGER
length=7
offset=1153785
 
name=STRING_TABLE
length=129
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:

message FileSummary {
  // The version of the above EBNF grammars.
  required uint32 ondiskVersion = 1;
  // layoutVersion describes which features are available in the
  // FSImage.
  required uint32 layoutVersion = 2;
  optional string codec         = 3;
  // index for each section
  message Section {
    optional string name = 1;
    optional uint64 length = 2;
    optional uint64 offset = 3;
  }
  repeated Section sections = 4;
}
        它就包含我们上面所描述的ondiskVersion、layoutVersion、codec、sections五部分,最后的sections是可以重复的,即repeated,而每个section又是一个message,包含name、length、offset三部分,正和我们上面解析的结果一致。

        又如StringTableSection:

/**
 * This section maps string to id
 * NAME: STRING_TABLE
 */
message StringTableSection {
  message Entry {
    optional uint32 id = 1;
    optional string str = 2;
  }
  optional uint32 numEntry = 1;
  // repeated Entry
}
        包含两部分,Entry数量:numEntry,和重复的Entry,每个Entry又是一个Message,包含id和str两部分。

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


相关文章
|
1月前
|
Java
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
58 2
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
|
1月前
Hadoop-09-HDFS集群 JavaClient 代码上手实战!详细附代码 安装依赖 上传下载文件 扫描列表 PUT GET 进度条显示(二)
Hadoop-09-HDFS集群 JavaClient 代码上手实战!详细附代码 安装依赖 上传下载文件 扫描列表 PUT GET 进度条显示(二)
43 3
|
1月前
|
分布式计算 Java Hadoop
Hadoop-09-HDFS集群 JavaClient 代码上手实战!详细附代码 安装依赖 上传下载文件 扫描列表 PUT GET 进度条显示(一)
Hadoop-09-HDFS集群 JavaClient 代码上手实战!详细附代码 安装依赖 上传下载文件 扫描列表 PUT GET 进度条显示(一)
40 2
|
1月前
|
分布式计算 Hadoop 网络安全
Hadoop-08-HDFS集群 基础知识 命令行上机实操 hadoop fs 分布式文件系统 读写原理 读流程与写流程 基本语法上传下载拷贝移动文件
Hadoop-08-HDFS集群 基础知识 命令行上机实操 hadoop fs 分布式文件系统 读写原理 读流程与写流程 基本语法上传下载拷贝移动文件
37 1
|
1月前
|
存储 机器学习/深度学习 缓存
Hadoop-07-HDFS集群 基础知识 分布式文件系统 读写原理 读流程与写流程 基本语法上传下载拷贝移动文件
Hadoop-07-HDFS集群 基础知识 分布式文件系统 读写原理 读流程与写流程 基本语法上传下载拷贝移动文件
47 1
|
3月前
|
存储 分布式计算 Hadoop
|
4月前
|
分布式计算 Hadoop 关系型数据库
实时计算 Flink版操作报错合集之Hadoop在将文件写入HDFS时,无法在所有指定的数据节点上进行复制,该如何解决
在使用实时计算Flink版过程中,可能会遇到各种错误,了解这些错误的原因及解决方法对于高效排错至关重要。针对具体问题,查看Flink的日志是关键,它们通常会提供更详细的错误信息和堆栈跟踪,有助于定位问题。此外,Flink社区文档和官方论坛也是寻求帮助的好去处。以下是一些常见的操作报错及其可能的原因与解决策略。
|
1月前
|
分布式计算 Kubernetes Hadoop
大数据-82 Spark 集群模式启动、集群架构、集群管理器 Spark的HelloWorld + Hadoop + HDFS
大数据-82 Spark 集群模式启动、集群架构、集群管理器 Spark的HelloWorld + Hadoop + HDFS
162 6
|
1月前
|
SQL 分布式计算 监控
Hadoop-20 Flume 采集数据双写至本地+HDFS中 监控目录变化 3个Agent MemoryChannel Source对比
Hadoop-20 Flume 采集数据双写至本地+HDFS中 监控目录变化 3个Agent MemoryChannel Source对比
62 3
|
1月前
|
SQL 分布式计算 Hadoop
Hadoop-14-Hive HQL学习与测试 表连接查询 HDFS数据导入导出等操作 逻辑运算 函数查询 全表查询 WHERE GROUP BY ORDER BY(一)
Hadoop-14-Hive HQL学习与测试 表连接查询 HDFS数据导入导出等操作 逻辑运算 函数查询 全表查询 WHERE GROUP BY ORDER BY(一)
46 4