HDFS Architecture
Introduction
HDFS(Hadoop Distributed File System)是一个分布式文件系统,它具有很高的容错性,可以支持运行在廉价的硬件设备上。HDFS具有很高的吞吐量,非常适合拥有海量数据的application。HDFS放宽了一些POSIX要求,以支持流的方式访问文件系统中的数据。HDFS是Apache Hadoop核心项目的一部分。
HDFS设计目标
- 硬件故障是经常会发生的
一个HDFS实例可以包含成百上千台的服务器,每个服务器存储整个文件系统数据的一部分。每台服务器都有一定的概率出现硬件故障,这意味着一个HDFS实例中的一部分服务器在某个时刻可能是不可用的。因此,发现故障并能够从故障中快速恢复是HDFS的核心目标。
- 批处理
HDFS适合应用在批处理领域,而不适合与用户交互的领域。它的设计目标是数据访问的高吞吐量,而不是低延迟。
- 大数据集
运行在HDFS上的application通常都拥有大数据集,例如一个文件可以达到gigabytes或terabytes大小。因此一个HDFS集群可以扩展到数百个节点,可以支持数千万个文件的存储。
- 简单的一致性模型
HDFS上的文件模型是write-once-read-many。一个文件在完成创建-写入数据-关闭后只能再对文件执行appends和truncates两种类型修改操作,无法再执行其它形式的修改。即只能在文件尾部添加或删除数据,不支持在文件的任意位置修改数据。
- 使计算节点靠近它需要的数据
在分布式系统中,如果一个计算节点要处理的数据离自己很近,那么会有助于提高数据计算的效率。要处理的数据量越大,则效率的提升越明显。因为这会降低整个系统的网络拥塞,并提高分布式系统整体的吞吐量。HDFS提供了相应的API,可以将application本身移向数据存储的节点。
NameNode and DataNodes
HDFS采用master/slave架构,集群中master的角色由NameNode扮演,slave的角色由DataNode扮演。
一个HDFS集群中只包含一个NameNode节点,它负责管理整个文件系统的命名空间(namespace),并对访问文件的client进行权限控制。此外,一个集群包含有多个DataNode节点,通常集群中的每个服务器上都运行一个DataNode程序,DataNode负责管理它所在服务器节点的存储空间。
HDFS对外暴露一个统一的文件系统命名空间并允许用户数据存储在文件中。在HDFS内部,一个文件会被拆分成一个或多个block,这些block会被存储到一组DataNodes中。
NameNode会负责执行文件系统的一系列操作,如open、close、对文件或目录rename等。此外,NameNode还会负责在文件block和DataNode间建立mapping关系。
DataNode会负责对从client发起的文件系统读写请求提供服务。DataNode还会负责文件block的创建和删除,并根据NameNode的指示创建文件block的多副本。
NameNode和DataNode都可以运行在廉价的硬件设备上,这些硬件设备通常会安装Linux操作系统。HDFS是由Java语言开发的,所以任何支持Java的平台都能够运行NameNode和DataNode程序。一个HDFS集群典型的部署方式是用一台专门的服务器部署NameNode,再在集群中的其它服务器各运行一个DataNode。也可以在一个服务器上部署多个DataNode,但这种部署方式并不常见。
整个集群只有一个NameNode节点大大简化了HDFS系统的架构——NameNode负责管理整个文件系统的元数据(metadata)。在这种架构下,用户数据只由DataNode处理,永远不会流经NameNode节点。
The File System Namespace
HDFS支持传统层级结构的文件组织方式(类似Linux文件结构),用户可以创建目录结构并用于存储文件。用户可以在HDFS创建、删除文件,将文件从一个目录转移到另一个目录,对文件重命名等,此外还支持文件权限控制。HDFS不支持hard link和soft link,但在未来不排除实现这个特性的可能。
NameNode负责维护整个文件系统的namespace,任何对文件系统namespace的修改都会被记录到NameNode中。例如,application可以指定一个文件在HDFS中存储的副本数量,这个副本数量叫做这个文件的副本因子(replication factor),这个信息就是存储在NameNode中。
Data Replication
HDFS的设计目标是能够跨集群中多个服务器可靠存储非常大的文件,它将文件切分成一系列的block进行存储。为了实现容错性,每个文件block都会存储多个副本。每个文件的block大小和其副本因子都是可以配置的,一个文件的所有block大小都相同(除了最后一个block)。application可以指定文件副本的数量,副本因子既可以在文件创建时指定,也可以在文件创建好后修改。HDFS中的文件都只能被写一次(除了appends和truncates),并且在任何时刻都只能有一个writer在执行写操作(不支持并发写操作)。
NameNode负责管理所有的文件block replication,它会周期的收到集群中的DataNode发送的心跳和Block report。收到心跳意味着DataNode工作正常,Blockreport则包含DataNode上所有block的列表。
The Persistence of File System Metadata
在NameNode服务器上有一个普通文件叫做FsImage,它存储了整个文件系统的namespace,包括blocks和文件的mapping,文件系统的其它属性等也都被存储在这个文件中。NameNode会将FsImage文件内容保存到内存中,以提高文件系统元数据的效率。虽然这样读取FsImage中的元数据会很高效,但如果每次文件系统元数据被修改就直接修改FsImage文件中的内容,效率会非常的差。
所以NameNode引入了Editlog机制,它利用一个被称作Editlog的事务日志持久化记录文件系统元数据的每次变化。例如,当在HDFS中创建一个新的文件时,NameNode将会在EditLog中insert一条新的记录标识这次变化。同样的,修改一个文件的副本因子也会向Editlog中insert一条新的记录。NameNode会借助它所在服务器上操作系统的文件系统存储Editlog,Editlog只是增量的记录每个对文件系统元数据的操作。
Editlog中存储的只是对文件系统元数据修改的操作,这些操作的结果总归要反应到FsImage中的数据变化才行。所以每隔一段时间,NameNode会结合Editlog和当前的FsImage生成一个新版本的FsImage,新版本的FsImage中包含了Editlog中记录的操作对文件系统对元数据的修改。新版本的FsImage生成好之后,Editlog记录的操作日志就可以被删除了。这个过程被称作checkpoint。
checkpoint的执行可以通过配置指定。dfs.namenode.checkpoint.period配置指定经过多长时间执行一次checkpoint,单位时间是秒。dfs.namenode.checkpoint.txns配置执行经过多少次事务操作触发一次checkpoint。如果同时配置了这两个选项,那么哪个先满足条件,哪个就会触发checkpoint。
在NameNode刚启动或者checkpoint期间,NameNode会执行从磁盘将FsImage文件的内容刷新到内存的操作。
DataNode并不知道文件的概念,在它眼中只有block,它存储的只是一个一个block数据。
Robustness
HDFS的主要目标就是即使在有硬件错误的情况下,也要能够可靠的存储数据。可能发生的错误主要有三种,分别是NameNode服务器出错,DataNode服务器出错,网络异常引发脑裂。
- 应对DataNode服务器异常,心跳机制和多副本数据
每个DataNode都会周期性的向NameNode发送心跳message。如果网络异常导致整个网络被割裂,将会导致有一部分DataNode与NameNode的连接断开。NameNode可以根据心跳message判断与DataNode的连接状况,如果一段时间内都没有收到DataNode的心跳message,那么NameNode会把那个DataNode标记为dead,并且不再向被标记为dead的DataNodes转发任何IO请求。被标记为dead的DataNodes上的所有数据对整个HDFS系统都是不可用的。
DataNode死亡可能会导致某些文件block的副本数量低于副本因子指定的副本数量,这就需要重新增加这些文件block的副本数量,使其恢复到原来指定的值。NameNode会持续跟踪这些需要增加副本数量的文件block并为其增加新的副本。
有下面几种情况,NameNode会为文件block增加新的副本:
a. DataNode节点故障,导致在其上的所有文件block副本均不可用
b. DataNode服务器上的磁盘故障,导致该磁盘上的所有文件block全部不可用
c. 重新对一个文件的副本因子进行设置使其增大,表示需要更多的副本
将一个DataNode标记为dead是很谨慎的,默认情况下是NameNode在10分钟内收不到一个DataNode的心跳message,才会将这个DataNode标记为dead。当然你可以将这个时间设置的更短。
- Cluster Rebalancing
HDFS可以对数据的存储位置做调整使数据在整个集群中的存储“重平衡”,即将数据从一个DataNode节点移动到另一个DataNode节点。但目前“重平衡”机制还没有被实现。
- Data Integrity
从DataNode获取的数据有可能是错误的,这种数据错误可能由存储硬件、网络错误和软件bug等多种原因导致。HDFS客户端实现了对文件内容的checksum检查机制。当客户端在HDFS上创建一个文件时,它会对这个文件的每个block都计算一个checksum,并将这些checksums分别保存在HDFS上的隐藏文件中。当客户端接收文件内容时,它会验证从DataNode发来的数据是否和存储在隐藏文件中的checksum值匹配。如果不匹配,客户端将选择从另一个DataNode节点获取那个block的副本。
- Metadata Disk Failure
FsImage和EditLog是HDFS系统的核心数据结构,如果它们出现一点问题,那么将会导致整个HDFS实例不可用。为了防止FsImage和EditLog数据被破坏,可以配置NameNode存储FsImage和EditLog两种数据的多份copy,任何FsImage和EditLog的修改操作都会触发对所有copy的同步更新。这种同步更新全部copy的机制会导致NameNode的TPS下降,但是元数据的TPS下降是可以接受的,因为HDFS应用一般都是读多写少,并不会对元数据做频繁的修改。
另一种增强NameNode的抗挫机制是增加多个NameNode节点,这点会在后面在讨论。
- Snapshots
可以在某个时刻存储HDFS整个系统的一个copy,这种机制可以支持在HDFS数据被破坏时rollback到之前的一个good point。
Data Organization
- Data Blocks
通常情况下,HDFS中的一个文件block大小为128MB。HDFS中的一个文件会被切割成多个128MB的block存储。如果条件允许,一个文件的每个block会被分散到不同的DataNode节点存储。
- Replication Pipelining
客户端向HDFS写入数据,假设副本因子是3。首先客户端会先访问NameNode节点,NameNode会根据replication target choosing algorithm从所有DataNodes中选出3个,并向客户端返回由这3个DataNode组成的列表。被选中的这3个DataNode将用来存储block的3个副本。
接着,客户端会向第一个DataNode写入数据,写入数据的方式是分批(portion)进行的,写完block的一批(一部分)数据后,再写下一批数据。第一个DataNode节点接收到一批数据后,会将接收到的数据保存在自己本地的存储,然后将数据转发给列表中第二个DataNode节点。第二个DataNode节点接收到数据后,也会将数据保存到自己本地存储,然后将数据转发给列表中第三个DataNode节点。第三个节点接收到数据,将数据写入到自己的本地存储,至此一批数据算是写入完成。
这三个DataNode构成了一条流水线,一个DataNode会从它前面的DataNode获取数据,并将收到的数据转发给它后面的DataNode。就这样,数据沿着这条流水线从第一个DataNode转发到最后一个DataNode。
详细了解HDFS写入数据流程,可以参考这个漫画。
The Communication Protocols
HDFS的通信协议是建立在TCP/IP协议之上的。NameNode不会主动发起连接请求,它只有对请求做出响应。