HDFS即Hadoop Distributed File System分布式文件系统,它的设计目标是把超大数据集存储到分布在网络中的多台普通商用计算机上,并且能够提供高可靠性和高吞吐量的服务。分布式文件系统要比普通磁盘文件系统复杂,因为它要引入网络编程,分布式文件系统要容忍节点故障也是一个很大的挑战。
Namenode 和 Datanode
HDFS采用master/slave架构。一个HDFS集群是一个Namenode和一定数量的Datanode组成。Namenode是一个中心服务器,负责管理文件系统的namespace和客户端对文件的访问。Datenode在集群中一般是一个节点一个,负责管理节点上他们附带的存储。在内部,一个文件其实分成一个或多个block,这些block存储在Datanode集合里。Namenode执行文件系统的namespace操作,例如打开、关闭、重命名文件和目录,同时决定block到具体Datanode节点的映射。Datanode在Namenode的指挥下进行block的创建、删除和复制。Namenode和Datenode都是设计成可以跑在普通的廉价的运行linux的机器上。HDFS采用java语言开发,因此可以部署在很大范围的机器上。一个典型的部署场景是一台机器跑一个单独的Namenode节点,集群中的其他机器各跑一个Datanode实例。这个架构并不排除一台机器上跑多个Datanode,不过这比较少见。
单个节点的Namenode大大简化了系统的架构。Namenode负责保管和管理所有的HDFS元数据,因而用户数据就不需要通过Namenode(也就是说文件数据的读写是直接在Datanode上)。
文件系统的namespace
HDFS支持传统的层次型文件组织,与大多数其他文件系统类似,用户可以创建目录,并在其间创建、删除、移动和重命名文件。HDFS不支持user quotas和访问权限,也不支持链接,不过当前的架构并不排除实现这些特性。Namenode维护文件系统的namespace,任何对文件系统namespace和文件属性的修改将被Namenode记录下来。应用可以设置HDFS保存的文件的副本数目,文件副本的数目称为文件的relication因子,这个信息也是由Namenode保存。
数据复制
HDFS被设计成在一个大集群中可以跨机器地可靠地存储海量地文件。它将每个文件存储成block序列,除了最后一个block,所有的block都是同样的大小。文件的所有bolck为了容错都会被复制。每个文件的block大小和replication因子都是可配置的。Replication因子可以在文件创建的时候配置,以后也可以改变。HDFS中的文件是write-one,并且严格要求在任何时候只有一个writer。Namenode全权管理block的复制,它周期性地从集群中的每个Datanode接收心跳包和一个Blockreport。心跳包的接收表示该Datanode节点正常工作,而Blockreport包括了该Datanode上所有的block组成的列表。
元数据块
元数据(Metadata)是描述其它数据的数据(data about other data),或者说是用于提供某种资源的有关信息的结构数据(structured data)。
元数据包括:
-文件系统目录树信息
-文件名,目录名
-文件和目录的从属关系
-文件和目录的大小,创建及最后访问时间
-权限
-文件和块的对应关系
-文件由哪些块组成
-块的存放位置
-机器名,块ID
数据块
兼容HDFS的应用都是处理大数据集合的。这些应用都是写数据一次,读却是一次到多次,并且读的速度要满足流式读。HDFS支持文件的write-once-read-many语义。一个典型的block大小是64MB,因而,文件总是按照64M切分成chunk,每个chunk存储于不同的Datanode。
在传统的块存储介质中,块是读写的最小数据单位(扇区)
传统文件系统基于存储块进行操作
HDFS也使用了块的概念,默认64MB
-可针对每个文件配置,由客户端指定
-每个块有一个自己的全局ID
HDFS将一个文件分为一个或数个块来存储
-每个块是一个独立的存储单位
-以块为单位在集群服务器上分配存储
与传统文件系统不同的是,如果实际数据没有达到块大小,则并不实际占用磁盘空间
-如果一个文件是200M,则它会被分为4个块:64+64+64+8
当一个文件大于集群中任意一个磁盘的时候,文件系统可以充分利用集群中所有的磁盘;管理块使底层的存储子系统相对简单;块更加适合备份,从而为容错和高可用性的实现带来方便;最重要的使,采用块方式,实现了名字与位置的分离,实现了存储位置的独立性。
HDFS读写策略
数据读策略
1、客户端调用FileSystem实例的open方法,获得这个文件对应的输入流InputStream。
2、通过RPC远程调用NameNode,获得NameNode中此文件对应的数据块保存位置,包括这个文件的副本的保存位置(主要是各DataNode的地址)
3、获得输入流之后,客户端调用read方法读取数据。选择最近的DataNode建立连接并读取数据。
4、如果客户端和其中一个DataNode位于同一机器(比如MapReduce)过程中的mapper 和 reducer ),那么就会直接从本地读取数据。
5、到达数据块末端,关闭与这个DataNode的连接,然后重新查找下一个数据块。
6、不断执行第2-5步直到数据全部读完。
7、客户端调用close,关闭输入流DF S InputStream。
数据写策略
1、客户端调用FileSystem实例的create方法,创建文件。NameNode通过一些检查,比如文件是否存在,客户端是否拥有创建权限等;通过检查之后,在NameNode添加文件信息。注意,因为此时文件没有数据,所以NameNode上也没有文件数据块的信息。
2、创建结束之后,HDFS会返回一个输出流,DFSDataOutputStream给客户端。
3、客户端调用输出流DFSDataOutPutStream的write方法向HDFS中对应的文件写入数据。
4、数据首先会被分包,这些分包会写入一个输出流的内部队列Data队列中,接收完数据分包,输出流DFSDataOutputStream会向NameNode申请保存文件和副本数据块的若干个DataNode,这若干个DataNode会形成一个数据传输管道。DFSDataOutputStream将数据传输给距离上最短的DataNode,这个DataNode接收到数据包之后会传给下一个DataNode。数据在各DataNode之间通过管道流动,而不是全部由输出流分发,以减少传输开销。
5、因为各DataNode位于不同机器上,数据需要通过网络发送,所以,为了保证所有DataNode的数据都是准确的,接受到数据的DataNode要向发送者发送ACK。对于某个数据块,只有当DFSDataOutputStream收到了所有DataNode的ACK,才能确认传输结束。DFSDataOutputStream内部专门维护了一个等待ACK队列,这个队列保存已经进入管道传输数据、但是并未被完全确认的数据包。
6、不断执行3-5步直到数据全部写完,客户端调用close关闭文件。
7、DFSDataInputStream继续等待直到所有数据写入完毕并被确认,调用complete方法通知NameNode文件写入完成。NameNode接受到complete消息之后,等待相应数量的副本写入完毕后,告知客户端。