数据节点:一般是一个节点运行一个数据节点进程,负责处理文件系统客户端的读/写请求,在名称节点的统一调度下进行数据块的创建、删除和复制等操作。每个数据节点的数据实际上是保存在本地 Linux 文件系统中的。每个数据节点会周期性地向名称节点发送“心跳”信息,报告自己的状态,没有按时发送心跳信息的数据节点会被标记为“宕机”,不会再给它分配任何 I/O 请求。
HDFS 提供了一个 API 可以确定一个数据节点所属的机架 ID,客户端也可以调用 API 获取自己所属的机架 ID。当客户端读取数据时,从名称节点获得数据块不同副本的存放位置列表,列表中包含了副本所在的数据节点,可以调用 API 来确定客户端和这些数据节点所属的机架 ID。当发现某个数据块副本对应的机架 ID 和客户端对应的机架 ID 相同时,就优先选择该副本读取数据,如果没有发现,就随机选择一个副本读取数据。
大数据时代必须解决海量数据的高效存储问题,为此,谷歌开发了分布式文件系统(Google File System,GFS),通过网络实现文件在多台机器上的分布式存储,较好地满足了大规模数据存储的需求。Hadoop 分布式文件系统(Hadoop Distributed File System,HDFS)是针对 GFS 的开源实现,它是 Hadoop 两大核心组成部分之一,提供了在廉价服务器集群中进行大规模分布式文件存储的能力。HDFS 具有很好的容错能力,并且兼容廉价的硬件设备,因此可以以较低的成本利用现有机器实现大流量和大数据量的读写。
计算机集群架构
普通的文件系统只需要单个计算机节点就可以完成文件的存储和处理,单个计算机节点由处理器、内存、高速缓存和本地磁盘构成。 分布式文件系统把文件分布存储到多个计算机节点上,成千上万的计算机节点构成计算机集群。与之前使用多个处理器和专用高级硬件的并行化处理装置不同的是,目前的分布式文件系统所采用的计算机集群都是由普通硬件构成的,这就大大降低了硬件上的开销
分布式文件系统的结构
与普通文件系统类似,分布式文件系统也采用了块的概念,文件被分成若干个块进行存储, 块是数据读写的基本单元,只不过分布式文件系统的块要比操作系统中的块大很多。比如,HDFS 默认的一个块的大小是 64 MB。与普通文件不同的是,在分布式文件系统中,如果一个文件小于 一个数据块的大小,它并不占用整个数据块的存储空间。
分布式文件系统在物理结构上是由计算机集群中的多个节点构成的,这些节点分为两类:一类叫“主节点”(Master Node),或者也被称为“名称节点”(NameNode);另一类叫“从节点”(Slave Node),或者也被称为“数据节点”(DataNode)
名称节点:负责文件和目录的创建、删除和重命名等,同时管理着数据节点和文件块的映射关系,因此客户端只有访问名称节点才能找到请求的文件块所在的位置,进而到相应位置读取所需文件块。
数据节点:负责数据的存储和读取,在存储时,由名称节点分配存储位置,然后由客户端把数据直接写入相应数据节点;在读取时,客户端从名称节点获得数据节点和文件块的映射关系,然后就可以到相应位置访问文件块。数据节点也要根据名称节点的命令创建、删除数据块和冗余复制。
多副本缓存:计算机集群中的节点可能发生故障,因此为了保证数据的完整性,分布式文件系统通常采用多副本存储。文件块会被复制为多个副本,存储在不同的节点上,而且存储同一文件块的不同副本的各个节点会分布在不同的机架上,这样,在单个节点出现故障时,就可以快速调用副本重启单个节点上的计算过程,而不用重启整个计算过程,整个机架出现故障时也不会丢失所有文件块。文件块的大小和副本个数通常可以由用户指定。
分布式系统的设计需求
HDFS简介
HDFS是开源实现了 GFS 的基本思想,HDFS 在设计上采取了多种机制保证在硬件出错的环境中实现数据的完整性
局限性:
HDFS相关概念
1、块
HDFS 也同样采用了块的概念,默认的一个块大小是 64 MB。在 HDFS 中的文件会被拆分成多个块,每个块作为独立的单元进行存储,HDFS 在块的大小的设计上明显要大于普通文件系统
为什么设计块的大小比较大?
为什么块的大小不易设置过大?
使用块带来的好处?
2、名称节点(NameNode)
负责管理分布式文件系统的命名空间(Namespace),保存了两个核心的数据结构即 FsImage 和 EditLog。FsImage 用于维护文件系统树以及文件树中所有的文件和文件夹的元数据,操作日志文件 EditLog 中记录了所有针对文件的创建、删除、重命名等操作。名称节点记录了每个文件中各个块所在的数据节点的位置信息,但是并不持久化存储这些信息,而是在系统每次启动时扫描所有数据节点重构得到这些信息。
名称节点在启动时,会将 FsImage 的内容加载到内存当中,然后执行 EditLog 文件中的各项操作,使得内存中的元数据保持最新。这个操作完成以后,就会创建一个新的 FsImage 文件和一个空的 EditLog 文件。名称节点启动成功并进入正常运行状态以后,HDFS 中的更新操作都会被写入到 EditLog,而不是直接写入 FsImage,这是因为对于分布式文件系统而言,FsImage文件通常都很庞大(一般都是 GB 级别以上),如果所有的更新操作都直接往 FsImage 文件中添加,那么系统就会变得非常缓慢。相对而言,EditLog 通常都要远远小于 FsImage,更新操作写 入到 EditLog 是非常高效的。名称节点在启动的过程中处于“安全模式”,只能对外提供读操作,无法提供写操作。启动过程结束后,系统就会退出安全模式,进入正常运行状态,对外提供读写操作。
3、数据节点
数据节点(DataNode)是分布式文件系统 HDFS 的工作节点,负责数据的存储和读取,会根据客户端或者名称节点的调度来进行数据的存储和检索,并且向名称节点定期发送自己所存储的块的列表。每个数据节点中的数据会被保存在各自节点的本地 Linux 文件系统中。
4、第二名称节点
问题:在名称节点运行期间,HDFS 会不断发生更新操作,这些更新操作都是直接被写入到 EditLog文件,因此 EditLog 文件也会逐渐变大。在名称节点运行期间,不断变大的 EditLog 文件通常对于系统性能不会产生显著影响,但是当名称节点重启时,需要将 FsImage 加载到内存中,然后逐条执行 EditLog 中的记录,使得 FsImage 保持最新。可想而知,如果 EditLog 很大,就会导致整个过程变得非常缓慢,使得名称节点在启动过程中长期处于“安全模式”,无法正常对外提供写操作,影响了用户的使用。
为了有效解决 EditLog 逐渐变大带来的问题,HDFS 在设计中采用了第二名称节点(Secondary NameNode)。第二名称节点是 HDFS 架构的一个重要组成部分,具有两个方面的功能:
(1)EditLog 与 FsImage 的合并操作。每隔一段时间,第二名称节点会和名称节点通信,请求其停止使用 EditLog 文件(这里假设这个时刻为 t1),如图 3-4 所示,暂时将新到达的写操作添加到一个新的文件 EditLog.new 中。然后,第二名称节点把名称节点中的 FsImage 文件和EditLog 文件拉回到本地,再加载到内存中;对二者执行合并操作,即在内存中逐条执行 EditLog中的操作,使得 FsImage 保持最新。合并结束后,第二名称节点会把合并后得到的最新的 FsImage文件发送到名称节点。名称节点收到后,会用最新的 FsImage 文件去替换旧的 FsImage 文件,同时用 EditLog.new 文件去替换 EditLog 文件(这里假设这个时刻为 t2),从而减小了 EditLog文件的大小。
(2)作为名称节点的“检查点”。从上面的合并过程可以看出,第二名称节点会定期和名称节点通信,从名称节点获取 FsImage 文件和 EditLog 文件,执行合并操作得到新的FsImage 文件。从这个角度来讲,第二名称节点相当于为名称节点设置了一个“检查点”,周期性地备份名称节点中的元数据信息,当名称节点发生故障时,就可以用第二名称节点中记录的元数据信息进行系统恢复。但是,在第二名称节点上合并操作得到的新的 FsImage 文件是合并操作发生时(即 t1 时刻)HDFS 记录的元数据信息,并没有包含 t1 时刻和 t2 时刻期间发生的更新操作,如果名称节点在 t1 时刻和 t2 时刻期间发生故障,系统就会丢失部分元数据信息,在 HDFS 的设计中,也并不支持把系统直接切换到第二名称节点,因此从这个角度来讲,第二名称节点只是起到了名称节点的“检查点”作用,并不能起到“热备份”作用。即使有了第二名称节点的存在,当名称节点发生故障时,系统还是有可能会丢失部分元数据信息的。
HDFS体系结构
名称节点:名称节点作为中心服务器,负责管理文件系统的命名空间及客户端对文件的访问。
数据节点:一般是一个节点运行一个数据节点进程,负责处理文件系统客户端的读/写请求,在名称节点的统一调度下进行数据块的创建、删除和复制等操作。每个数据节点的数据实际上是保存在本地 Linux 文件系统中的。每个数据节点会周期性地向名称节点发送“心跳”信息,报告自己的状态,没有按时发送心跳信息的数据节点会被标记为“宕机”,不会再给它分配任何 I/O 请求。
客户端访问文件的过程:
当客户端需要访问一个文件时,首先把文件名发送给名称节点,名称节点根据文件名找 到对应的数据块(一个文件可能包括多个数据块),再根据每个数据块信息找到实际存储各个数据 块的数据节点的位置,并把数据节点位置发送给客户端,最后客户端直接访问这些数据节点获取 数据。在整个访问过程中,名称节点并不参与数据的传输。这种设计方式,使得一个文件的数据 能够在不同的数据节点上实现并发访问,大大提高了数据访问速度。
HDFS 集群中只有唯一一个名称节点,该节点负责所有元数据的管理,这种设计大大简化了分布式文件系统的结构,可以保证数据不会脱离名称节点的控制,同时,用户数据也永远不会经过名称节点,这大大减轻了中心服务器的负担,方便了数据管理。
1、 HDFS命名空间管理
HDFS 的命名空间包含目录、文件和块。命名空间管理是指命名空间支持对 HDFS 中的目录、文件和块做类似文件系统的创建、修改、删除等基本操作,因为在HDFS中只存在一个名称节点,该节点负责对这个命名空间进行 管理,那么说整个HDFS 集群中只有一个命名空间
2、通信协议
HDFS 是一个部署在集群上的分布式文件系统,因此很多数据需要通过网络进行传输。所有的 HDFS 通信协议都是构建在 TCP/IP 协议基础之上的。客户端通过一个可配置的端口向名称节点主动发起 TCP 连接,并使用客户端协议与名称节点进行交互。名称节点和数据节点之间则使用数据节点协议进行交互。客户端与数据节点的交互是通过 RPC(Remote Procedure Call)来实现的。在设计上,名称节点不会主动发起 RPC,而是响应来自客户端和数据节点的RPC 请求。
3、客户端
严格来说客户端并不是HDFS中的一部分,客户端支持支持打开、读取、写入等常见的操作,并且提供了类似 Shell 的命令行方式来访问 HDFS 中的数据(参见第 3.7.1 小节)。此外,HDFS也提供了 Java API,作为应用程序访问文件系统的客户端编程接口
4、局限性
HDFS 只设置唯一一个名称节点,这样做虽然大大简化了系统设计,但也带来了一些明显的局限性,具体如下。
HDFS数据存储原理
作为一个分布式文件系统,为了保证系统的容错性和可用性,HDFS 采用了多副本方式对数据进行冗余存储,通常一个数据块的多个副本会被分布到不同的数据节点上
加快数据传输速度。当多个客户端需要同时访问同一个文件时,可以让各个客户端分别 从不同的数据块副本中读取数据,这就大大加快了数据传输速度。
容易检查数据错误。HDFS 的数据节点之间通过网络传输数据,采用多个副本可以很容 易判断数据传输是否出错。
保证数据的可靠性。即使某个数据节点出现故障失效,也不会造成数据丢失
1、数据存储
为了提高数据的可靠性与系统的可用性,以及充分利用网络带宽,HDFS 采用了以机架(Rack) 为基础的数据存放策略。一个 HDFS 集群通常包含多个机架,不同机架之间的数据通信需要经过 交换机或者路由器,同一个机架中不同机器之间的通信则不需要经过交换机和路由器,这意味着 同一个机架中不同机器之间的通信要比不同机架之间机器的通信带宽大。
HDFS 默认每个数据节点都是在不同的机架上,这种方法会存在一个缺点,那就是写入 数据的时候不能充分利用同一机架内部机器之间的带宽。但是,与这点缺点相比,这种方法 也带来了更多很显著的优点:首先,可以获得很高的数据可靠性,即使一个机架发生故障, 位于其他机架上的数据副本仍然是可用的;其次,在读取数据的时候,可以在多个机架上并 行读取数据,大大提高了数据读取速度;最后,可以更容易地实现系统内部负载均衡和错误 处理。
HDFS 默认的冗余复制因子是 3,每一个文件块会被同时保存到 3 个地方,其中,有两份副 本放在同一个机架的不同机器上面,第三个副本放在不同机架的机器上面,这样既可以保证机 架发生异常时的数据恢复,也可以提高数据读写性能。一般而言,HDFS 副本的放置策略如下
如果是在集群内发起写操作请求,则把第一个副本放置在发起写操作请求的数据节点上, 实现就近写入数据。如果是来自集群外部的写操作请求,则从集群内部挑选一台磁盘不太满、CPU 不太忙的数据节点,作为第一个副本的存放地。
第二个副本会被放置在与第一个副本不同的机架的数据节点上。
第三个副本会被放置在与第一个副本相同的机架的其他节点上。
如果还有更多的副本,则继续从集群中随机选择数据节点进行存放。
2、数据读取
HDFS 提供了一个 API 可以确定一个数据节点所属的机架 ID,客户端也可以调用 API 获取自己所属的机架 ID。当客户端读取数据时,从名称节点获得数据块不同副本的存放位置列表,列表中包含了副本所在的数据节点,可以调用 API 来确定客户端和这些数据节点所属的机架 ID。当发现某个数据块副本对应的机架 ID 和客户端对应的机架 ID 相同时,就优先选择该副本读取数据,如果没有发现,就随机选择一个副本读取数据。
3、数据复制
HDFS 的数据复制采用了流水线复制的策略
当客户端要往 HDFS 中写入一个文件时,这个文件会首先被写入本地,并被切分成若干个块,每个块的大小是由 HDFS 的设定值来决定的。每个块都向 HDFS 集群中的名称节点发起写请求,名称节点会根据系统中各个数据节点的使用情况,选择一个数据节点列表返回给客户端,然后客户端就把数据首先写入列表中的第一个数据节点,同时把列表传给第一个数据节点,当第一个数据节点接收到 4 KB 数据的时候,写入本地,并且向列表中的第二个数据节点发起连接请求,把自己已经接收到的 4 KB 数据和列表传给第二个数据节点,当第二个数据节点接收到 4 KB 数据的时候,写入本地,并且向列表中的第三个数据节点发起连接请求,依次类推,列表中的多个数据节点形成一条数据复制的流水线。最后,当文件写完的时候,数据复制也同时完成。
4、名称节点出错
名称节点保存了所有的元数据信息,其中最核心的两大数据结构是 FsImage 和 EditLog,如果这两个文件发生损坏,那么整个 HDFS 实例将失效。Hadoop 采用两种机制来确保名称节点的安全:第一,把名称节点上的元数据信息同步存储到其他文件系统(比如远程挂载的网络文件系统 NFS)中;第二,运行一个第二名称节点,当名称节点宕机以后,可以把第二名称节点作为一种弥补措施,利用第二名称节点中的元数据信息进行系统恢复,但是从前面对第二名称节点的介绍中可以看出,这样做仍然会丢失部分数据。因此,一般会把上述两种方式结合使用,当名称节点发生宕机时,首先到远程挂载的网络文件系统中获取备份的元数据信息,放到第二名称节点上进行恢复,并把第二名称节点作为名称节点来使用。
5、数据节点出错
每个数据节点会定期向名称节点发送“心跳”信息,向名称节点报告自己的状态。当数据节点发生故障,或者网络发生断网时,名称节点就无法收到来自一些数据节点的“心跳”信息,这时这些数据节点就会被标记为“宕机”,节点上面的所有数据都会被标记为“不可读”,名称节点不会再给它们发送任何 I/O 请求。这时,有可能出现一种情形,即由于一些数据节点的不可用,会导致一些数据块的副本数量小于冗余因子。名称节点会定期检查这种情况,一旦发现某个数据块的副本数量小于冗余因子,就会启动数据冗余复制,为它生成新的副本。HDFS 与其他分布式文件系统的最大区别就是可以调整冗余数据的位置。
6、数据出错
网络传输和磁盘错误等因素都会造成数据错误。客户端在读取到数据后,会采用 md5 和 sha1对数据块进行校验,以确定读取到正确的数据。在文件被创建时,客户端就会对每一个文件块进行信息摘录,并把这些信息写入同一个路径的隐藏文件里面。当客户端读取文件的时候,会先读取该信息文件,然后利用该信息文件对每个读取的数据块进行校验,如果校验出错,客户端就会请求到另外一个数据节点读取该文件块,并且向名称节点报告这个文件块有错误,名称节点会定期检查并且重新复制这个块。
HDFS数据读写过程
在介绍 HDFS 的数据读写过程之前,需要简单介绍一下相关的类。FileSystem 是一个通用文件系统的抽象基类,可以被分布式文件系统继承,所有可能使用 Hadoop 文件系统的代码都要使用到这个类。Hadoop 为 FileSystem 这个抽象类提供了多种具体的实现,DistributedFileSystem 就是 FileSystem在 HDFS 文件系统中的实现。FileSystem 的 open()方法返回的是一个输入流 FSDataInputStream 对象,在 HDFS 文件系统中具体的输入流就是 DFSInputStream;FileSystem 中的 create()方法返回的是一个输出流 FSDataOutputStream 对象,在 HDFS 文件系统中具体的输出流就是 DFSOutputStream。
1、读数据过程
客户端调用open()、read()、close()读取数据,HDFS内部执行过程如下:
客户端通过 FileSystem.open()打开文件,相应地,在 HDFS 文件系统中 DistributedFileSystem 具体实现了 FileSystem。因此,调用 open()方法后,DistributedFileSystem 会创建输入流 FSDataInputStream,对于 HDFS 而言,具体的输入流就是 DFSInputStream。
在 DFSInputStream 的构造函数中,输入流通过 ClientProtocal.getBlockLocations()远程调 用名称节点,获得文件开始部分数据块的保存位置。对于该数据块,名称节点返回保存该数据块 的所有数据节点的地址,同时根据距离客户端的远近对数据节点进行排序;然后, DistributedFileSystem 会利用 DFSInputStream 来实例化 FSDataInputStream,返回给客户端,同时返回了数据块的数据节点地址。
获得输入流 FSDataInputStream 后,客户端调用 read()函数开始读取数据。输入流根据前 面的排序结果,选择距离客户端最近的数据节点建立连接并读取数据。
数据从该数据节点读到客户端;当该数据块读取完毕时,FSDataInputStream 关闭和该数据节点的连接。
输入流通过 getBlockLocations()方法查找下一个数据块(如果客户端缓存中已经包含了该数据块的位置信息,就不需要调用该方法)。
找到该数据块的最佳数据节点,读取数据。 (7)当客户端读取完毕数据的时候,调用 FSDataInputStream 的 close()函数,关闭输入流。
2、写数据过程
客户端连续调用create()、write()、close(),HDFS内部执行过程
客户端通过 FileSystem.create()创建文件,相应地,在 HDFS 文件系统中 Distributed FileSystem 具体实现了 FileSystem。因此,调用 create ()方法后,DistributedFileSystem 会创建输出 流 FSDataOutputStream,对于 HDFS 而言,具体的输出流就是 DFSOutputStream
然后,DistributedFileSystem 通过 RPC 远程调用名称节点,在文件系统的命名空间中创 建一个新的文件。名称节点会执行一些检查,比如文件是否已经存在、客户端是否有权限创建文 件等。检查通过之后,名称节点会构造一个新文件,并添加文件信息。远程方法调用结束后, DistributedFileSystem 会利用 DFSOutputStream 来实例化 FSDataOutputStream(输入流),返回给客户端,客 户端使用这个输出流写入数据。
获得输出流 FSDataOutputStream 以后,客户端调用输出流的 write()方法向 HDFS 中对应 的文件写入数据。
客户端向输出流 FSDataOutputStream 中写入的数据会首先被分成一个个的分包,这些分 包被放入 DFSOutputStream 对象的内部队列。输出流 FSDataOutputStream 会向名称节点申请保存 文件和副本数据块的若干个数据节点,这些数据节点形成一个数据流管道。队列中的分包最后被 打包成数据包,发往数据流管道中的第一个数据节点,第一个数据节点将数据包发送给第二个数 据节点,第二个数据节点将数据包发送给第三个数据节点,这样,数据包会流经管道上的各个数 据节点(即第 3.5.2 节介绍的流水线复制策略)。
因为各个数据节点位于不同的机器上,数据需要通过网络发送。因此,为了保证所有 数据节点的数据都是准确的,接收到数据的数据节点要向发送者发送“确认包”(ACK Packet)。 确认包沿着数据流管道逆流而上,从数据流管道依次经过各个数据节点并最终发往客户端,当客户端收到应答时,它将对应的分包从内部队列移除。不断执行(3)~(5)步,直到数据全 部写完。
客户端调用 close()方法关闭输出流,此时开始,客户端不会再向输出流中写入数据,所 以,当DFSOutputStream 对象内部队列中的分包都收到应答以后,就可以使用 ClientProtocol.complete()方法通知名称节点关闭文件,完成一次正常的写文件过程。