03-HDFS(1)

发布时间 2023-06-29 00:01:12作者: tree6x7

1. 存储系统

1.1 硬盘

硬盘(Hard Disk Drive)是计算机的主要存储硬件,可以用来存储数据。

a. 硬盘类别

b. RAID 磁盘阵列

单个硬盘的存储能力是有限的,如果要存储更多的数据,可以通过某种技术,将若干个硬盘连接在一起,提供能耗的存储能力。我们在服务器上插更多的磁盘来提高存储容量,而服务器上的插槽是有限的,我们无法无限地增加硬盘。所以,我们可以买 RAID 磁盘阵列来解决数据存储速度、容错问题。

RAID 可以将多块独立的硬盘组织在一起,可以将多块硬盘连接在一起,并在性能上、容错上会有一定地提升。

1.2 存储架构

a. DAS 存储架构

DAS 存储架构也称为直连式存储(Direct-Attached Storage),存储设备是通过电缆(通常是 SCSI 接口电缆)直接挂到服务器总线上。DAS 比较依赖操作系统来进行 IO 操作。

b. NAS 网络接入存储

NAS 也称为网络接入存储(Network-Attached Storage),存储设备通过标准的网络拓扑结构(例如以太网)连接采用 NAS 较多的功能是用来文档共享、图片共享、电影共享等等,而且随着云计算的发展,一些 NAS 厂商也推出了云存储功能,大大方便了企业和个人用户的使用。

因为 NAS 存储是通过网络连接的,所以并不依赖某一种类的操作系统,所以不管是 Windows、MacOS、Linux、Unix 都是可以使用 NAS 的。

c. SAN 存储区域网络存储

SAN(Storage Area Network)是一种高速的、专门用于存储操作的网络,通常独立于计算机局域网(LAN)。SAN 将主机和存储设备连接在一起,能够为其上的任意一台主机和任意一台存储设备提供专用的通信通道。SAN 将存储设备从服务器中独立出来,实现了服务器层次上的存储资源共享。

d. 对比

1.3 文件系统

a. 概述

文件系统是一种用于向用户提供底层数据访问的机制。它将设备中的空间划分为特定大小的块(或者称为簇),一般每块 512 字节。数据存储在这些块中,大小被修正为占用整数个块。由文件系统软件来负责将这些块组织为文件和目录,并记录哪些块被分配给了哪个文件,以及哪些块没有被使用。

  • 文件系统是一种存储和组织数据的方法,它使得对文件访问和查找变得容易;
  • 使用文件和树形目录的抽象逻辑概念代替了硬盘等物理设备使用数据块的概念,用户使用文件系统来保存数据不必关心数据底层存在硬盘哪里,只需要记住这个文件的所属目录和文件名;
  • 文件系统通常使用硬盘和光盘这样的存储设备,以维护文件在设备中的物理位置;
  • 文件系统是一套实现了数据的存储、分级组织、访问和获取等操作的抽象数据类型(Abstract data type)。

不过,文件系统并不一定只在特定存储设备上出现。它是数据的组织者和提供者,至于它的底层,可以是磁盘,也可以是其它动态生成数据的设备(比如网络设备)。

b. 分类

(1)基于磁盘的文件系统

磁盘文件系统是一种设计用来利用数据存储设备来保存计算机文件的文件系统,最常用的数据存储设备是磁盘驱动器,可以直接或者间接地连接到计算机上。例如:FAT、exFAT、NTFS、HFS、HFS+、ext2、ext3、ext4、ODS-5、btrfs、XFS、UFS、ZFS。Linux 中可以使用 df -Th 查看。

(2)虚拟文件系统

在内核中生成的文件系统,比如 proc。 proc 文件系统是一个虚拟文件系统,通过它可以使用一种新的方法在 Linux 内核空间和用户间之间进行通信。

(3)网络文件系统

网络文件系统(NFS,Network File System)是一种将远程主机上的分区(目录)经网络挂载到本地系统的一种机制。允许本地计算机访问另一台计算机上的数据,对此类文件系统中文件的操作都通过网络连接进行。

c. 问题

(1)成本高

传统存储硬件通用性差,设备投资加上后期维护、升级扩容的成本非常高。

(2)性能低

单节点 I/O 性能瓶颈无法逾越,难以支撑海量数据的高并发高吞吐场景。

(3)可扩展性差

无法实现快速部署和弹性扩展,动态扩容、缩容成本高,技术实现难度大。

(4)无法支撑高效率的计算分析

传统存储方式意味着数据存储是存储,计算是计算,当需要处理数据的时候把数据移动过来。程序和数据存储是属于不同的技术厂商实现,无法有机统一整合在一起。

1.4 DFS 设计

(1)如何解决海量数据存的下的问题?

传统做法是在单机存储。但是随着数据变多,会遇到存储瓶颈。

  • 单机纵向扩展:内存不够加内存,磁盘不够加磁盘。有上限限制,不能无限制加下去;
  • 多机横向扩展:采用多台机器存储,一台不够就加机器。理论上可以无限。

多台机器存储也就意味着迈入了分布式存储。

(2)如何解决海量数据文件查询便捷问题?

当文件被分布式存储在多台机器之后,后续获取文件的时候如何能快速找到文件位于哪台机器上呢。

一台一台查询过来也不靠谱,因此可以借助于元数据记录来解决这个问题。把文件和其存储的机器的位置信息记录下来,类似于图书馆查阅图书系统,这样就可以快速定位文件存储在哪一台机器上了。

(3)如何解决大文件传输效率慢的问题?

大数据使用场景下,GB、TP 级别的大文件是常见的。当单个文件过大的时候,如何提高传输效率?

通常的做法是「分块存储」:把大文件拆分成若干个小块(block 简写 blk),分别存储在不同机器上,并行操作提高效率。此外分块存储还可以解决数据存储负载均衡问题。此时元数据记录信息也应该更加详细:文件分了几块,分别位于哪些机器上。

(4)如何解决硬件故障数据丢失问题?

机器、磁盘等硬件出现故障是难以避免的事情,如何保证数据存储的安全性?

如果某台机器故障,数据块丢失,对于文件来说整体就是不完整的。冗余存储是个不错的选择,采用副本机制,副本越多,数据越安全,当然冗余也会越多。通过“不要把鸡蛋放在一个篮子里”的思想,可以把数据丢失的风险分散到各个机器上。

(5)如何解决用户查询视角统一问题?

随着存储的进行,数据文件越来越多,与之对应元数据信息也越来越多,如何让用户视觉层面感觉不到元数据的凌乱,同时也与传统的文件系统操作体验保持一致?

传统的文件系统拥有所谓的目录树结构、带有层次感的 namespace(命名空间),因此可以把分布式文件系统的元数据记录这一块也抽象成统一的目录树结构

小结

  1. 如何解决海量数据存的下的存储:分布式存储
  2. 如何解决海量数据文件便捷查询:元数据记录
  3. 如何解决大文件传输效率慢:分块存储
  4. 如何解决硬件故障数据丢失:副本机制
  5. 如何解决用户查询视角统一规整:抽象目录树结构

2. HDFS 概述

2.1 介绍&设计

https://hadoop.apache.org/docs/r3.1.3/

HDFS(Hadoop Distributed File System)是 Apache Hadoop 核心组件之一,作为大数据生态圈最底层的分布式存储服务而存在。

HDFS 是一种能够在普通硬件上运行的分布式文件系统,它是高度容错的,适应于具有大数据集的应用程序,它非常适于存储大型数据 (比如 TB 和 PB)。

HDFS 使用多台计算机存储文件,并且提供统一的访问接口,像是访问一个普通文件系统一样使用分布式文件系统。

Doug Cutting 后来根据 Google 发表的论文 GFS,创造了一个新的文件系统,叫做 HDFS。

HDFS 设计目标

  • 硬件故障(Hardware Failure)是常态, HDFS 可能有成百上千的服务器组成,每一个组件都有可能出现故障。因此故障检测和自动快速恢复是 HDFS 的核心架构目标。
  • HDFS 上的应用主要是以流式读取数据(Streaming Data Access)。HDFS 被设计成用于批处理,而不是用户交互式的。相较于数据访问的反应时间,更注重数据访问的高吞吐量。
  • 典型的 HDFS 文件大小是 GB 到 TB 的级别。所以,HDFS 被调整成支持大文件(Large Data Sets)。它应该提供很高的聚合数据带宽,一个集群中支持数百个节点,一个集群中还应该支持千万级别的文件。
  • 大部分 HDFS 应用对文件要求的是 write-one-read-many 访问模型。一个文件一旦创建、写入、关闭之后就不需要修改了。这一假设简化了数据一致性问题,使高吞吐量的数据访问成为可能。
  • 移动计算的代价比之移动数据的代价低。一个应用请求的计算,离它操作的数据越近就越高效。将计算移动到数据附近,比之将数据移动到应用所在显然更好。
  • HDFS 被设计为可从一个平台轻松移植到另一个平台。这有助于将 HDFS 广泛用作大量应用程序的首选平台。

优点

(1)高容错性

  • 数据自动保存多个副本。通过增加副本的形式,提高容错性;
  • 某个副本丢时候,它可以自动恢复。

(2)适合处理大数据

  • 数据规模:能够处理数据规模达到 GB、TB、PB 级别的数据;
  • 文件规模:能够处理百万规模的文件数量;

(3)可构建在廉价的机器上,通过多副本机制,提高可靠性。

缺点

(1)不适合低延时数据访问,比如毫秒级的存储数据,是做不到的;

(2)无法高效的对大量小文件进行存储

  • 存储大量小文件的话,它会占用 NameNode 大量的内存来存储文件目录和块信息。这样是不可取的,因为 NameNode 的内存总是有限的;
  • 小文件存储的寻址时间会超过读取时间,它违反了 HDFS 的设计目标。

(3)不支持并发写入、文件随机修改

  • 一个文件只能有一个写,不允许多个线程同时写;
  • 仅支持数据 append(追加),不支持文件的随机修改。

2.2 特性

a. 主从架构

HDFS 采用 Master/Slave 架构。一般一个 HDFS 集群是有一个 NameNode 和一定数目的 DataNode 组成。

NameNode 是 HDFS 主节点,DataNode 是 HDFS 从节点,两种角色各司其职,共同协调完成分布式的文件存储服务。

b. 分块机制

HDFS 中的文件在物理上是分块存储(Block),块的大小可以通过配置参数(dfs.blocksize)来规定,默认大小在 Hadoop2.x/3.x 版本中是 128M,1.x 版本中是 64M。配置位于 hdfs-default.xml 中。

为什么块的大小不能设置太小,也不能设置太大?

  1. HDFS 的块设置太小,会增加寻址时间,程序一直在找块的开始位置;
  2. 如果块设置的太大,从磁盘传输数据的时间会明显大于定位这个块开始位置所需的时间,导致程序在处理这块数据时会非常慢。

=> HDFS block 的大小设置主要取决于磁盘传输速率。

c. 副本机制

为了容错,文件的所有 block 都会有副本。每个文件的 block 大小(dfs.blocksize)和副本系数(dfs.replication)都是可配置的。

应用程序可以指定某个文件的副本数目。副本系数可以在文件创建的时候指定,也可以在之后通过命令改变。

默认 dfs.replication=3,也就是会额外再复制 2 份,连同本身总共 3 份副本。

d. 名称空间

HDFS 支持传统的层次型文件组织结构。用户可以创建目录,然后将文件保存在这些目录里。文件系统 Namespace 的层次结构和大多数现有的文件系统类似:用户可以创建、删除、移动或重命名文件。

NameNode 负责维护文件系统的 Namespace,任何对文件系统 Namespace 或属性的修改都将被 NameNode 记录下来(NameNode 的 Name 指的就是 Namespace)。

HDFS 会给客户端提供一个统一的抽象目录树,客户端通过路径来访问文件,形如:hdfs://namenode:port/dir-a/dir-b/dir-c/file.data。

e. 元数据&数据块

在 HDFS 中,NameNode 管理的元数据具有 2 种类型:

  • 【文件自身属性信息】文件名称、权限,修改时间,文件大小,复制因子,数据块大小;
  • 【文件块位置映射信息】记录文件块和 DataNode 之间的映射信息,即哪个块位于哪个节点上。

文件的各个数据块(block)的具体存储管理由 DataNode 节点承担。每一个 block 都可以在多个 DataNode 上存储(多副本机制)。

2.3 组成架构

HDFS 遵循主从架构

NameNode 是主节点,负责存储和管理文件系统元数据信息,包括 Namespace 目录结构、文件块位置信息等。

DataNode 是从节点,负责存储文件具体的数据块。两种角色各司其职,共同协调完成分布式的文件存储服务。

SecondaryNameNode 是主角色的辅助角色,帮助主角色进行元数据的合并。

a. NameNode

  • NameNode 是 Hadoop 分布式文件系统的核心,架构中的主角色。NameNode 维护和管理文件系统元数据,包括名称空间目录树结构、文件和块的位置信息、访问权限等信息。基于此,NameNode 成为了访问 HDFS 的唯一入口;
  • NameNode 内部通过「内存」和「磁盘文件」两种方式管理元数据,其中磁盘上的元数据文件包括「fsimage 内存元数据镜像文件」和「edits 编辑日志」;
  • 处理客户端读写请求。

b. DataNode

  • DataNode 是 Hadoop HDFS 中的「从角色」,负责具体的数据块存储。
  • DataNode 的数量决定了 HDFS 集群的整体数据存储能力,通过和 NameNode 配合维护着数据块。

c. 2rdNameNode

除了 DataNode 和 NameNode 之外,还有另一个守护进程,称为 Secondary NameNode。

当 NameNode 启动时,NameNode 合并 fsimage 和 edits log 文件以还原当前文件系统名称空间。

如果 edits log 过大则不利于加载,Secondary NameNode 就辅助 NameNode(但不能替代 NameNode)分担工作量,定期合并 fsimage 和 edits,并推送给 NameNode。

d. Client

  • 文件切分。文件上传到 HDFS 的时候,Client 将文件切分成一个一个的块,然后进行上传;
  • 与 NameNode 交互,获取文件块的位置信息;
  • 与 DataNode 交互,读取/写入数据;
  • Client 提供一些命令来管理 HDFS,比如 NameNode 格式化;
  • Client 可以通过一些命令来访问 HDFS,比如对 HDFS 增删改查操作。

3. 读写原理

因为 NameNode 维护管理了文件系统的元数据信息,这就造成了不管是读还是写数据都是基于 NameNode 开始的,也就是说 NameNode 成为了 HDFS 访问的唯一入口

入口地址是:http://NAMENODE_HOST:8020。

3.1 核心概念

a. Pipeline&ACK

Pipeline(管道)是 HDFS 在上传文件写数据过程中采用的一种数据传输方式。

客户端将数据块写入第 1 个数据节点,第 1 个数据节点保存数据之后再将块复制到第 2 个数据节点,后者保存后将其复制到第 3 个数据节点。通俗描述 Pipeline 的过程就是:Client => A => B => C。

为什么 DataNode 之间采用 Pipeline 线性传输,而不是一次给 3 个 DataNode 拓扑式传输呢?

因为数据以管道的方式,顺序的沿着一个方向传输,这样能够充分利用每个机器的带宽,避免网络瓶颈和高延迟时的连接,最小化推送所有数据的延时。在线性推送模式下,每台机器所有的出口宽带都用于以最快的速度传输数据,而不是在多个接收者之间分配宽带。

ACK (Acknowledge Character) 在数据通信中,是接收方发给发送方的一种「传输类控制字符」,表示发来的数据已确认接收无误。

在 Pipeline 管道传输数据的过程中,传输的反方向会进行 ACK 校验,确保数据传输安全。

客户端何时关闭 HDFS 文件输出流对象?

客户端会在完成数据写入后关闭 HDFS 文件输出流对象,即客户端调用 close() 方法关闭流对象。当客户端调用 close() 方法时,HDFS 文件输出流对象会执行以下操作:

  1. 向数据管道发送结束标志;
  2. 关闭数据管道,等待所有数据节点的 ACK 消息;
  3. 向 NameNode 发送一个关闭请求;
  4. 等待 NameNode 的 ACK 消息;
  5. 如果所有健康节点都发送了 ACK 消息,HDFS 文件输出流对象会关闭成功。

如果在执行 step2 时,某个数据节点在一定时间内没有发送 ACK 消息,HDFS 文件输出流对象会将该节点标记为故障节点,并将数据包重新发送到其他节点。如果所有健康节点都发送了 ACK 消息,HDFS 文件输出流对象才会执行 step3。如果在执行 step3 时,NameNode 在一定时间内没有发送 ACK 消息,HDFS 文件输出流对象会将 NameNode 标记为故障节点,并重试关闭操作。

在所有健康节点都返回 ACK 之后,HDFS 文件输出流对象会关闭成功,但并不保证数据写入操作一定成功。如果有任何一个数据节点在写入数据时发生了错误,HDFS 文件输出流对象会抛出 IOException。因此,为了保证数据写入的可靠性,需要在关闭流对象前对写入操作进行检查。

b. 机架感知·副本存储节点

  • 第 1 块副本:优先客户端本地,否则随机;
  • 第 2 块副本:不同于第 1 块副本的不同机架;
  • 第 3 块副本:第 2 块副本相同机架的不同机器。

c. 网络拓扑·节点距离计算

在 HDFS 写数据的过程中,NameNode 会选择距离待上传数据最近距离的 DataNode 接收数据。

那么这个最近距离怎么计算呢?

节点距离:两个节点到达「最近的共同祖先」的距离总和

For the common case, when the replication factor is three, HDFS’s placement policy is to put one replica on the local machine if the writer is on a datanode, otherwise on a random datanode, another replica on a node in a different (remote) rack, and the last on a different node in the same remote rack. This policy cuts the inter-rack write traffic which generally improves write performance. The chance of rack failure is far less than that of node failure; this policy does not impact data reliability and availability guarantees. However, it does reduce the aggregate network bandwidth used when reading data since a block is placed in only two unique racks rather than three. With this policy, the replicas of a file do not evenly distribute across the racks. One third of replicas are on one node, two thirds of replicas are on one rack, and the other third are evenly distributed across the remaining racks. This policy improves write performance without compromising data reliability or read performance.

d. 数据单位

在 DFSClient 写 HDFS 的过程中,有 3 个需要搞清楚的单位:

  • block 是最大的一个单位,它是最终存储于 DataNode 上的数据粒度,由 dfs.block.size 决定,默认是 128M(注:该参数由客户端配置决定);
  • Packet 是中等的一个单位,它是数据由 DFSClient 流向 DataNode 的粒度,以 dfs.write.Packet.size 为参考值,默认是 64K(注:该参数为参考值,是指真正在进行数据传输时,会以它为基准进行调整,调整的原因是一个 Packet 有特定的结构,调整的目标是这个 Packet 的大小刚好包含结构中的所有成员,同时也保证写到 DataNode 后当前 block 的大小不超过设定值);
  • chunk 是最小的一个单位,它是 DFSClient 到 DataNode 数据传输中进行数据校验的粒度,由 io.bytes.per.checksum 决定,默认是 512B(注:事实上一个 chunk 还包含 4B 的校验值,因而 chunk 写入 Packet 时是 516B;数据与检验值的比值为 128:1,所以对于一个 128M 的 block 会有一个 1M 的校验文件与之对应)。

写过程中的 3 层缓存(写过程中会以 chunk、Packet 及 PacketQueue 三个粒度做三层缓存):

  • 首先,当数据流入 DFSOutputStream 时,DFSOutputStream 内会有一个 chunk 大小的 buf,当数据写满这个 buf(或遇到强制 flush),会计算 checksum 值,然后填塞进 Packet;
  • 当一个 chunk 填塞进入 Packet 后,仍然不会立即发送,而是累积到一个 Packet 填满后,将这个 Packet 放入 DataQueue 队列;
  • 进入 DataQueue 队列的 Packet 会被另一线程按序取出发送到 DataNode(注:生产者/消费者模型,阻塞生产者的条件是 DataQueue 与 AckQueue 之和超过一个 block 的 Packet 上限)。

e. 角色职责

NameNode

  1. NameNode 是 HDFS 的核心,集群的主角色,被称为 Master;
  2. NameNode 仅存储管理 HDFS 的元数据:文件系统 Namespace 操作维护目录树,文件和块的位置信息;
  3. NameNode 不存储实际数据或数据集,数据本身实际存储在 DataNodes 中;
  4. NameNode 知道 HDFS 中任何给定文件的块列表及其位置,使用此信息 NameNode 知道如何从块中构建文件;
  5. NameNode 并不持久化存储每个文件中各个块所在的 DataNode 的位置信息,这些信息会在系统启动时从 DataNode 的汇报中重建;
  6. NameNode 对于 HDFS 至关重要,当 NameNode 关闭时,HDFS / Hadoop 集群无法访问;
  7. NameNode 是 Hadoop 集群中的单点故障;
  8. NameNode 所在机器通常会配置有大量内存(RAM)。

DataNode

  1. DataNode 负责将实际数据存储在 HDFS 中,是集群的从角色,被称为 Slave;
  2. DataNode 启动时,它将自己发布到 NameNode 并汇报自己负责持有的块列表;
  3. 根据 NameNode 的指令,执行块的创建、复制、删除操作;
  4. DataNode 会定期(dfs.heartbeat.interval=3s)向 NameNode 发送心跳,如果 NameNode 长时间没有接受到 DataNode 发送的心跳, NameNode 就会认为该 DataNode 失效;
  5. DataNode 会定期(dfs.blockreport.intervalMsec=6h)向 NameNode 进行自己持有的数据块信息汇报;
  6. DataNode 所在机器通常配置有大量的硬盘空间,因为实际数据存储在 DataNode 中。

3.2 写数据

a. 简要说明

  1. Client 发起文件上传请求,通过 RPC 与 NameNode 建立通讯,NameNode 检查目标文件是否已存在,父目录是否存在,返回是否可以上传;
  2. Client 请求第一个 block 该传输到哪些 DataNode 服务器上;
  3. NameNode 根据配置文件中指定的备份数量及副本放置策略进行文件分配,返回可用的 DataNode 的地址,如:A、B、C;
  4. Client 请求 3 台 DataNode 中的一台 A 上传数据(本质上是一个 RPC 调用,建立 Pipeline),A 收到请求会继续调用 B,然后 B 调用 C,将整个 Pipeline 建立完成,后逐级返回 Client;
  5. Client 开始往 A 上传第一个 block(先从磁盘读取数据放到一个本地内存缓存),以 Packet 为单位(默认 64K),A 收到一个 Packet 就会传给 B,B 传给 C;A 每传一个 Packet 会放入一个应答队列等待应答;
  6. 数据被分割成一个个 Packet 数据包在 Pipeline 上依次传输,在 Pipeline 反方向上,逐个发送 ack(ack 应答机制),最终由 Pipeline 中第一个 DataNode 节点 A 将 Pipeline ack 发送给 Client;
  7. 当第 1 个 block 传输完成之后,Client 再次请求 NameNode 上传第 2 个 block 到服务器。

b. 详细说明

  1. HDFS 客户端通过对 DistributedFileSystem 对象调用 create() 请求创建文件;
  2. DistributedFileSystem 对 NameNode 进行 RPC 调用,请求上传文件。NameNode 执行各种检查判断:目标文件是否存在、父目录是否存在、客户端是否具有创建该文件的权限。检查通过,NameNode 就会为创建新文件记录一条记录。否则,文件创建失败并向客户端抛出一个 IOException;
  3. DistributedFileSystem 为客户端返回 FSDataOutputStream 输出流对象,由此客户端可以开始写入数据。FSDataOutputStream 是一个包装类,所包装的是 DFSOutputStream;
  4. 在客户端写入数据时,数据会被先写入到本地缓存(OutputStreamBuffer)中。DFSOutputStream 将它分成一个个数据包(Packet 默认 64KB),并写入一个称之为“数据队列(Data Queue)”的内部队列。DFSOutputStream 有一个内部类做 DataStreamer,用于请求 NameNode 挑选出适合存储数据副本的一组 DataNode。这一组 DataNode 采用 Pipeline 机制做数据的发送(默认是 3 副本存储);
  5. DataStreamer 将数据包流式传输到 Pipeline 的第 1 个 DataNode,该 DataNode 存储数据包并将它发送到Pipeline 的第 2 个 DataNode。同样,第 2 个 DataNode 存储数据包并且发送给第 2 个(也是最后一个)DataNode;
  6. DFSOutputStream 也维护着一个内部数据包队列来等待 DataNode 的收到确认回执,称之为“确认队列(Ack Queue)”,收到 Pipeline 中所有 DataNode 确认信息后,该数据包才会从确认队列删除;
  7. 客户端完成数据写入后,将在流上调用 close() 关闭。该操作将剩余的所有数据包写入 DataNode Pipeline,并在联系到 NameNode 告知其文件写入完成之前,等待确认;
  8. 当一个 block 传输完成之后,客户端(Client)再次请求 NameNode 上传第 2 个 block 的服务器,直到文件写完 ...
  9. 因为 NameNode 已经知道文件由哪些 block 组成(DataStream 请求分配数据块),因此它仅需等待最小复制块即可成功返回(block 最小复制是由参数 dfs.namenode.replication.min 指定,默认 1)。

3.3 读数据

a. 简要说明

  1. Client 向 NameNode 发起 RPC 请求,来确定请求文件 block 所在的位置;
  2. NameNode 会视情况返回文件的部分或者全部 block 列表,对于每个 block,NameNode 都会返回含有该 block 副本的 DataNode 地址;
  3. 这些返回的 DataNode 地址,会按照集群拓扑结构得出 DataNode 与客户端的距离,然后进行排序,排序两个规则:网络拓扑结构中距离 Client 近的排靠前;心跳机制中超时汇报的 DataNode 状态为 STALE,这样的排靠后;
  4. Client 选取排序靠前的 DataNode 来读取 block,如果客户端本身就是 DataNode,那么将从本地直接获取数据;底层上本质是建立 Socket Stream(FSDataInputStream),重复的调用父类 DataInputStream 的 read() 方法,直到这个 block 上的数据读取完毕(以 Packet 为读取单位);
  5. read() 是并行的读取 block 信息(建立多个 Pipeline),不是一块一块的读取;NameNode 只是返回 Client 请求包含块的 DataNode 地址,并不是返回请求块的数据;
  6. 当读完列表的 block 后,若文件读取还没有结束,客户端会继续向 NameNode 获取下一批的 block 列表;
  7. 读取完一个 block 都会进行 checksum 验证,如果读取 DataNode 时出现错误,客户端会通知 NameNode,然后再从下一个拥有该 block 副本的 DataNode 继续读;
  8. 最终读取来所有的 block 会合并成一个完整的最终文件。

b. 详细说明

  1. HDFS 客户端通过调用 DistributedFileSystem 对象上的 open() 来打开希望读取的文件;
  2. DistributedFileSystem 使用 RPC 调用 NameNode 来确定文件中前几个块的块位置(分批次读取)信息。对于每个块,NameNode 返回具有该 block 副本的 DataNode 的地址,并且 DataNode 根据 block 与客户端的距离进行排序(注意此距离指的是网络拓扑中的距离)。比如客户端的本身就是一个 DataNode,那么从本地读取数据明显比跨网络读取数据效率要高;
  3. DistributedFileSystem 将 FSDataInputStream(支持文件 seek 定位读的输入流)返回到客户端以供其读取数据。FSDataInputStream 类转而封装为 DFSInputStream 类,DFSInputStream 管理着 DataNode 和 NameNode 之间的 IO;
  4. 客户端在流上调用 read() 方法。然后,已存储着文件前几个 block 的 DataNode 地址的 DFSInputStream 随即连接到文件中第一个块的最近的 DataNode 节点。通过对数据流反复调用 read() 方法,可以将数据从 DataNode 传输到客户端;
  5. 当该 block 快要读取结束时,DFSInputStream 将关闭与该 DataNode 的连接,然后寻找下一个 block 的最佳 DataNode。这些操作对用户来说是透明的。所以用户感觉起来它一直在读取一个连续的流;
  6. 客户端从流中读取数据时,block 是按照打开 DFSInputStream 与 DataNode 新建连接的顺序读取的。它也会根据需要询问 NameNode 来检索下一批数据块的 DataNode 位置信息。一旦客户端完成读取,就对 FSDataInputStream 调用 close() 方法;
  7. 如果 DFSInputStream 与 DataNode 通信时遇到错误,它将尝试该块的下一个最接近的 DataNode 读取数据。并将记住发生故障的 DataNode,保证以后不会反复读取该 DataNode 后续的块。此外,DFSInputStream 也会通过校验和(checksum)确认从 DataNode 发来的数据是否完整。如果发现有损坏的 block,DFSInputStream 会尝试从其他 DataNode 读取该 block 的副本,也会将被损坏的 block 报告给 NameNode。

在这个过程中,有一些底层原理和读取策略需要注意:

  • HDFS 将大文件划分为多个块,每个块通常为 128MB 大小。当客户端读取数据时,HDFS 会根据块的位置和大小等信息,通过流水线(Pipeline)机制从多个数据节点并行读取数据,从而提高读取速度和效率;
  • HDFS 的读取过程是基于流式处理的,即数据节点将数据通过网络发送给客户端,客户端再将数据缓存到本地的内存或磁盘上,从而实现流式读取。为了提高性能和效率,客户端通常会使用缓存机制,将部分数据缓存到本地的内存或磁盘上,减少网络传输和磁盘读取的次数;
  • HDFS 支持多种读取策略,包括顺序读取、随机读取、预读取等。不同的读取策略适用于不同的场景,例如顺序读取适用于批量数据处理,而随机读取适用于实时数据查询等;
  • 在流水线机制中,数据节点的位置和距离会影响数据传输的速度和效率。如果客户端距离数据节点较远,数据传输的速度可能会受到影响,从而影响整个读取过程的性能。因此,客户端通常会根据数据节点的位置和距离等因素选择最优的数据节点,从而提高数据传输的速度和效率。