hadoop文件数据结构在代码里的组成可以分为 storage、block、file、Inode几类
很容易搞混,下面对这几个部分进行分别分析
1storage
Storage的结构图下图:
Storage是系统运行时对应的数据结构。从大到小,Hadoop中最大的结构是Storage,最小的结构是block。
Storage保存了和存储相关的信息(包括节点的类型:namenode/datanode,状态版本,namespace id, 状态的创建时间),它继承了StorageInfo。
Storage包括 protected List<StorageDirectory> storageDirs = new ArrayList<StorageDirectory>();
StorageDirectory是Storage的内部类,
Storage可以存储在多个StorageDirectory内,但是要保证这些文件的版本号是一致的,在namenode/datanoe初始化的时候会读取(还原)存储状态信息。
应用于DataNode的DataStorage,则继承了Storage,总体类图如下:
DataStorage是Storage的子类,专门应用于DataNode。对DataNode的升级/回滚/提交过程,就是对DataStorage的doUpgrade/doRollback/doFinalize分析得到的。
DataStorage提供了format方法,用于创建DataNode上的Storage格式化。
StorageInfo包含了3个字段,分别是layoutVersion:版本号,如果Hadoop调整文件结构布局,版本号就会修改,这样可以保证文件结构和应用一致。
namespaceID是Storage的ID。
StorageDirectory中最重要的方法是analyzeStorage,它将根据系统启动时的参数和我们上面提到的一些判断条件,返回系统现在的状态。StorageDirectory可能处于以下的某一个状态(与系统的工作状态一定的对应):
NON_EXISTENT:指定的目录不存在; NOT_FORMATTED:指定的目录存在但未被格式化; COMPLETE_UPGRADE:previous.tmp存在,current也存在 RECOVER_UPGRADE:previous.tmp存在,current不存在 COMPLETE_FINALIZE:finalized.tmp存在,current也存在 COMPLETE_ROLLBACK:removed.tmp存在,current也存在,previous不存在 RECOVER_ROLLBACK:removed.tmp存在,current不存在,previous存在 COMPLETE_CHECKPOINT:lastcheckpoint.tmp存在,current也存在 RECOVER_CHECKPOINT:lastcheckpoint.tmp存在,current不存在 NORMAL:普通工作模式。
StorageDirectory处于某些状态是通过发生对应状态改变需要的工作文件夹和正常工作的current夹来进行判断。状态改变需要的工作文件夹包括:
previous:用于升级后保存以前版本的文件 previous.tmp:用于升级过程中保存以前版本的文件 removed.tmp:用于回滚过程中保存文件 finalized.tmp:用于提交过程中保存文件 lastcheckpoint.tmp:应用于从NameNode中,导入一个检查点 previous.checkpoint:应用于从NameNode中,结束导入一个检查点
有了这些状态,就可以对系统进行状态相关操作。
fsImage是storage的子类,提供了状态的管理功能。FSImage也能够管理多个Storage,而且还能够区分Storage为IMAGE(目录结构)/EDITS(日志)/IMAGE_AND_EDITS(前面两种的组合)。
在namnode初始化的时候,会初始化FSNamesystem, 然后查找StorageDirectory,查找配置文件(dfs.name.edits.dir和 dfs.name.dir)就是正在修改的命名空间文件和命名空间文件,放到List<StorageDirectory> storageDirs 中,循环找出需要进行修复或者回滚的文件内容,进行处理。查找到最新的StorageDirectory,装载image文件(\home\baqun\tmp\dfs\name\current\fsimage其中包括了版本信息、文件个数、最后修改时间、包含的block的信息、权限控制信息、datanode信息、正在创建的文件信息)
(这是一个复杂的过程,把blocks的信息、inode的信息,permission、parent inode然后组装起来 读取datanode\)
然后把edit log也装载进来:读取两个edit文件,一个是之前的edit log,另外一个是正在写的log文件,装载然后merge进内存。
2 datanode
![](http://img.my.csdn.net/uploads/201211/25/1353856681_9047.png)
DatanodeInfo和DatanodeID都定义在包org.apache.hadoop.hdfs.protocol。
DatanodeID只包含了一些配置信息(name:hostname:portNumber,storageID,infoPort,ipcPort),DatanodeInfo增加了一些动态信息(使用容量,结余等)。
DatanodeDescriptor是DatanodeInfo的子类,包含了NameNode需要的附加信息。DatanodeDescriptor 包含了DataNode上一些Block的动态信息。DatanodeDescriptor包含了内部类BlockTargetPair,它保存Block和对应DatanodeDescriptor的关联,BlockQueue是BlockTargetPair队列。DatanodeDescriptor包含了两个BlockQueue,分别记录了该DataNode上正在复制(replicateBlocks)和Lease恢复(recoverBlocks)的Block。同时还有一个Block集合,保存的是该DataNode上已经失效的Block。
private BlockQueuereplicateBlocks =new BlockQueue(); private BlockQueuerecoverBlocks =new BlockQueue(); private Set<Block>invalidateBlocks =newTreeSet<Block>();
DatanodeDescriptor提供一系列方法,用于操作上面保存的队列和集合。也提供get*Command方法,用于生成发送到DataNode的命令。
而DatanodeRegistration存储了namenode需要进行验证的一切信息,还包含了StorageInfo(layoutVersion,namespaceID创建时间)
只有DatanodeDescriptor 是放在namenode包中的,其他都是放在protocol包
DataNode中的每个block副本由本机本地文件系统中的两个文件组成。第一个文件包含数据本身,第二个文件是该block的元数据包括该block数据的校验和及该block的 generation stamp。数据文件大小等于该block的实际长度,同时不需要补上额外的空间以达到标准的块大小{!比如该block只有10MB,那么本地文件系统中的数据文件大小就是10MB,而无需在额外补足让它变成标准的128MB}。因此,如果一个block只有标准大小的一半,那么本地磁盘也只需要半个标准block所需的空间。
在每个DataNode启动时,会连接到NameNode执行一个握手。握手的目的是为了验证名字空间ID及DataNode的软件版本。如果其中只要有一个无法与NameNode匹配,那么DataNode会自动关闭。
名字空间ID是在文件系统创建时分配给它的实例编号。名字空间ID会持久化存储在集群的所有节点中。具有不同名字空间ID的节点无法加入到集群中,这就保护了文件系统的数据完整性。
软件版本的一致性是非常重要的,因为不兼容的版本可能会导致数据损坏或丢失,同时在一个具有数千个节点的大规模集群上,很容易会在升级期间忽略掉某些节点,比如它没有在升级之前正确的关闭或者在升级时处于不可用的状态。
允许一个新初始化的并且没有任何名字空间ID的DataNode加入到集群中,它会接受集群的名字空间ID。{这是因为很多情况下我们需要对集群进行扩容,因此HDFS应该允许我们往集群中添加新机器}
在握手过程完成之后,DataNode会与NameNode进行注册。DataNodes会持久化存储它们自己对应的那个唯一的存储ID。存储ID是DataNode的内部标识符,可以保证即使是它更换了IP地址或者端口也能识别出来。存储ID是在DataNode第一次向NameNode进行注册时分配的,之后它就再也不会改变。
DataNode会通过向NameNode发送一个block report来声明它所拥有的block副本。一个block report包含该block的id,世代戳(generation stamp)以及它所持有的block副本长度。当DataNode注册完成之后就会立即发送第一次的block report。之后,会每隔1小时就进行一次block reports发送,从而为NameNode提供关于该集群内的所有block副本的最新位置信息。
在正常情况下,DataNode会向NameNode发送心跳信息以证实它自己正在运行以及它所持有的block副本是可用的。默认的心跳周期是3秒钟。如果NameNode在十分钟内收不到来自某个DataNode的心跳信息,它会认为该DataNode已经不能提供服务,它所持有的block副本就变成了不可用状态。NameNode就会将这些block副本在其他DataNode上创建出来。
来自DataNode的心跳中还会携带一些关于总的存储容量、存储空间使用量及当前正在处理的数据传输量方面的信息。这些统计信息会被用于NameNode的空间分配及负载平衡决定中。
NameNode不会直接联系DataNode,它会通过对心跳的响应信息来向DataNodes发送指令。这些指令包括如下一些命令:
l 复制blocks到其他节点
l 删除本地的block副本
l 重新注册或者关闭节点
l 发送一个即时block report
这些命令对于维护整个系统的完整性是十分重要的,因此就算是在大规模的集群中,保持心跳的通畅也是非常重要的。NameNode每秒可以处理数千个心跳请求而不会影响到其他的NameNode操作。
3 inode*
INode*抽象了文件层次结构。如果我们对文件系统进行面向对象的抽象,一定会得到和下面一样类似的结构图(类INode*):
Hadoop在内存中存储了file/block的层级结构,Inode就是一个最上层的抽象类,存储了普通的信息:修改时间、父node。内部类DirCounts封装了namespace使用和diskspace使用。
INode是一个抽象类,它的两个字类,分别对应着目录(INodeDirectory)和文件(INodeFile)。INodeDirectoryWithQuota,如它的名字隐含的,是带了容量限制的目录。INodeFileUnderConstruction,抽象了正在构造的文件,当我们需要在HDFS中创建文件的时候,由于创建过程比较长,目录系统会维护对应的信息。
INode中的成员变量有:name,目录/文件名;modificationTime和accessTime是最后的修改时间和访问时间;parent指向了父目录;permission是访问权限。HDFS采用了和UNIX/Linux类似的访问控制机制。系统维护了一个类似于UNIX系统的组表(group)和用户表(user),并给每一个组和用户一个ID,permission在INode中是long型,它同时包含了组和用户信息。
INodeDirectory是HDFS管理的目录的抽象,它最重要的成员变量是:
private List<INode>children;
就是这个目录下的所有目录/文件集合。INodeDirectory也是有大量的get和set方法,都很简单。INodeDirectoryWithQuota进一步加强了INodeDirectory,限制了INodeDirectory可以使用的空间(包括NameSpace和磁盘空间)。
INodeFile是HDFS中的文件,最重要的成员变量是:
protected BlockInfo blocks[] =null;
这是这个文件对应的Block列表,BlockInfo增强了Block类。
INodeFileUnderConstruction
INodeFileUnderConstruction保存了正在构造的文件的一些信息,包括clientName,这是目前拥有租约的节点名(创建文件时,只有一个节点拥有租约,其他节点配合这个节点工作)。clientMachine是构造该文件的客户端名称,如果构造请求由DataNode发起,clientNode会保持相应的信息,targets保存了配合构造文件的所有节点。
INodeDirectoryWithQuota
有一定限额的文件目录类
4 fileSystem
程序员通过org.apache.hadoop.fs.FileSystem来和HDFS打交道
子类分布式文件系统包含了dfsClient,用来调用数据
DistributedFileSystem是分布式文件系统的主要实现类:
文件系统之间的交互,通过DFSClient实现
初始化的时候就新建了DFSClient
public void initialize(URI uri, Configuration conf) throws IOException { setConf(conf); String host = uri.getHost(); if (host == null) { throw new IOException("Incomplete HDFS URI, no host: "+ uri); } InetSocketAddress namenode = NameNode.getAddress(uri.getAuthority());//localhost/127.0.0.1:9200 this.dfs = new DFSClient(namenode, conf, statistics); this.uri = NameNode.getUri(namenode); this.workingDir = getHomeDirectory(); }
5 block
代码结构如下
BlocksMap(主要是FSNamesystem负责管理)保存了Block和它在NameNode上一些相关的信息。其核心是一个map:Map<Block, BlockInfo>。BlockInfo扩展了Block,保存了该Block归属的INodeFile和DatanodeDescriptor,同时还包括了它的前继和后继Block。有了BlocksMap,就可以通过Block找对应的文件和这个Block存放的DataNode的相关信息。
FSDir(FSDataset)对应了hadoop的目录,目录可以是层级嵌套的,因为每一个文件夹规定了所包含的块的个数的限制.
FSDir实现了对block的管理,添加block的时,会优先使用不创建子目录的方式添加(所包含的block个数不到到最大值)。
FSVolume对应了Storage的管理。
datdanode初始化的时候:
storage = new DataStorage(); FSDatasetInterface data = new FSDataset(storage, conf);
将storage和block的信息进行关联管理。
FSDataset包括变量:
FSVolumeSet volumes; private HashMap<Block,DatanodeBlockInfo> volumeMap = null;
实现了block和datanode信息的关联。
总结:
他们之间的关系可以通过下图来表示