HDFS是传统的Master-Slave架构:一个集群由一个Master节点和若干个Slave节点组成。在HDFS中,Master节点称为Namenode,Slave节点称为Datanode。下面我们详细说明。
Block
首先说一个概念:块(block)。在普通的磁盘文件系统中也有block的概念,它是我们读写数据的最小单元,对用户来说一般是透明的。同样,在HDFS中也有block的概念,但它和传统的文件系统的block有以下两个显著的区别:
块大小。普通的文件系统的块大小一般都是KB级别的,比如Linux一般是4KB;而HDFS的block现在默认大小128MB,而且实际中往往可能会设置的更大一些,设置比较大的主要原因是为了缩短寻址(seek)的时间。如果block足够大,那我们从磁盘上传输数据所耗费的时间将远远大于寻找起始block的时间。这样我们传输一个由多个block组成的数据所花费的时间基本就是由传输速率决定。有一个经验上的快速计算公式,如果寻址时间是10ms,数据传输速率是100MB/s,那如果想让寻址时间是传输时间的1%,block的大小就需要是100MB。
在普通磁盘文件系统中,当一个文件不足一个block大小的时候,它也会独占这个block,其他文件不可以再使用该block。但在HDFS中,一个比block小的文件不会独占一个block,比如一个1MB的文件只会占1MB的空间,而不是128MB,其他文件还可以使用该block的其他空间。
Filesystem Namespace
HDFS的文件系统命名空间与传统文件系统结构类似,由目录和文件组成,支持创建、删除、移动、重命名文件和目录等操作。HDFS支持用户配额(users quotas)和权限控制,权限控制与Linux的权限控制类似。用户配额主要包含两个维度:
Name Quotas:限制用户根目录所能包含的文件和目录总数。
Space Quotas:限制用户根目录的最大容量(字节)。
目前HDFS还不支持软连接和硬链接。
Namenode和Datanode
Namenode保存着HDFS中所有文件和目录的元数据信息,这些元数据信息以namespace image和edit log的形式存在Namenode所在节点的本地硬盘上面。Namenode还记录着一个文件的所有block在哪些Datanode上面,以及具体的位置,这些信息保存着内存里面,不落盘。因为这些信息每个Datanode会周期性的发给Namenode。Datanode是真正存储数据的节点,它会周期性的将自己上面所存储的block列表发给Namenode。当我们要获取一个文件时,从Namenode处查到这个文件的所有block在哪些Datanode上面,然后去这些Datanode上面查出具体的block。
从上面的描述可以看出,如果Namenode节点挂掉,所有的元数据将丢失,导致整个HDFS不可用。针对这个问题,Hadoop提供了两种机制来避免该问题:
备份所有的元数据信息。我们可以配置HDFS同时向多个文件系统写这些元数据信息,这个写是同步的、原子的。常用的配置策略是本地写一份,远程的NFS文件系统写一份。
运行一个Secondary NameNode。之前我们安装Hadoop的时候,已经看到有一个进程叫SecondaryNameNode了。需要注意的是这个SecondaryNameNode并不是Namenode的一个热备(standby),它的作用其实是周期性的将namespace image和edit log合并,产生新的image,防止edit log变的非常大。同时会保留一份合并后的image文件。这样当Namenode挂掉后,我们可以从这个保留的image文件进行恢复。但SecondaryNameNode的操作较Namenode还是有一定延迟的,所以这种方式还是会丢一些数据。
当然,上面的两种机制都只能保证Namenode出现故障时数据不丢或者丢失的少,但无法保证服务继续可用。Hadoop 2目前也提供了热备的方式来实现HA,当一个Namenode故障后,另外一个热备的Namenode马上会接替故障的Namenode对外提供服务。当然要实现这种热备需要做一些配置:
两个Namenode使用高可用的共享存储来共享edit log。
Datanode需要周期性的将自己上面的block信息同时发送给两个Namenode,因为这个信息是存在Namenode的内存里面的。
客户端需要能够在一个Namenode故障后,自动切换到热备Namenode上。
如果配了热备后,就不需要SecondaryNameNode了,因为standby的Namenode会包含SecondaryNameNode的功能,去做合并。
Data Replication
为了保证一些Datanode挂掉的情况下数据不丢失,HDFS和许多分布式文件系统一样做了数据冗余:默认情况下,一份数据会有三份(即Replication Factor为3,可通过dfs.replication配置项更改)。也就是说,我们在HDFS上面存储一份数据的时候,实际存储了Replication Factor份。那这三份数据的存放位置如何选择呢?目前Hadoop的选择机制如下:
第一份数据存储在发起写操作的客户端所在的Datanode上面。如果该客户端不在集群中Datanode节点上,则随机选择一个负载不是很重的Datanode。
如果第一份数据存储在机架A上面的某一个Datanode节点上,那第二份数据就随机找一个非A机架上的一个Datanode节点存储。
第三份数据找一个与第二份数据同机架,但不同节点的Datanode存储。
如果Replication Factor大于3的话,其他的副本在集群内随机找一些节点存储,同时会尽量避免很多个副本存储于同一机架。
Read&Write
这里借Hadoop The Definitive Guide上的读写图。
先看读的过程:
![](https://img.haomeiwen.com/i11134080/8f9d9fcae2ae2c62.png)
1、HDFS的读操作相对比较简单,客户端先去Namenode获取block的信息,然后去Datanode获取block。这里有两个注意点:
一个block有多个副本,具体获取的时候有优先从离客户端比较近的Datanode获取数据。
从Namenode拿到block信息后,具体获取block只需要和Datanode交互,这样降低了Namenode的负担,不会使之成为读的瓶颈,并且把所有的度分到了各个Datanode上面,达到了负载均衡。
写的过程:这里假设我们创建一个新文件,然后写数据,最后关闭。
2、{
过程描述:
(1)客户端调用FileSyste对象的open()方法在分布式文件系统中打开要读取的文件。
(2)分布式文件系统通过使用RPC(远程过程调用)来调用namenode,确定文件起始块的位置。
(3)分布式文件系统的DistributedFileSystem类返回一个支持文件定位的输入流FSDataInputStream对象,FSDataInputStream对象接着封装DFSInputStream对象(存储着文件起始几个块的datanode地址),客户端对这个输入流调用read()方法。
(4)DFSInputStream连接距离最近的datanode,通过反复调用read方法,将数据从datanode传输到客户端。
(5) 到达块的末端时,DFSInputStream关闭与该datanode的连接,寻找下一个块的最佳datanode。
(6)客户端完成读取,对FSDataInputStream调用close()方法关闭连接。
}
![](https://img.haomeiwen.com/i11134080/5325820e859f91d0.png)
写的过程涉及两个队列和一个pipeline:
data queue:写数据的时候,其实就是将数据拆成多个packet写到该队列里面。
Datanode pipeline:实质就是一个由多个Datanode组成的列表,Datanode的个数由Replication Factor决定。
ack queue:该队列里面存放的也是数据packet。
完整的过程如下:
DistributedFileSystem先向Namenode发一个创建的请求,如果Namenode会做一些检查(如同名文件是否存在、是否有创建权限等)。如果检查通过,就会创建该文件的元数据,此时没有任何block与之关联。接下来就是写具体数据。
FSDataOutputStream把数据拆成多个packet写到data queue里面。然后DataStreamer向Namenode申请新的block来存储这些数据,Namenode会返回一个Datanode pipeline,然后DataStreamer把数据发给pipeline里面的第一个Datanode,该Datanode存储数据后,再把数据转发给第二个Datanode,这样一直到pipeline里面的最后一个Datanode。
FSDataOutputStream同时维护着一个ack queue,里面是各Datanode已经存储了去写,但还未确认的packet。等收到pipeline里面所有Datanode数据写成功的ack后,就删掉ack queue队列里面的对应的packet。如果某个Datanode失败的话,就会被Namenode从pipeline中删除,并重新选择一个Datanode加到pipeline里面去,然后重新写数据到新加的Datanode上面。失败的Datanode上面写的部分不完整数据会在该Datanode恢复后删除。
这里需要注意的是并不是等所有副本都写成功后才向客户端返回,而是只要有dfs.namenode.replication.min(默认值是1)个副本写成功,就向客户端返回成功。其他的副本会异步的去写。
2、{
写文件过程分析:
(1) 客户端通过对DistributedFileSystem对象调用create()函数来新建文件。
(2) 分布式文件系统对namenod创建一个RPC调用,在文件系统的命名空间中新建一个文件。
(3)Namenode对新建文件进行检查无误后,分布式文件系统返回给客户端一个FSDataOutputStream对象,FSDataOutputStream对象封装一个DFSoutPutstream对象,负责处理namenode和datanode之间的通信,客户端开始写入数据。
(4)FSDataOutputStream将数据分成一个一个的数据包,写入内部队列“数据队列”,DataStreamer负责将数据包依次流式传输到由一组namenode构成的管线中。
(5)DFSOutputStream维护着确认队列来等待datanode收到确认回执,收到管道中所有datanode确认后,数据包从确认队列删除。
(6)客户端完成数据的写入,对数据流调用close()方法。
(7)namenode确认完成。
}
网友评论