全文没有代码,不要慌,主要是概念结合图片进行理解,觉得对你有用的话,坚持看完并提出建议。
The Hadoop Distributed File System (HDFS) is a distributed file system designed to run on commodity hardware.
从以上官方描述可以看出,HDFS 是 Hadoop 分布式文件系统,并且运行在普通硬件上。这就意味着 HDFS 不需要优秀的硬件资源、高昂的硬件成本,只需要简单的物理机组成分布式集群,HDFS 使用横向拓展(增加机器)来提高存储容量,而非纵向扩展(提高单个机器的配置)。
HDFS 并非唯一的分布式文件系统,还有 GFS、TFS 等,但 HDFS 是使用最多的开源分布式文件存储系统,具有高度容错(highly fault-tolerant)及低成本的特点。
接受硬件故障
HDFS 可以运行在成百上千台廉价的物理机上,存储着海量的数据,机器出故障是常见的一件事, 作为一个优秀的文件存储系统,HDFS 能够接受机器故障,它会进行故障检测以及恢复故障文件。
流式数据访问
运行在 HDFS 上的应用程序能够访问数据流,HDFS 主要用于批处理。
看到流数据访问时,卡了我很久,没明白流式数据、实时数据、流式计算、实时计算有什么区别,经过一番查阅,发现这些概念或许不应该放在一起作比较。
流式数据:理解为不是一次性加载完的数据,比如看电影,数据是一帧一帧过来的,动态的;
实时数据:实时产生的数据,和流式数据区别不大,有时候也会叫做实时流数据;
实时计算:处理实时数据,区别与离线计算(处理历史数据);
流式计算:这与实时计算不应该一起比较,实时计算强调的是数据的实时性,而流式计算强调的是计算方法,理解为 Java8 中的流式数据处理;
支持大数据集
HDFS 存储的典型文件是 GB 或 TB 大小,一个磁盘无法存储大文件,HDFS 将文件切分成小块,分别存储在不同服务器的磁盘上,通过网络进行连接。
简单的一致性模型(write-once-read-many)
HDFS 通常是一次写入,多次读取,不支持随机写操作,可以在文件末尾追加。这种方式简化了数据一致性问题。
移动计算比移动数据更划算
针对海量数据的处理,如果需要将数据移动到计算程序所在的节点,受网络的限制,计算将变得非常缓慢。HDFS 提供接口将计算程序移动到数据所在的位置,移动应用程序比移动海量数据效率高得多。
跨硬件和平台的可移植性
HDFS 易于从一个平台移植到另一个平台,这有助于 HDFS 成为大量应用程序的首选。HDFS 由 Java 编写,只要支持 Java 语言的机器便可以运行 HDFS NameNode 或 DataNode,这也是利用了 Java 的跨平台特性。
HDFS 是 master/slave 架构,在分布式中,一主多从的架构很常见。NameNode 主要存储和管理数据的元信息以及接受客户端的请求,DataNode 主要存储文件,所有的元信息都存储在一个 NameNode 节点上,这样极大地简化了架构的复杂性。
文件以 block(块)的形式存储在 DataNode 上,一个大文件存储到 HDFS 上时,会被切分成很多 block,这个过程是由 HDFS 自己完成的。单个 block 默认大小 128M,每个 block 默认备份三份,存储在不同的节点,冗余备份保证了 HDFS 文件的可靠性。
NameNode:
DataNode:
注意点
Blockreport
NameNode 会与 DataNode 之间会通过心跳机制进行通信,每个 DataNode 会定期向 NameNode 发送心跳以及 Blockreport ,Blockreport 上包含了该 DataNode 上的 block 列表,这种心跳机制也是 NameNode 检测 DataNode 是否存活的依据。
默认发送心跳的时间是 3 秒,默认判断 DataNode 是否存活的时间是 10 分钟,也就是 10 分钟接收不到该 DataNode 的心跳,则认为它已经宕机,不会再与该 DataNode 发送读写操作。
重写副本的触发条件:DataNode 节点不可用、某一个备份文件处于故障状态、DataNode 磁盘出现故障、备份数量发生改变。
一个文件的元信息如下图所示,包含了文件名,备份的份数,对应的 block id。
以文件 part-0 为例,备份数是 2,block id 是 1 和 3,在 Datanodes 中可以找到,id 为 1 的 block 有两个,id 为 3 的 block 有两个,分别存储在不同 Datanode。两个不同 block id 组合起来就是一个完整的名称为 part-0 的文件。
大型 HDFS 实例通常分布运行在由许多机架组成的集群中,一个机房中有很多机架,一个机架上有多个服务器,不同机架的机器通信需要经过交换机,受带宽等因素的影响,需要更高的网络通信成本。所以默认 3 个副本的情况下,采用如下的放置策略:
这样放置的好处:
从单个文件看来,考虑带宽似乎没有多大意义,但是对于大规模数据的情况下,请求并发量大时,网络是非常重要的一个因素,特别是对于写请求,这里要了解 HDFS 写的流程,先简单介绍写流程,后面会详细讲解。
因为写副本的过程类似于流水线,先写副本一,但这里写完后就将写成功的结果返回给客户端了。之后由副本一将内容写到副本二,接着由副本二将内容写到副本三。
假设副本三和副本一放置在一个机架上,那么就会产生两次不同机架间的写操作。而目前的情况是副本二和副本三在同一个机架,机架间的写操作只会发生在副本一到副本二之间,副本二和副本三的写操作是在同一个机架,节省了网络流量。
话不多说,用图说话:
了解 HDFS 默认三份备份后,会想到一个问题,NameNode 怎么知道 DataNode 在哪个机架呢?写文件时怎么能正确知道 DataNode 是否满足上面的备份策略呢?
Hadoop 组件有机架感知(Rack Awareness)功能,默认是关闭的,可以通过配置文件开启,在 core-site.xml
文件中有此配置项:net.topology.script.file.name
,以下是官方文档对该配置项的描述。
net.topology.script.file.name
The default implementation of the DNSToSwitchMapping. It invokes a script specified in net.topology.script.file.name to resolve node names. If the value for net.topology.script.file.name is not set, the default value of DEFAULT_RACK is returned for all node names.
该配置项的值是一个脚本的路径,当没有配置时,默认值为 DEFAULT_RACK,DEFAULT_RACK 就是将所有 DataNode 认为是一个机架,物理上它们可能是在不同机架。此时 HDFS 并不知道每个 DataNode 对应的真实 rack,就会将副本随机写到 DataNode 上,不一定满足上面提到的副本放置策略。
开启机架感知后,指定的脚本接受一个入参,DataNode 的 ip,计算完返回一个结果,DataNode 所在的机架 id,格式如下:/myrack/myhost
eg: /192.168.100.0/192.168.100.5
NameNode 在启动时,会判断该配置是否为空,如果不为空,说明开启了机架感知。NameNode 会根据配置找到该脚本,当接受到 DataNode 的心跳时,会运行该脚本,将其 ip 作为入参,将输出的结果作为该 DataNode 的 rack id,保存为一个 map 的形式存放在内存中。
开启机架感知后,NameNode 就能够正确识别每个 DataNode 所属的机架,能够轻松实现上述的副本存放策略。
为了最大的减少带宽和延迟,HDFS 读取文件采用就近原则,如果与客户端在同一机架上的 DataNode 上存有副本,则直接读取该副本。如果 HDFS 是跨数据中心的,则优先选择同一数据中心的副本。
对元数据的每一次更改都会记录在名为 EditLog 的文件中,该文件由 NameNode 维护,存储在 NameNode 节点的本地磁盘,比如在 HDFS 中新建一个文件、修改备份因子都会记录在 EditLog 文件中。
整个文件系统的信息,包括文件与 block 的映射和文件系统的属性,存储在一个名为 FsImage 的文件中,该文件也存储在 NameNode 的本地磁盘。
假设没有 EditLog,每次写操作对元数据进行了更改,都通过写 FsImage 的方式进行,那么必定会大大降低写操作效率。因为 FsImage 中存储的是 HDFS 文件系统所有的元数据信息,随着数据量增大,该文件也会增大,每次都对它进行写操作,耗时会很长,所以通过 EditLog 作为临时文件就解决了该问题,只需要定期将 EditLog 中的内容合并到 FsImage 即可。
如果对元数据的修改每次都以写磁盘上文件的方式进行,那必定会降低读写效率,NameNode 实际上将 FsImage 和 EditLog 中记录的元数据信息加载到内存中。
当 NameNode 启动时,会从磁盘读取 FsImage 文件将元信息加载到内存,再读取 EditLog 文件中的信息将元数据同步至最新状态,NameNode 只会在启动的时候合并 FsImage 和 EditLog 文件。
如果长时间没有重启 NameNode,EditLog 文件将会变得非常大,写数据将会越来越慢,对于高并发、数据量大的场景,写操作很慢肯定是不能容忍的。只有下一次重启 NameNode 时才会将 EditLog 合并到 FsImage,但生产环境是很少重启的,必须保证服务不间断,并且 EditLog 文件非常大的话,会导致 NameNode 重启时间变长。
那么这个问题如何解决呢?HDFS 引入了 SecondaryNameNode。
SecondaryNameNode 不是 NameNode 的备份,不是为了做高可用(HA)的。
checkpoint 是触发 FsImage 和 EditLog 文件进行合并的条件,形成新的 FsImage,也就是检查点。到达 checkpoint 时,会将 FsImage 和 EditLog 文件读取到内存,并通过 http 的方式发送给 SecondaryNamenode,由 SecondaryNamenode 完成合并,再发回给 NameNode。
checkpoint 的触发条件有两个:
指定时间间隔,通过 dfs.namenode.checkpoint.period
进行配置,默认是一小时;
指定 EditLog 文件大小,通过 dfs.namenode.checkpoint.txns
进行配置,默认是 1 百万条事务记录;
只要达到任何一个触发条件,就会将 EditLog 合并到 FsImage。
注:SecondaryNameNode 也会将 fsimage 等信息载入内存,上图把这一块省略了。
读操作(简略版)
读取 block 并不是一整块拿下来,读取文件都是以二进制流的方式,所以会先创建文件,再将数据内容追加写入文件。
写操作(简略版)
1、客户端发起写文件请求,会带上元数据信息;
2、NameNode 接受到请求后,会做一些校验工作,如文件是否存在、客户端是否有写权限等,并将写操作记录到 edits 文件中,如果写失败,比如断电了,edits 文件中还记录了上一次操作的信息,能够复原上一次操作;
3、NameNode 将返回每个 block 存放的 DataNode 列表;
4、客户端从 block 所属的 DataNode 列表中,假设备份 3 份,根据就近原则开始写操作,比如选择 DataNode1,在写的同时,DataNode1 会将文件信息传递给 DataNode2,DataNode2 接收到后再传递给 DataNode3,DataNode 接收到信息后,再依次返回确认信息,就像流水线一样,1 -> 2 -> 3,这个过程叫 Replication Pipelining。
5、DataNode 写完之后,会将结果返回给客户端,收到一个成功的结果,客户端就认为写操作已经完成了,剩余两个备份会异步进行。假设 2 -> 3 的过程中写失败了, 3 号机器宕机,2 号收不到成功确认 ack,则会告知 NameNode,NameNode 再重新指定一个 DataNode 进行写操作,1、2 随机选择一个作为写操作的发起端,保证最后是 3 份备份。
使用 Pipeline 的方式进行写操作,不需要客户端写三份备份,因为客户端写文件时是通过网络传输,所有备份由客户端写的话将严重影响写操作的速度。
NameNode 启动时,会从磁盘读取 FsImage 和 EditLog 文件至内存,然后等待 DataNode 发送 Blockreport,此时 NameNode 处于只读状态,这时不能进行写操作,这个过程 NameNode 处于安全模式。当 DataNode 将 block 的信息发送给 NameNode,大多数 block 处于可用状态时,NameNode 会自动退出安全模式。也可以通过 hdfs 命令行或 NameNode 页面对安全模式进行开关操作。
HDFS 中的文件写入只支持单个写入者,而且写操作总是以「只添加」方式在文件末尾写数据吗?
为什么HDFS中块(block)不能设置太大,也不能设置太小?
前面我们已经了解过,HDFS 读取数据时是读取 block,再将不同 block 组成完整的文件。
首先得了解寻址时间在这里是指 HDFS 找到目标文件块(block)所需要的时间,如果文件非常多,寻址时间就会更长,如果单个 block 非常大,网络传输的时间就会更长,得出以下结论:
因此,block 适当设置大一些,减少寻址时间,那么传输一个由多个 block 组成的文件的时间主要取决于磁盘的传输速率。
为什么HDFS文件块(block)大小设定为128M?
1、HDFS 中平均寻址时间大概为 10ms;
2、经过前人的大量测试发现,寻址时间为传输时间的 1% 时,为最佳状态;
所以最佳传输时间为 10ms/0.01=1000ms=1s
3、目前磁盘的传输速率普遍为 100MB/s
计算出最佳 block 大小:100MB/s * 1s = 100MB
所以设定 block 大小为 128MB(程序员的世界中,整数都是 2^n)
ps:实际在工业生产中,磁盘传输速率为 200MB/s 时,一般设定 block 大小为 256MB;磁盘传输速率为 400MB/s 时,一般设定 block 大小为 512M;
如果我要存的文件确实都是小文件,那如何处理呢?
存大量小文件也没有关系,因为即使有很多小文件,NameNode 的元数据信息也不会特别特别大,大多数情况都是能存储下来的,只是 HDFS 比较适合存储大文件。
如果某个文件没有达到 block 大小(默认 128M),那么会占用多少空间呢?
文件内容在 DataNode 上占用实际大小的空间。
为什么需要有 FsImage 和 EditLog 来存储元数据?
单独存在磁盘上?
存储在磁盘上的话会导致访问速度非常慢,每次请求文件时都需要访问 NameNode,如果存在磁盘上,NameNode 还需要从磁盘访问元数据信息,如果多个读操作同时请求,延迟将非常高。
单独存在内存中?
存在内存中显然不行,一方面是内存容量问题,尽管一个 block 元信息只占用约 150 byte,但是面对大量文件,内存容量会不够;其次,内存中无法持久化数据,如果机器宕机,内存数据将丢失,元信息都丢失,整个 HDFS 将无法使用。
存一部分内存中作为缓存,比如到了 100M 就刷到磁盘中?
数据分开存储存在的问题是如何保证一致性,并且缓存中的数据也存在丢失的情况,如果机器宕机,缓存中丢失的数据仍然无法恢复。
思考完这几个问题更能理解 HDFS 元数据的管理。
NameNode 维护了哪些信息?
NameNode 维护了两套数据,一个是文件目录与 block 数据块之间的关系,另一个是 block 与 datanode 节点之间的关系。
前一个数据关系是静态的,存放在磁盘上,通过 fsimage 和 edits 文件来维护,这里就涉及到元信息如何保存一致性的问题,就是使用这两个文件以及 SecondaryNameNode 实现的。
后一个数据关系是动态的,不会持久化到磁盘,每当集群启动的时候,会自动建立这些信息
NameNode 已经有 DataNode 的元信息,为什么 DataNode 还需要向 NameNode 定期汇报 block 信息?
因为 datanode 上的数据可能会被手动删除,比如手动去 datanode 上删除掉对应的 block,此时 namenode 中还有该元信息,误以为 block 还存在。
参考来源:
https://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-hdfs/HdfsDesign.html
https://blog.csdn.net/l1028386804/article/details/51935169
https://juejin.im/post/5d885bbcf265da039e12fa13
https://www.maiyewang.com/?p=21911