第三章 分布式文件系统HDFS
大数据要解决数据存储问题,所以有了分布式文件系统(DFS),但可能不符合当时的一些应用需求,于是谷歌公司开发了GFS(Google file System)。GFS是闭源的,而HDFS是对GFS的开源实现。
1.GFS和DFS有什么区别?
GFS(Google File System)和DFS(Distributed File System)都是分布式文件系统,但是它们有以下几个不同点:
1.设计目标不同:GFS是为了在大规模集群中处理大型文件而设计的,而DFS更侧重于在多个计算机之间共享和存储文件。
2.数据复制策略不同:GFS使用了一种称为“三副本策略”的数据复制策略,即将数据分成多个块,每个块都会被复制三次,并存储在不同的计算机上以提高可靠性。DFS使用了“副本策略”将文件分割成固定大小的块,每个块都有多个副本存储在不同的计算机上。
3.处理方式不同:GFS采用主从模式,其中有一个主节点负责协调所有数据读写操作,而DFS采用对等模式,即每个节点都可以进行读写操作。
4.应用场景不同:GFS适用于需要存储大型文件的应用程序,例如基于Web的应用程序或科学计算,而DFS更适合需要在多个计算机之间共享和访问文件的应用程序,例如云存储和日志收集等。
疑问:
2.GFS和HDFS区别是什么?
GFS(Google File System)和HDFS(Hadoop Distributed File System)都是分布式文件系统,但是它们有以下几个不同点:
1.设计目标不同:GFS是为了在大规模集群中处理大型文件而设计的,而HDFS是为了在分布式计算环境中存储和处理海量数据而设计的。
2.数据块大小不同:GFS将文件划分成固定大小的数据块(通常为64MB),而HDFS默认将文件划分成128MB大小的数据块,但可以通过配置进行调整。
3.元数据管理不同:GFS使用单独的主服务器来管理文件系统的元数据,而HDFS使用一个分布式的元数据管理器来协调不同计算节点之间的元数据信息。
4.处理方式不同:GFS采用主从模式,其中有一个主节点负责协调所有数据读写操作,而HDFS采用主从架构,其中有一个主节点(NameNode)负责协调客户端访问文件的元数据,另外还有多个数据节点(DataNode)用于存储实际的数据块。
5.副本策略不同:GFS使用了一种称为“三副本策略”的数据复制策略,即将数据分成多个块,每个块都会被复制三次,并存储在不同的计算机上以提高可靠性。HDFS默认采用“三副本策略”,但可以通过配置进行调整。
6.应用场景不同:GFS适用于需要存储大型文件的应用程序,例如基于Web的应用程序或科学计算,而HDFS更适合用于大数据处理和分析,例如日志处理、数据仓库和机器学习等。
3.为什么说HDFS是GFS的开源实现?
HDFS(Hadoop Distributed File System)被认为是GFS(Google File System)的开源实现,因为它基于GFS的设计思想和架构,在很多方面都与GFS非常相似,如数据划分、数据副本、元数据管理、主从架构等。
HDFS最初是由Apache Hadoop项目开发的,它旨在为Hadoop分布式计算平台提供高性能和可靠的数据存储服务。在设计HDFS时,Hadoop团队借鉴了GFS的思想,并在此基础上做出了一些改进和优化。因此,虽然HDFS并非直接从GFS衍生而来,但在很多方面与GFS非常相似,并且也被认为是一种开源实现的GFS。
尽管HDFS与GFS在一些方面存在差异,但其基于GFS的设计思想和架构,因此HDFS也是一种高性能、可靠、可扩展的分布式文件系统,广泛应用于大数据处理和分析领域。
3.1 分布式文件系统
分布式文件系统(Distributed File System,DFS)是一种计算机网络中的文件系统,它通过将文件存储在多个计算机节点上,以实现数据的可靠存储和高效访问。DFS通过将文件划分为多个数据块并在不同计算机节点上进行存储和复制,从而提高了数据的可靠性和可用性。
3.1.1 计算机集群结构
计算机集群是由多个计算机节点组成的计算系统,每个节点都可以独立地执行任务,并通过网络进行通信和协作。
所谓分布式文件系统就是把一个文件分成多块存在不同的电脑上面。这些电脑放一堆就成了一个计算机集群,不过这些电脑不是人用的,当然不需要显示器什么的外设,只由普通硬件构成,降低成本。
集群中的计算机节点存放在机架( Rack)上,每个架可以存放 8~64个节点,同一机架上的不同节点之间通过网络互连( 常采用吉比特以太网),多个不同机架之间采用另一级网络或交换机互连。(每个节点可视作一台计算机)
3.1.2 分布式文件系统的结构
- 块
Windows、Linux 等操作系统中,文件系统一般会把磁盘空间划分为每512Byte组,称为“磁盘块”,它是文件系统进行读写操作的最小单位,文件系统的块 (Block) 通常是碰盘块的整数倍,即每次读写的数据量必须是磁盘块大小的整数倍。
分布式文件系统也采用了块的概念, HDFS默认的一个块的大小是 64 MB。与普通文件不同的是,在分布式文件系统中,如果一个文件小于个数据块的大小,它并不占用整个数据块的存储空间。
- 节点
分布式文件系统在物理结构上是由计算机集群中的多个节点构成的
这些节点分为两类:一类叫“主节点”(Master Node),或者被称为“名称节点”(NameNode ); 另一类叫“从节点”(Slave Node),或者被称为“数据节点”( DataNode )。
名称节点:名称节点负责文件和目录的创建、删除和重命名等,同时管理着数据节点和文件块的映射关系,因此客户端只有访问名称节点才能找到请求的文件块所在的位置,进而到相应位置读取所需文件块。
数据节点:数据节点负责数据的存储和读取,在存储时,由名称节点分配存储位置,然后由客户端把数据直接写入相应数据节点;
在读取时,客户端从名称节点获得数据节点和文件块的映射关系,然后就可以到相应位置访问文件块。数据节点也要根据名称节点的命令创建、删除和复制数据块。
- 容错:
计算机集群中的节点可能发生故障,因此为了保证数据的完整性,分布式文件系统通常采用多副本存储。
文件块会被复制为多个副本,存储在不同的节点上,而且存储同一文件块的不同副本的各个节点会分布在不同的机架上。
这样,在单个节点出现故障时,就可以快速调用副本重启单个节点上的计算过程,而不用重启整个计算过程,整个机架出现故障时也不会丢失所有文件块,
注意:
文件块的大小和副本个数通常可以由用户指定。
分布式文件系统是针对大规模数据存储而设计的,主要用于处理大规模文件,如 TB 级文件处理规模过小的文件不仅无法充分发挥其优势,而且会严重影响系统的扩展和性能。
3.1.3 分布式文件系统的设计需求
分布式文件系统的设计需求主要包括透明性、并发控制、文件复制、硬件和操作系统的异松性、可伸缩性、容错以及安全需求等。但是,在具体实现中,不同产品实现的级别和方式都有不同。
设计需求
含义
HDFS的实现情况
透明性
具备访问透明性、位置透明性、性能和伸缩透明性。
访问透明性是指用户不需要专门区分哪些是本地文件,哪些是远程文件,用户能够通过相同的操作来访问本地文件和远程文件资源。
位置透明性是指在不改变路径名的前提下,不管文件副本数量和实际存储位置发生何种变化,对用户而言都是透明的,用户不会感受到这种变化,只需要使用相同的路径名就始终可以访问同一个文件。
性能和伸缩透明性是指系统中节点的增加或减少以及性能的变化对用户而言是透明的,用户感受不到什么时候一个节点加人或退出了
只能提供一定程度的访问透明性,完全支持位置透明性、性能和伸缩明性
并发控制
- 客户端对文件的读写不应该影响其他客户端对同一个文件的读写
机制非常简单,任何时间都只允许有一个程序写人某个文件
文件复制
一个文件可以拥有在不同位置的多个副本
HDFS 采用了多副本机制
硬件和操作系统的异构性
可以在不同的操作系统和计算机上实现同样的客户端和服务器端程序
采用 Java 语言开发,具有很好的跨平台能力
可伸缩性
支持节点的动态加人或退出
建立在大规模廉价机器上的分布式文件系统集群,具有很好的可伸缩性
容错
保证文件服务在客户端或者服务端出现问题的时候能正常使用
具有多副本机制和故障自动检测、恢复机制
安全
保障系统的安全性
安全性较弱
3.2 HDFS简介
HDFS开源实现了GFS 的基本思想。HDFS 原来是 Apache Nutch 搜索引擎的一部分,后来独立出来作为一个Apache子项目,并和 MapReduce 一起成为 Hadoop 的核心组成部分。HDFS 支持流读取和处理超大规模文件。并能够运行在由廉价的普通机器组成的集群上,这主要得益于HDFS 在设计之初就充分考虑了实际应用环境的特点,那就是,硬件出错在普通服务器集群中是一种常态,而不是异常。因此,HDFS 在设计上采取了多种机制保证在硬件出错的环境中实现数据的完整性。总体而言,HDFS 要实现以下目标。
- 兼容廉价的硬件设备
在成百上千台廉价服务器中存储数据,常会出现节点失效的情况,因此HDFS设计了快速检测硬件故障和进行自动恢复的机制,可以实现持续监视、错误检查、容错处理和自动恢复,从而在硬件出错的情况下也能实现数据的完整性。
- 流数据读写
普通文件系统主要用于随机读写以及与用户进行交互,HDFS则是为了满足批量数据处理的要求而设计的,因此为了提高数据吞吐率,HDFS 放松了一些POSIX 的要求,从而能够以流式方式来访问文件系统数据。
流式就是写的时候就存,充分利用资源,而不是写完了再存,比如ctrl+s手动存的时候就是写了一段后才存,没有存的时候资源就是闲置的。
- 大数据集。
HDFS中的文件通常可以达到GB甚至TB级别,一个数百台机器组成的集群可以支持千万级别这样的文件。
- 简单的文件模型。
HDFS采用了“一次写人、多次读取”的简单文件模型,文件一旦完成写入,关闭后就无法再次写入,只能被读取。
- 强大的跨平台兼容性
HDFS 是采用 Java 语言实现的,具有很好的跨平台兼容性,支持Java虚拟机(JavaVirtualMachine,JVM)的机器都可以运行HDFS。
HDFS局限:
- 不适合低延迟数据访问
HDFS 主要是面向大规模数据批量处理而设计的,采用流式数据读取,具有很高的数据吞吐率,但是,这也意味着较高的延迟。因此,HDFS 不适合用在需要较低延迟(如数十毫秒)的应用场合。对于低延时要求的应用程序而言,HBase 是一个更好的选择。
- 无法高效存储大量小文件
HDFS 无法高效存储和处理大量小文件,过多小文件会给系统扩展性和性能带来诸多问题。
首先,HDPS 采用名称节点来管理文件系统的元数据,这些元数据被保存在内存中,从而使客户端可以快速获取文件实际存储位置。
通常,每个文件、目录和块大约占150Byte,如果有 1000万个文件,每个文件对应一个块,那么,名称节点至少要消耗3 GB 的内存来保存这些元数据信息。
很显然,这时元数据检索的效率就比较低了,需要花费较多的时间找到一个文件的实际存储位置。
而且,如果继续扩展到数十亿个文件,名称节点保存元数据所需要的内存空间就会大大增加,以现有的硬件水平,是无法在内存中保存如此大量的元数据的。
其次,用 MapReduce 处理大量小文件时,会产生过多的Map 任务,进程管理开销会大大增加,因此处理大量小文件的速度远远低于处理同等规模的大文牛的速度。
再次,访问大量小文件的速度远远低于访问几个大文件的速度,因为访问大量小文件要不断从一个数据节点跳到另一个数据节点,严重影响性能。
就是文件太多的话,名称节点的压力很大,因为名称节点负责文件和目录的创建、删除和重命名等,同时管理着数据节点和文件块的映射关系,文件太多管理的东西越多,效率越低。
- 不支持多用户写人及任意修改文件
HDFS 只允许一个文件有一个写人者,不允许多个用户对同一个文件执行写操作,而且只允许对文件执行追加操作,不能执行随机写操作。
3.3 HDFS的相关概念
3.3.1 块
HDFS默认的块大小是64MB,而普通文件系统的块一般只有几千字节。HDFS的块之所以这么大,是为了节省寻址开销,在处理大规模文件时性能更好。不过块也不宜太大。
HDFS采用抽象的块概念可以带来以下几个明显的好处:
- 支持大规模文件存储
文件以块为单位进行存储,一个大规模文件可以被拆分成若干个文件块,不同的文件块可以被分发到不同的节点上,因此一个文件的大小不会受到单个节点的存储容量的限制,可以远远大于网络中任意节点的存储容量。
- 简化系统设计
首先,HDFS 采用块概念大大简化了存储管理,因为文件块大小是固定的这样就可以很容易计算出一个节点可以存储多少文件块;其次,这方便了元数据的管理,元数据不需要和文件块一起存储,可以由其他系统负责管理元数据。
- 适合数据备份
每个文件块都可以冗余存储到多个节点上,大大提高了系统的容错性和可用性。
3.3.2 名称节点和数据节点
1.名称节点
在EDFS中,名称节点负责管理分布式文件系统的命名空间(Namespace ),保存了两个核心的数据结构:即FsImage和 EditLog。
- FsImage
用于维护文件系统树以及文件树中所的文件和文件夹的元数据。
- EditLog
操作日志文件 EditLog 中记录了所有针对文件的创建、删除、重命名等操作
名称节点记录了每个文件中各个块所在的数据节点的位置信息,但是并不持久化地存储这些信息,而是在系统每次启动时扫描所有数据节点并重构,得到这些信息。
- FsImage和 EditLog作用过程:
名称节点在启动时,会将(磁盘中的)FsImage 的内容加载到内存当中,然后执行 EditLog 文件中的各项操作,使内存中的元数据保持最新。这个操作完成以后,就会创建一个新的 FsImage文件和一个空的EditLog 文件。
名称节点启动成功并进入正常运行状态以后,HDFS 中的更新操作都会被写人EdiLog,而不是直接被写人FsImage。这是因为对于分布式文件系统而言,FsImage文件通常都很庞大(一般都是GB级别以上)如果所有的更新操作都直接在 FsImage文件中进行,那么系统的运行速度会变得非常缓慢。相对而言,EditLog 通常都要远远小于 FsImage,更新操作写入EditLog是非常高效的。
名称节点在启动的过程中处于“安全模式”,只能对外提供读操作,无法提供写操作。启动过程结束后,系统就会退出安全模式,进人正常运行状态,对外提供读写操作。
2.数据节点
数据节点(DataNode)是分布式文件系统HDFS的工作节点,负责数据的存储和读取,会根据客户端或者名称节点的调度来进行数据的存储和检索,并且向名称节点定期发送自己所存储的块的列表信息。每个数据节点中的数据会被保存在各自节点的本地 Linux 文件系统中。
3.3.3 第二名称节点(SecondarNameNode)
在名称节点运行期间,HDFS会不断产生更新操作,这些更新操作直接被写人EditLog文件,因此 EditLog 文件也会逐渐变大。在名称节点运行期间,不断变大的 EditLog 文件通常对于系统性能不会产生显著影响,但是当名称节点重启时,需要将 FsImage 加载到内存中,然后逐条执行EditLog 中的记录,使FsImage 保持最新。可想而知,如果 EditLog很大,就会导致整个过程变得非常缓慢,使名称节点在启动过程中长期处于“安全模式”,无法正常对外提供写操作,影响用户的使用。
第二名称节点就用来解决这个问题。其功能有:
首先,它可以完成EditLog与FsImage 的合并操作,减小EditLog 文件大小,缩短名称节点重启时间
其次,它可以作为名称节点的“检查点”,保存名称节点中的元数据信息。具体如下。
EditLog与FsImage 的合并操作
每隔一段时间,第二名称节点会和名称节点通信,请求其停止使用 EditLog 文件,暂时将新到达的写操作添加到一个新的文件 EditLog.new 中。
- 然后,第二名称节点把名称节点中的 FsImage 文件和 EditLog文件拉回本地,再加载到内存中。
- 对二者执行合并操作,即在内存中逐条执行 EditLog 中的操作使FsImage 保持最新。
- 合并结束后,第二名称节点会把合并后得到的最新的FsImage.ckpt 文件发送到名称节点
名称节点收到后,会用最新的 FsImageckpt 文件去替换旧的FsImage文件,同时用EditLognew文件去替换 EditLog 文件,从而减小了 EditLog 文件的太小。
作为名称节点的“检查点”
从上面的合并过程可以看出,第二名称节点会定期和名称节点通信,从名称节点获取FsImage 文件和EditLog 文件,执行合并操作得到新的 FsImage.ckpt文件。
从这个角度来讲,第二名称节点相当于为名称节点设置了一个“检查点”,周期性地备份名称节点中的元数据信息,当名称节点发生故障时,就可以用第二名称节点中记录的元数据信息进行系统恢复。
但是,在第二名称节点上合并操作得到的新的 FsImage 文件是合并操作发生时HDFS记录的元数据信息,这个合并只是为了减小EditLog文件大小,合并完了之后,新的FsImage给到名称节点,使用EditLog.new把 FsImage更到最新,如果这个过程中名称节点故障,EditLog.new没了,那么在合并过程中的更新操作就没了,系统就丢失了部分元数据信息。
所以第二名称节点只能做”检查点“,不能做热备份使用。
3.4 HDFS体系结构
3.4.1 概述
HDFS采用了主从(Master/Slave)结构模型,一个HDFS集群包括一个名称节点和若干个数据节点(见图3-5)。
名称节点作为中心服务器,负责管理文件系统的命名空间及客户端对文件的访间。
集群中的数据节点一般是一个节点运行一个数据节点进程,负责处理文件系统客户端的读写请求,在名称节点的统一调度下进行数据块的创建、删除和复制等操作。每个数据节点的数据实际上是保存在本地 Linux 文件系统中的。每个数据节点会周期性地向名称节点发送“心跳”信息,报告自己的状态,没有按时发送心跳信息的数据节点会被标记为“死机”,不会再给它分配任何I/O请求。
- 用户在使用HDFS 时,仍然可以像在普通文件系统中那样,使用文件名去存储和访问文件。
实际上,在系统内部,一个文件会被切分成若干个数据块,这些数据块被分布存储到若干个数据节点上。
当客户端需要访问一个文件时,首先把文件名发送给名称节点,名称节点根据文件名找到对应的数据块(一个文件可能包括多个数据块)再根据每个数据块信息找到实际存储各个数据块的数据节点的位置,并把数据节点位置发送给客户端,最后客户端直接访问这些数据节点获取收据。
在整个访问过程中,名称节点并不参与数据的传输。这种设计方式,使得一个文件的数据能够在不同的数据节点上实现并发访问,大大提高数据访问速度。
- HDFS采用 Java语言开发,因此任何支持JVM的机器都可以部署名称节点和数据节点。
在实际部署时,通常在集群中选择一台性能较好的机器作为名称节点,其他机器作为数据节点。当然,一台机器可以运行任意多个数据节点,甚至名称节点和数据节点也可以放在一台机器上运行,不过,很少在正式部署中采用这种模式。HDFS 集群中只有唯一一个名称节点,该节点负责所有元数据的管理,这种设计大大简化了分布式文件系统的结构,可以保证数据不会脱离名称节点的控制,同时,用户数据也永远不会经过名称节点,这大大减轻了中心服务器的负担,方便了数据管理。
虽然在理论上,一台机器可以运行任意多个数据节点和名称节点,但是在实际的生产环境中,很少采用这种模式,原因如下:
1.难以扩展:采用一台机器运行多个数据节点和名称节点的方式,难以扩展。当需要增加节点时,需要增加更多的机器,而不是在现有的机器上添加新的节点。
2.容错性差:在一台机器上运行多个数据节点和名称节点时,如果机器出现故障,会影响所有的节点,导致数据丢失或不可用。
3.性能问题:当多个节点在同一台机器上运行时,它们会竞争同一个机器的资源,如 CPU、内存、磁盘带宽等,会导致性能问题。
因此,为了提高系统的可靠性、性能和可扩展性,通常在生产环境中采用分布式架构,即将名称节点和数据节点分别分布在不同的机器上运行。这样可以通过增加机器来扩展集群,提高容错性和性能,并实现更好的负载均衡。
3.4.2 HDFS命名空间
HDFS的命名空间包含目录、文件和块。命名空间管理是指命名空间支持对HDFS中的目录文件和块做类似文件系统的创建、修改、删除等基本操作。在当前的 HDFS 体系结构中,整个HDFS 集群只有一个命名空间,并且只有唯一一个名称节点,该节点负责对这个命名空间进行管理。
在HDFS中,命名空间是指文件和目录的逻辑结构。它类似于本地文件系统中的树形结构,其中每个节点都可以是一个目录或一个文件。在HDFS中,名称节点负责维护命名空间,它存储有关文件和目录的元数据,例如文件名、文件大小、创建时间、访问权限等。HDFS的客户端可以通过名称节点对命名空间进行操作,例如创建、删除、重命名文件和目录。命名空间在HDFS中是一个关键的概念,它为文件系统的整体组织和管理提供了基础。
为什么叫做”命名空间“?
简单理解下:某个由命名组成的集合,其中的命名遵循着给定的规则,比如”命名不能重复“等。
HDFS 使用的是传统的分级文件体系,因此用户可以像使用普通文件系统一样,创建、删除目录和文件,在目录间转移文件、重命名文件等。但是,HDFS 还没有实现磁盘配额和文件访问权限等功能,也不支持文件的硬连接和软连接(快捷方式)。
3.4.3 通信协议
HDFS 是一个部署在集群上的分布式文件系统,因此很多数据需要通过网络进行传输。所有的HDFS通信协议都是构建在TCP/IP基础之上的。客户端通过一个可配置的端口向名称节点主动发起 TCP 连接,并使用客户端协议与名称节点进行交互。
名称节点和数据节点之间则使用数据节点协议进行交互。
客户端与数据节点的交互通过远程过程调用(Remote ProcedureCaRPC)来实现。在设计上,名称书点不会主动发起 RPC,而是响应来自客户端和数据节点的RPC请求。
3.4.4 客户端
客户端是用户操作HDFS最常用的方式,HDFS在部署时都提供了客户端。不过需要说明是,严格来说,客户端并不算是HDFS的一部分。客户端可以支持打开、读取、写人等常见的操作,并且提供了类似 Shell 的命令行方式来访间 HDFS 中的数据。此外,HDFS也提供了JavaAPI,作为应用程序访问文件系统的客户端编接口。
3.4.5 HDFS体系结构的局限性
HDFS 只设置唯一一个名称节点,这样做虽然大大简化了系统设计,但也带来了一些明显局限性,具体如下。
- (1)命名空间的限制
名称节点是保存在内存中的,因此名称节点能够容纳对象(文件、块)的个数会受到内存空间大小的限制。
- (2)性能的瓶颈
整个分布式文件系统的吞吐量受限于单个名称节点的吞吐量。
- (3)隔离问题
由于集群中只有一个名称节点,只有一个命名空间,因此无法对不同应用程序进行隔离。
- (4)集群的可用性。
一旦这个唯一的名称节点发生故障,会导致整个集群变得不可用。
3.5 HDFS的存储原理
3.5.1 数据的冗余存储
HDFS 采用了多副本方式对数据进行冗余存储,通常一个数据块的多个副本会被分布到不同的数据节点上。这种多副本方式具有以下3个优点:
- 加快数据传输速度。
当多个客户端需要同时访间同一个文件时,可以让各个客户端分别从不同的数据块副本中读取数据,这就大大加快了数据传输速度。
- 容易检查数据错误
HDFS 的数据节点之间通过网络传输数据,采用多个副本可以很容易判断数据传输是否出错。
- 保证数据的可靠性
即使某个数据节点出现故障失效,也不会造成数据丢失。
3.5.2 数据存取策略
数据存取策略包括数据存放、数据读取和数据复制等方面,它在很大程度上会影响到整个分布式文件系统的读写性能,是分布式文件系统的核心内容。
数据存放
HDFS采用了以机架( Rack)为基础的数据存放策略。一个HDFS集群通常包含多个机架,不同机架之间的数据通信需要经过交换机或者路由器,同一个机架中不同机器之间的通信则不需要经过交换机和路由器,这意味着同一个机架中不同机器之间的通信要比不同机架之间机器的通信带宽大。
HDFS 默认每个数据节点都在不同的机架上,这种方法会存在一个缺点,那就是写入数据的时候不能充分利用同一机架内部机器之间的带宽。
但是,与这个缺点相比,这种方法也带来了更多很显著的优点:首先,可以获得很高的数据可靠性,即使一个机架发生故障,位于其他机架上的数据副本仍然是可用的;
其次,可以在多个机架上并行读取数据,大大提高数据读取速度,最后,可以更容易地实现系统内部负载均衡和错误处理。
HDFS 默认的冗余复制因子是 3,每一个文件块会被同时保存到3个地方,其中,有两个副本放在同一个机架的不同机器上面,第3个副本放在不同机架的机器上面,这样既可以保证机架发生异常时的数据恢复,也可以提高数据读写性能。
一个文件切成三块,每块保存在三台计算机中,两台计算机在同一支架,一台计算机在不同机架。
如果是在集群内发起写操作请求,则把第1个副本放置在发起写操作请求的数据节点上实现就近写人数据。如果是在集群外发起写操作请求,则从集群内部挑选一台磁盘空间较为充足CPU不太忙的数据节点,作为第1个副本的存放地。
第2个副本会被放置在与第1个副本不同的机架的数据节点上
第3个副本会被放置在与第1个副本相同的机架的其他节点上。
如果还有更多的副本,则继续从集群中随机选择数据节点进行存放
数据读取
HDFS 提供了一个API可以确定一个数据节点所属的机架ID,客户端也可以调用API取自己所属机架的ID。
当客户端读取数据时,从名称节点获得数据块不同副本的存放位置列表,列表中包含了副本所在的数据书点,可以调用API来确定客户端和这些数据节点所属的机架 ID。
当发现某个数据块副本对应的机架ID和客户端对应的机架ID相同时,就优先选择该副本读取数据。
如果没有发现,就随机选择一个副本读取数据。
3.数据复制
HDFS 的数据复制采用了流水线复制的策略,大大提高了数据复制过程的效率。
当客户端要往HDFS 中写入一个文件时,这个文件会首先被写入本地,并被切分成若干个块,每个块的大小是由HDFS的设定值来决定的。
每个块都向 HDFS 集群中的名称节点发起写请求,名称节点会据系统中各个数据节点的使用情况,选择一个数据节点列表返回给客户端,
然后客户端就把数据首先写入列表中的第1个数据节点,同时把列表传给第1个数据节点,
当第1个数据节点接收到4KB数据的时候,写人本地,并且向列表中的第2个数据节点发起连接请求,把自己已经接收到的4KB数据和列表传给第2个数据节点。
当第2个数据节点接收到4KB数据的时候写入本地并且向列表中的第3个数据节点发起连接请求,
依此类推,列表中的多个数据节点形成一条数据复制的流水线。最后,当文件写完的时候,数据复制也同时完成。
处理数据与传输数据并行处理,因此提高了复制的效率
3.5.3 数据错误与恢复
HDFS 具有较高的容错性,可以兼容廉价的硬件设备,它把硬件出错看成一种常态,而不是异常,并设计了相应的机制检测数据错误和进行自动恢复,主要包括以下3种情形:
名称节点出错
名称节点保存了所有的元数据信息,其中最核心的两大文件是FsImage 和Editog,如果这两个文件发生损坏,那么整个HDFS实例将失效。
Hadoop 采用两种机制来确保名称节点的安全:
一是把名称节点上的元数据信息同步存储到其他文件系统,比如远程挂载的网络文件系统(NetwonFile System,NFS)中;
二是运行个第二名称节点,当名称节点死机以后,可以把运行第二称节点作为一种弥补措施,利用第二名称节点中的元数据信息进行系统恢复,但是从前面对第名称节点的介绍中可以看出,这样做仍然会丢失部分数据。
因此,一般会把上述两种方式结合用,当名称节点发生死机时,首先到远程挂载的网络文件系统中获取备份的元数据信息,放到第二名称节点上进行恢复,并把第二名称节点作为名称节点来使用。
疑问:
- 为什么还要将NFS中拿到的文件和第二名称节点上恢复?直接使用NFS中的备份元数据替换给新的名称节点不行吗?
使用NFS中的备份元数据替换新的名称节点可能会导致数据的不一致性,因为在名称节点崩溃之前,存在一些数据更新的操作还没有被同步到NFS中。如果直接使用NFS中的备份元数据,这些未同步的数据更新就会丢失,导致数据的不一致性。
为了避免这种情况,Hadoop会将从NFS中获取的备份元数据与第二名称节点上的元数据进行合并和比较,找出数据更新操作的差异,并将这些操作重新应用到第二名称节点上,以确保数据的一致性和完整性。然后再将更新后的元数据用于恢复整个系统。
- 备份数据和第二名称节点恢复后的数据一定是最新的,无缺失的吗?
不一定。备份数据和第二名称节点恢复后的数据不一定是最新的,可能存在一定程度的数据缺失。这是因为在名称节点故障的时间段内,可能还有新的数据写入,这些数据不会被备份到网络文件系统中或者第二名称节点中。因此,如果使用备份数据或者第二名称节点中的数据来替换名称节点,可能会导致部分数据的丢失。但是,这种丢失通常是可接受的,因为HDFS本身就具有数据冗余机制,即在数据节点之间进行数据备份和复制,从而保证数据的可靠性和容错性。
即:使用NFS与第二名称节点恢复而不是直接使用NFS的备份数据,是为了确保要使用比较新的数据进行恢复,因为二者都存了数据,对比一下时间戳什么的就可以确定哪一个数据是比较新的。用来恢复名称节点。当然再怎么样也有可能缺失,这就需要数据冗余机制出手了。
2.数据节点出错
每个数据节点会定期向名称节点发送“心跳”信息,向名称节点报告自己的状态。
当数据节点发生故障,或者网络发生断网时,名称节点就无法收到来自一些数据节点的“心跳”信息,这时这些数据节点就会被标记为“死机”,节点上面的所有数据都会被标记为“不可读”,名称节点也不会再给它们发送任何I/O请求。
这时,有可能出现一种情形,即由于一些数据节点的不可用会导致一些数据块的副本数量小于冗余因子。名称节点会定期检查这种情况,一旦发现某个数块的副本数量小于冗余因子,就会启动数据冗余复制,为它生成新的副本。
HDFS 与其他分布式文件系统的最大区别就是可以调整冗余数据的位置。
3.数据出错
网络传输和磁盘错误等因素都会造成数据错误。
客户端在读取数据后,会采用MD5和SHA-1对数据块进行校验,以确定读取正确的数据。
在文件被创建时,客户端就会对每一个文件块进行信息摘录,并把这些信息写入同一个路径的隐藏文件里面。
当客户端读取文件的时候,会先读取该信息文件,然后利用该信息文件对每个读取的数据块进行校验。
如果校验出错,客户端就会请求到另外一个数据节点读取该文件块,并且向名称节点报告这个文件有错误,名称节点会定期检查并且重新复制这个块。
3.6 HDFS的数据读写过程
在介绍HDFS的数据读写过程之前,需要简单介绍一下相关的类。
FileSystem是一个通用文件系统的抽象基类,可以被分布式文件系统继承,所有可能使用 Hadoop 文件系统的代码都要使用到这个类。
Hadoop为FileSystem 这个抽象类提供了多种具体的实现,DistributedFileSystem 就是FileSystem在HDFS中的实现。
FileSystem的open()方法返回的是一个输人流FSDataInputStream对象,在HDFS中具体的输人流就是DFSInputStream;
FileSystem 中的create()方法返回的是一个输出流FSDataOutputStream对象,在HDFS中具体的输出流就是DFSOutputStream。
3.6.1 读数据的过程
客户端连续调用open()、read()、close()读取数据时,HDFS内部的执行过程如下:
- 客户端通过 FileSystem.open()打开文件,相应地,在HDFS 中 DistributedFileSystem 具体实现了FileSystem。因此,调用open()方法后,DistributedFileSystem 会创建输人流FSDataInputStream,对于HDFS而言,具体的输人流就是DFSInputStream。
- 在 DFSInputStream的构造函数中,输入流通过 ClientProtocal.getBlockLocations()远程调用名称节点,获得文件开始部分数据块的保存位置。
对于该数据块,名称节点返回保存该数据块的所有数据节点的地址,同时根据距离客户端的远近对数据节点进行排序;
然后,DistributedFileSystem会利用DFSInputStream来实例化FSDatalnputStream,并返回给客户端,同时返回数据块的数据节点地址。
3.获得输入流FSDatanputStream后,客户端调用read()方法开始读取数据。
输人流根据前面的排序结果,选择距离客户端最近的数据节点建立连接并读取数据。
4.数据从该数据节点读到客户端;当该数据块读取完毕时,FSDataInputStream 关闭和该数
据节点的连接。
5.输入流通过 getBlockLocations()方法查找下一个数据块(如果客户端缓存中已经包含了该数据块的位置信息,就不需要调用该方法)。
6.找到该数据块的最佳数据节点,读取数据。
7.当客户端读取完数据的时候,调用FSDataputStream 的 close()方法,关闭输入流需要注意的是,在读取数据的过程中,如果客户端与数据节点通信时出现错误,就会尝试连接包含此数据块的下一个数据节点。
这个过程可以类比成一个人去取快递的过程:
1.客户端打开文件就像一个人到快递站取快递,而DistributedFileSystem则相当于快递站。
2.DFSInputStream构造函数中的远程调用名称节点相当于快递站工作人员帮忙查看快递信息,返回存放快递的位置和取快递的顺序,客户端需要按照顺序依次取快递。
3.客户端调用read()方法开始读取数据,就像一个人从柜子里取快递,根据取快递的顺序,先取最前面的那个快递。
4.当一个快递被取出后,与该快递对应的柜子就关闭了,就像客户端读取完一个数据块的数据后,与该数据块对应的数据节点连接也会关闭。
5.输入流通过getBlockLocations()方法查找下一个数据块,就相当于客户端去取下一个柜子的快递,如果之前已经查过该柜子,就可以直接去那个柜子取快递。
6.找到下一个数据块后,读取数据,就像一个人到下一个柜子取快递
7.客户端读取完所有数据后,调用close()方法关闭输入流,就像一个人取完所有快递后离开快递站。需要注意的是,在读取数据的过程中,如果出现错误就会尝试连接下一个数据节点,就像如果快递柜子坏了,就会去找下一个柜子取快递。
3.6.2 写数据的过程
客户端向HDFS 写数据是一个复杂的过程,这里介绍一下在不发生任何异常的情况下,客户端连续调用create0write0和close0时,HDFS内部的执行过程:
1.客户端通过 FileSystem.create()创建文件,相应地,在 HDFS 中 DistributedFileSystem 具体实现了FileSystem。
因此,调用create()方法后,DistributedFileSystem 会创建输出流 FSDataOutputStream。
对于HDFS而言,具体的输出流就是DFSOutputStream。
2.然后,DistributedFileSystem 通过 RPC 远程调用名称节点,在文件系统的命名空间中创建一新的文件。
名称节点会执行一些检查,比如文件是否已经存在,客户端是否有权限创建文件等。
检查通过之后,名称节点会构造一个新文件,并添加文件信息。
远程方法调用结束后,DistributedFileSystem会利用DFSOutputStream 来实例化FSDataOutputStream,并返回给客户端,客户端使用这个输出流写人数据。
3.获得输出流FSDataOutputStream以后,客户端调用输出流的 write()方法向HDFS中对应的文件写人数据。
4.客户端向输出流FSDataOutputStream中写人的数据会首先被分成一个个的分包,这些分包被放入DFSOutputStream对象的内部队列。
输出流FSDatautputStream会向名称节点申请保存文件和副本数据块的若干个数据节点,这些数据节点形成一个数据流管道。
队列中的分包最后被打包成数据包,发往数据流管道中的第1个数据节点,第1个数据节点将数据包发送给第 2个数据节点,第2个数据节点将数据包发送给第3 个数据节点,这样,数据包会流经管道上的各个数据节点(即3.5.2节介绍的流水线复制策略)。
5.因为各个数据节点位于不同的机器上,数据需要通过网络发送。
因此,为了保证所有数据节点的数据都是准确的,接收到数据的数据节点要向发送者发送“确认包”(ACK Packet)。
确认包沿着数据流管道逆流而上,从数据流管道依次经过各个数据节点并最终发往客户端,当客户端收到应答时,它将对应的分包从内部队列移除。不断执行(3)~(5步,直到数据全部写完。
6.客户端调用 close()方法关闭输出流,此时开始,客户端不会再向输出流中写入数据,所以,当DFSOutputStream 对象内部队列中的分包都收到应答以后,就可以使用 ClintProtocol.complete()方法通知名称节点关闭文件,完成一次正常的写文件过程。
下面用购物送货的例子来解释这个过程:
1.客户下单购买商品,这就相当于客户端通过 FileSystem.create() 创建了一个文件。
2.商家收到订单后,在自己的仓库中创建了一份新的购物清单(相当于名称节点创建了新的文件),并进行了一系列的检查(比如库存是否充足,客户是否有购买权限等),确认无误后将清单发往物流中心(相当于通过 RPC 远程调用名称节点)。
3.物流中心收到清单后,开始安排货车(相当于 DistributedFileSystem 通过 DFSOutputStream 来实例化 FSDataOutputStream),并将清单发往仓库(相当于客户端使用 FSDataOutputStream.write() 向 HDFS 中对应的文件写入数据)。
4.仓库将商品按照清单进行打包,并将各个包裹交给货车(相当于输出流 FSDataOutputStream 向名称节点申请保存文件和副本数据块的若干个数据节点,这些数据节点形成一个数据流管道)。
5.货车在行驶的过程中,需要经过各个中转站,将包裹依次交给下一个中转站(相当于数据包经过管道上的各个数据节点)。每个中转站都需要确认包裹是否正确,并将其交给下一个中转站(相当于确认包 ACK Packet 沿着数据流管道逆流而上,从数据流管道依次经过各个数据节点并最终发往客户端)。
当货车到达目的地后,送货员开始将包裹逐一送到客户手中(相当于客户端使用 FSDataOutputStream.close() 方法关闭输出流,此时开始,客户端不会再向输出流中写入数据,所以
6.当 DFSOutputStream 对象内部队列中的分包都收到应答以后,就可以通知名称节点关闭文件,完成一次正常的写文件过程)。
后续为上机实验,略。
版权归原作者 m0_62096596 所有, 如有侵权,请联系我们删除。