HDFS 原理史上最详细解析(包含 2.x 版本)
1. 背景
HDFS最初是参考谷歌GFS论文原理开发的一个开源产品,由Lucene开源项目的创始人Doug Cutting开发,现在已经成为大数据平台的基石。HDFS借鉴了GFS的技术架构,在设计理念上又与GFS有很大的不同,它致力于提供一个通用的分布式文件系统,与GFS作为Google内部存储系统的定位有很大区别。
HDFS定义了一套文件系统API规范(http://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/filesystem/index.html),确立了HDFS的核心模型,为用户提供了一个稳定的依赖。
HDFS的核心模型:
- 名称
HDFS文件、目录的命名规则,及其逻辑关系,与Linux普通文件系统看起来几乎一模一样。
- 原子性
在HDFS中,创建文件、删除文件、重命名文件、重命名目录、创建目录都是原子操作。递归删除目录也是原子操作。
- 一致性
HDFS的一致性模型是”复制-更新“语义,我理解就是是强一致模型。
Create、Update、Delete、Delete then create、Rename操作,在操作结束后,结果必须对后续的访问可见。
- 并发
HDFS对并发操作没有数据隔离保证。假如一个Client在访问文件的同时,另一个Client正在修改文件,那么修改的内容可能可见,也可能不可见。
- 操作失败
所有操作必须最终完成,要么成功,要么失败。
实现通过重试来保证操作成功,前提是保证一致性语义,并且重试操作对Client透明。
- 超时
HDFS对操作的超时没有定义。
在HDFS中,阻塞操作超时实际上是可变的,因为站点和客户机可能会调优重试参数,从而将文件系统故障和故障排除程序转换为操作中的暂停。取而代之的是一种普遍的假设,即FS操作“快但没有本地FS操作快”,并且数据读写的延迟随数据量的增加而增加。客户机应用程序的这种假设揭示了一个更基本的假设:文件系统性能接近网络延迟和带宽上线。
对于某些操作的开销也有一些隐含的假设。seek()操作非常快,几乎不会造成网络延迟。对于条目较少的目录,目录列表操作非常快。
- vs 对象存储
HDFS与对象存储(例如S3)有明显的不同,
- 对象存储是最终一致性模型。也就是说,一个操作的结果,要经过一段时间才能被所有的Client看到,在此之前,Client可能访问到过期数据。
- 原子性。对象存储没有目录的概念,虽然可以通过基于文件名前缀的操作,来达到类似的效果,比如通过删除前缀/user的文件,来达到删除/user目录的效果,但这不是一个原子操作,而是一个文件一个文件的独立操作。
- 持久性。HDFS和传统文件持久化非常相近,调用flush,close,文件以流的形式不停得更新到存储。而对象存储只有对文件操作结束后的时刻,才把完整的文件PUT到存储系统。
- 权限。HDFS提供传统文件系统的用户、组权限管理概念,对象存储通常没有。
2. 架构
HDFS架构和GFS非常相近
2.1 Moving Computation is Cheaper than Moving Data
这是HDFS的一个设计预期和目标,也是Hadoop大数据处理的精髓所在。
2.2 The File System Namespace
HDFS目标是做一个通用文件系统,支持传统的文件、目录概念。在这方面,GFS更像一个对象存储,它不支持文件系统模型。
2.3 BlockId
根据[1],早期block id是一个64位数随机数。当时实现比较简单,并没有判重,所以如果两个block碰巧得到同样的block id,系统会误认为是多余的备份block,而将其中一个删除。这样这个block很有可能会出错,包含它的文件则损坏。解决的办法有两个,一是记录好所有使用过的block id,以实现判重功能;二是以一种不会重复的方式生成block id,比如顺序生成。顺序生成的缺点有三个,一是现有的系统迁移困难,所有的block都要重新命名;二是用完了64位数后仍然有麻烦;三是要记录好最高的block id。
判重并不是最优的方法,因为它需要额外的工作,而且随着文件系统变得庞大将变重。假设用一个Hash实现判重,一个1PB的文件系统,假设1个block大小64MB,则包含有16M个block id,每个id为8个byte,则需要一个128MB的Hash表,这对于一个本身就很复杂的NameNode是个不小的压力。[2]中提出了一种综合的方法,给一个文件的所有block指定一个相同的range id(5个byte)作为它们block id的高位,然后按顺序每个block生成剩余的3个byte。较之前的单纯判重,好处在于减小了判重的数量;同时又方便管理同一个文件的block,因为它们的block id是连续的。[2]也指出这种方法的问题,当一个文件被删除时,此range id要从系统中抹去,如果此时某个包含此文件某block的数据结点掉线了,在它重新上线之后,它又带回这个已经无效的range id。所以需要timestamps,即creation time of the file,当两个文件碰巧有相同的range id时,根据timestamps来判定谁是最新的文件,旧的文件将被删除。[2]中能看到Doug Cutting和Sameer Paranjpye的一些其它討論,比如range-id也采用顺序生成(又回到随机VS顺序的问题上)。
[1] potential conflict in block id’s, leading to data corruption
https://issues.apache.org/jira/browse/HADOOP-146
[2] dfs should allocate a random blockid range to a file, then assign ids sequentially to blocks in the file
https://issues.apache.org/jira/browse/HADOOP-158
[3] Sequential generation of block ids
https://issues.apache.org/jira/browse/HDFS-898
2.4 数据校验
硬盘故障、网络错误或软件漏洞,都可能造成数据损坏。客户端创建文件时,会针对文件的每一个Block计算校验码,并把校验码存储在相同命名空间一个单独的因此文件中。当客户端读数据时,会使用这些校验码进行数据验证。如果校验失败,客户端会从其他副本重新拉取文件。
这一点上HDFS与GFS差异明显。G