HDFS架构设计和读写流程

HDFS是一种分布式文件系统,具有高度的容错能力,旨在部署在低成本硬件上。

设计目标:

  • 考虑硬件故障,检测故障并快速、自动地从故障中恢复是HDFS的核心目标。
  • HDFS设计用于批处理,而不是用户交互使用。重点在于数据访问的高吞吐量,而不是数据访问的低延迟。
  • 大数据集。它应该提供较高的聚合数据带宽,并可以扩展到单个群集中的数百个节点。
  • 简化了数据一致性模型。考虑应用场景出于简化设计和实现的目的,HDFS假设了一种 write-once-read-many 的文件访问模型。支持将内容追加到文件末尾,但不能在任意点更新。
  • 移动"计算"比移动数据便宜。将计算迁移到更靠近数据的位置,而不是将数据移动到应用程序正在运行的位置。
  • 跨异构硬件和软件平台的可移植性

HDFS架构

HSFS是以master/slave模式运行的,其中NameNode运行在master节点,DataNode运行slave节点。在内部,一个文件其实被分成一个或多个数据块,这些块存储在一组Datanode上。

Namenode执行文件系统的Namespace操作,比如打开、关闭、重命名文件或目录。它也负责确定数据块到具体Datanode节点的映射。
Datanode负责处理文件系统客户端的读写请求。在Namenode的统一调度下进行数据块的创建、删除和复制。

Namenode是所有HDFS元数据的仲裁者和管理者。群集中单个NameNode的存在极大地简化了系统的体系结构。

在这里插入图片描述

文件系统命名空间

HDFS支持传统的分层文件组织(目录结构)。用户或应用程序可以创建目录并将文件存储在这些目录中。文件系统名称空间层次结构与大多数其他现有文件系统相似。可以创建和删除文件,将文件从一个目录移动到另一个目录或重命名文件。

当前(hadoop 3.2.1),HDFS支持用户磁盘配额和访问权限控制。暂不支持硬链接或软链接。

配额(Quota)

HDFS允许管理员为使用的名称数和用于单个目录的空间量设置配额。

名称配额是对以该目录为根结点的树中,文件和目录名称数量的硬限制。如果超出配额,文件和目录创建将失败。

空间配额是对以该目录为根节点的树中,文件所使用的字节数的硬性限制。如果配额不允许写入整个块,则块分配失败。块的每个副本均计为配额。

fsimage的两种配额都是持久的。启动时,如果fsimage立即违反配额(可能是fsimage被秘密修改),则会为每个此类违反打印警告。
查看配额命令:hadoop fs -count -q [-h] [-v] [-t [以逗号分隔的存储类型列表]] <目录> … <目录>
设置目录空间配额命令:hdfs dfsadmin -setSpaceQuota <目录> … <目录>

访问权限
HDFS 每个文件和目录都与一个Owner和一个Group相关联。对于文件,需要r权限才能读取文件,而w权限才需要写入或附加到文件。

数据复制

Block是HDFS的最小存储单元。默认大小:128M(HDFS 1.x中,默认64M),若文件大小不足128M,则会单独成为一个block。实质上就是Linux相应目录下的普通文件,名称格式:blk_xxxxxxx。

HDFS将每个文件存储为一系列块(Block),复制文件的块是为了容错。支持机架的副本放置策略的目的是提高数据可靠性,可用性和网络带宽利用率。

块大小和复制因子是每个文件可配置的。应用程序可以指定文件的副本数。复制因子可以在文件创建时指定,以后可以更改。
HDFS中的文件只能写入一次(追加和截断除外),并且在任何时候都只能具有一个Writer。

在常见情况下,当复制因子为3时,HDFS的放置策略是:如果Writer位于数据节点上,则将第一个副本放置在本地计算机上;否则,将第一个副本放入与Writer位于同一机架的随机数据节点上;第二个副本放置在另一个不同机架上的一个结点上,最后一个副本放置在另一个机架的另一个结点上。使用此策略,文件的副本不会均匀分布在机架上。三分之一的副本位于一个节点上,三分之二的副本位于同一个机架上,其余三分之一则平均分布在第二个机架上。

为了最大程度地减少全局带宽消耗和读取延迟,HDFS尝试满足最接近Reader的副本的读取请求。如果在与Reader节点相同的机架上存在一个副本,则该副本应优先满足读取请求。

元数据的持久化

NameNode使用一个称为EditLog的事务日志来永久记录文件系统元数据发生的每个更改。
NameNode使用其本地主机OS文件系统中的文件来存储EditLog。整个文件系统Namespace(包括块到文件的映射和文件系统属性)存储在名为FsImage的文件中。FsImage作为文件,存储在NameNode的本地文件系统中

NameNode也在内存中保留整个文件系统名称空间和文件Blockmap的镜像Image。当NameNode启动或由可配置的阈值触发检查点时,它会从磁盘读取FsImage和EditLog,将EditLog中的所有事务应用于FsImage的内存中表示形式,并将此新版本刷新为磁盘上的新FsImage。然后,它可以截断旧的EditLog,因为其事务已应用于持久性FsImage。此过程称为检查点(checkpoint恢复)。

我们无需为每个编辑修改FsImage,而是将编辑保留在Editlog中。在checkpoint期间,来自Editlog的更改将应用​​于FsImage。
可以在给定的时间间隔触发检查点(以秒表示的dfs.namenode.checkpoint.period,或者在累积一定数量的文件系统事务之后(dfs.namenode.checkpoint.txns)。如果同时设置了这两个属性,则要达到的第一个阈值将触发检查点。

HDFS通信

按照设计,NameNode永远不会启动任何RPC。相反,它仅响应由DataNode或客户端发出的RPC请求。

健壮性

HDFS的主要目标是即使出现故障也能可靠地存储数据。常见的故障类型是NameNode故障,DataNode故障和网络分区(network partitions)。

重新复制

每个DataNode定期向NameNode发送心跳消息。网络分区(CAP理论中的分区概念)可能导致一部分DataNode失去与NameNode的连接。NameNode将没有最近心跳的DataNode标记为已死,并且不向其转发任何新的IO请求。DataNode挂掉可能导致某些块的复制因子降至其指定值以下。NameNode不断跟踪需要复制的块,并在必要时进行重新复制

判断DataNode失效的超时时间比较保守地长(默认情况下超过10分钟),以避免由DataNode的状态震荡引起的复制风暴。

集群Rebalance
如果DataNode的可用空间低于某个阈值,可能会自动将数据从一个DataNode移至另一个DataNode。

数据完整性
从DataNode提取的数据块可能会损坏。由于存储设备故障,网络故障或软件故障,可能会导致这种损坏。客户端创建HDFS文件时,它将计算文件每个块的校验和,并将这些校验和存储在同一HDFS命名空间中的单独的隐藏文件中。客户端检索文件内容时,它将验证从每个DataNode接收的数据是否与存储在关联的checksum文件中的校验和匹配。

元数据磁盘故障
FsImage和EditLog的损坏可能导致HDFS实例无法正常运行。可以将NameNode配置为支持维护FsImage和EditLog的多个副本。多个元数据副本的同步更新可能会降低NameNode的处理速度,好在数据密集型的HDFS元数据的量并不大。

快照
HDFS实现的快照非常高效,可以在文件系统的子树上甚至整个文件系统上创建快照。快照的一些常见用例是数据备份,防止用户错误和灾难恢复。快照文件记录了块列表和文件大小,没有数据复制。如果快照表目录中有快照,则在删除所有快照之前,不能删除或重命名该目录。
创建快照命令:hdfs dfs -createSnapshot <路径> [<快照名称>]

Ref:
https://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-hdfs/HdfsDesign.html


HDFS读文件过程简介

在Hadoop中使用FileSystem.open()方法来创建输入流。MapReduce在提交作业时,已经确定了每个map和reduce要读取的文件,文件的偏移量和读取的长度,所以MapReduce作业大部分是文件的随机读取。
HDFS在数据传输过程中,对数据块不是按整个block进行传输的,而是将block切分成一个个的数据包DFSPacket进行传输。

  1. Client向namenode发RPC请求, 获取要打开文件的blocks信息。
  2. Namenode返回文件的元信息(BlockId以及所在的Datanode列表)
  3. Client收到后和最近的DataNode建立连接,以packet为单位依次传输对应的数据块。
  4. Client反复读取各个Block块,直到最后一个Block完成后,关闭和DataNode的连接。
    如果读取某个block失败, 它会去尝试读取存储此block的其他DataNode,并向NameNode报告损坏的块。

下图所示:

在这里插入图片描述

HDFS写文件过程简介

Client会将文件数据缓存到一个临时的本地文件中。当本地文件累积的数据至少具有一个HDFS块大小时,才与NameNode建立连接。NameNode返回DataNode的数据流管道Pipline。
客户端将块写入流水线中的第一个DataNode,同时将其传递给流水线中的下一个DataNode,将blockReceived消息发送给NameNode,并将确认消息发送给DFSOutputStream客户端。

  1. Client调用FileSystem.create()创建文件
    首先会通过RPC调用,在NameNode上创建文件的元数据,构造输出流outputStream。
  2. 首次写入时,先向NameNode申请数据块,NameNode在目录树的命名空间中创建一个空的新文件,并记录在EditLog。分配数据块成功后,连同这个块的所有DataNode列表给客户端。
  3. 通过Pipeline方式将数据分成一个个DFSPacket,在多个DataNode之间依次顺序传输文件包。比如写入Block5时,传输Block5给DN1,DN1传给复制集中的DN2,DN2传给DN3,DN3校验后返回ack给DN2,DN2返回ack给DN1,最后DN1返回给客户端写入完成。
  4. 写完一个数据块后,数据流管道上的DataNode,会通过RPC向NameNode提交数据块。如果还有等待输出的数据,会再申请添加一个新的Block,直到所有数据块传输完成。
  5. 关闭流的时候,DFSOutputStream通过RPC向NameNode提交所有的Block,所有DataNode都汇报完成后,NameNode确认当前文件写完成。
  6. 如果管道流水线中的任何一个DataNode失败,失败的Stream会被关闭。数据将会继续写到剩余的DataNode中。同时NameNode会被告知待备份状态,继续备份数据到新的可用的节点,实现容错。

参考下图
在这里插入图片描述

。。

猜你喜欢

转载自blog.csdn.net/rover2002/article/details/106477536