转载自:
https://my.oschina.net/freelili/blog/1853668
HDFS 优缺点
优点
1.1 高容错性
可以由数百或数千个服务器机器组成, 每个服务器机器存储文件系统数据的一部分;
数据自动保存多个副本;
副本丢失后检测故障快速, 自动恢复.
1.2 适合批处理
移动计算而非数据;
数据位置暴露给计算框架;
数据访问的高吞吐量;
运行的应用程序对其数据集进行流式访问.
1.3 适合大数据处理
典型文件大小为千兆字节到太字节;
支持单个实例中的数千万个文件;
10K + 节点.
1.4 可构建在廉价的机器上
通过多副本提高可靠性;
提供了容错与恢复机制.
1.5 跨异构硬件和软件平台的可移植性强
轻松地从一个平台移植到另一个平台.
1.6 简单一致性模型
应用程序需要一次写入多次读取文件的访问模型;
除了追加和截断之外, 不需要更改已创建, 写入和关闭的文件;
简化了数据一致性问题, 并实现了高吞吐量数据访问;
高度可配置, 具有非常适合于许多安装的默认配置. 大多数时候, 只需要为非常大的集群调整配置.
缺点
2.1 不适合低延迟的数据访问
HDFS 设计更多的是批处理, 而不是用户交互使用. 重点在于数据访问的高吞吐量, 而不是数据访问的低延迟.
2.2 不适合小文件存取
占用 NameNode 大量内存;
寻道时间超过读取时间.
2.3 无法并发写入, 文件随即修改
一个文件只能有一个写者;
仅支持追加和截断.
基本组成
Namenode
接受客户端的读写服务
执行文件系统命名空间操作, 如打开, 关闭和重命名文件和目录.
管理文件系统命名空间
记录对文件系统命名空间或其属性的任何更改.
存储元数据信息 - metadata
Metadata 是存储在 Namenode 上的元数据信息, 它存储到磁盘的文件名为: fsimage. 并且有个叫 edits 的文件记录对 metadata 的操作日志. 总体来说, fsimage 与 edits 文件记录了 Metadata 中的权限信息和文件系统目录树, 文件包含哪些块, 确定块到 DataNode 的映射, Block 存放在哪些 DataNode 上(由 DataNode 启动时上报).
NameNode 将这些信息加载到内存并进行拼装, 就成为了一个完整的元数据信息. Namenode 在内存中保存着整个文件系统的命名空间和文件数据块映射 (Blockmap) 的映像. 这个关键的元数据结构设计得很紧凑, 因而一个有 4G 内存的 Namenode 足够支撑大量的文件和目录. 当 Namenode 启动时, 它从硬盘中读取 Edits 和 FsImage, 将所有 Edits 中的事务作用在内存中的 FsImage 上, 并将这个新版本的 FsImage 从内存中保存到本地磁盘上, 然后删除旧的 Edits, 因为这个旧的 Edits 的事务都已经作用在 FsImage 上了. 这个过程称为一个检查点(checkpoint).
Datanode 将 HDFS 数据以文件的形式存储在本地的文件系统中, 它并不知道有关 HDFS 文件的信息. 它把每个 HDFS 数据块存储在本地文件系统的一个单独的文件中. Datanode 并不在同一个目录创建所有的文件, 实际上, 它用试探的方法来确定每个目录的最佳文件数目, 并且在适当的时候创建子目录. 在同一个目录中创建所有的本地文件并不是最优的选择, 这是因为本地文件系统可能无法高效地在单个目录中支持大量的文件. 当一个 Datanode 启动时, 它会扫描本地文件系统, 产生一个这些本地文件对应的所有 HDFS 数据块的列表, 然后作为报告发送到 Namenode, 这个报告就是块状态报告.
管理文件系统命名空间
HDFS 支持传统的分层文件组织. 用户或应用程序可以在这些目录中创建目录和存储文件. 文件系统命名空间层次结构与大多数其他现有文件系统类似: 可以创建和删除文件, 将文件从一个目录移动到另一个目录, 或重命名文件. HDFS 支持用户配额和访问权限. 但不支持硬链接或软链接.
NameNode 维护文件系统命名空间. 对文件系统命名空间或其属性的任何更改由 NameNode 记录. 应用程序可以指定应由 HDFS 维护的文件的副本数. 文件的副本数称为该文件的复制因子. 此信息由 NameNode 存储.
SecondaryNameNode
它不是 NameNode 的备份, 但可以作为 NameNode 的备份, 当因为断电或服务器损坏的情况, 可以用 SecondNameNode 中已合并的 fsimage 文件作为备份文件恢复到 NameNode 上, 但是很有可能丢失掉在合并过程中新生成的 edits 信息. 因此不是完全的备份.
由于 NameNode 仅在启动期间合并 fsimage 和 edits 文件, 因此在繁忙的群集上, edits 日志文件可能会随时间变得非常大. 较大编辑文件的另一个副作用是下一次重新启动 NameNode 需要更长时间. SecondNameNode 的主要功能是帮助 NameNode 合并 edits 和 fsimage 文件, 从而减少 NameNode 启动时间.
SNN 执行合并时机
根据配置文件配置的时间间隔 fs.checkpoint.period 默认 1 小时;
dfs.namenode.checkpoint.txns, 默认设置为 1 百万, 也就是 Edits 中的事务条数达到 1 百万就会触发一次合并, 即使未达到检查点期间.
SNN 合并流程
snn 合并流程. PNG
首先生成一个名叫 edits.new 的文件用于记录合并过程中产生的日志信息;
当触发到某一时机时 (时间间隔达到 1 小时或 Edits 中的事务条数达到 1 百万) 时 SecondaryNamenode 将 edits 文件, 与 fsimage 文件从 NameNode 上读取到 SecondNamenode 上;
将 edits 文件与 fsimage 进行合并操作, 合并成一个 fsimage.ckpt 文件;
将生成的合并后的文件 fsimage.ckpt 文件转换到 NameNode 上;
将 fsimage.ckpt 在 NameNode 上变成 fsimage 文件替换 NameNode 上原有的 fsimage 文件, 并将 edits.new 文件上变成 edits 文件替换 NameNode 上原有的 edits 文件.
SNN 在 hadoop2.x 及以上版本在非高可用状态时还存在, 但是在 hadoop2.x 及以上版本高可用状态下 SNN 就不存在了, 在 hadoop2.x 及以上版本在高可用状态下, 处于 standby 状态的 NameNode 来做合并操作.
DataNode
管理附加到它们运行的节点的存储, 并允许用户数据存储在文件中;
在内部, 文件被分割成一个或多个块(Block), 并且这些块被存储在一组 DataNode 中;
负责提供来自文件系统客户端的读取和写入请求;
执行块创建, 删除;
启动 DN 进程的时候会向 NN 汇报 Block 信息;
通过向 NN 发送心跳保持与其联系(3 秒一次), 如果 NN10 分钟没有收到 DN 的心跳, 则认为 DN 已经丢失, 并且复制其上的 Block 到其他的 DN 上.
HDFS 存储单元(block)
文件被切分成固定大小的数据块
默认数据块大小为 64MB(hadoop1.x),128MB(hadoop2.x),256MB(hadoop3.x), 可配置;
若文件大小不到一个块大小, 则单独存成一个 block,block 块是一个逻辑意义上的概念. 文件大小是多少, 就占多少空间.
一个文件存储方式
按大小被切分成不同的 block, 存储到不同的节点上;
默认情况下, 每个 block 都有 3 个副本;
block 大小与副本数通过 client 端上传文件时设置, 文件上传成功后副本数可以变更, block size 不可变更.
将大文件拆分成 256MB 的 block 块, 每个 block 块分别随机存放在不同的节点上, 从而避免了数据倾斜的问题, 但是在开发过程中, 如果算法, 程序写的不好, 同样也会出现数据倾斜的问题.
数据复制
数据复制概述
HDFS 被设计成能够在一个大集群中跨机器可靠地存储超大文件. 它将每个文件存储成一系列的数据块, 除了最后一个, 所有的数据块都是同样大小的. 为了容错, 文件的所有数据块都会有副本. 每个文件的数据块大小和副本系数都是可配置的. 应用程序可以指定某个文件的副本数目. 副本系数可以在文件创建的时候指定, 也可以在之后改变. HDFS 中的文件都是一次性写入的, 并且严格要求在任何时候只能有一个写入者.
Namenode 全权管理数据块的复制, 它周期性地从集群中的每个 Datanode 接收心跳信号和块状态报告(Blockreport). 接收到心跳信号意味着该 Datanode 节点工作正常. 块状态报告包含了一个该 Datanode 上所有数据块的列表.
Block 的副本放置策略
副本的存放是 HDFS 可靠性和性能的关键.
在大多数情况下, 副本系数是 3,HDFS 的存放策略是将一个副本存放在本地机架的节点上, 一个副本放在同一机架的另一个节点上, 最后一个副本放在不同机架的节点上. 这种策略减少了机架间的数据传输, 这就提高了写操作的效率. 机架的错误远远比节点的错误少, 所以这个策略不会影响到数据的可靠性和可用性. 于此同时, 因为数据块只放在两个 (不是三个) 不同的机架上, 所以此策略减少了读取数据时需要的网络传输总带宽. 在这种策略下, 副本并不是均匀分布在不同的机架上. 三分之一的副本在一个节点上, 三分之二的副本在一个机架上, 其他副本均匀分布在剩下的机架中, 这一策略在不损害数据可靠性和读取性能的情况下改进了写的性能.
副本选择
为了降低整体的带宽消耗和读取延时, HDFS 会尽量让读取程序读取离它最近的副本. 其计算方式大致如下:
相同节点 = 0
相同机架不同节点 = 2
相同数据中心不同机架 = 4
不同数据中心 = 6
如果在读取程序的同一个机架上有一个副本, 那么就读取该副本. 如果一个 HDFS 集群跨越多个数据中心, 那么客户端也将首先读本地数据中心的副本.
安全模式
NameNode 在启动的时候会进入一个称为安全模式的特殊状态, 它首先将映像文件 (fsimage) 载入内存, 并执行编辑日志 (edits) 中的各项操作; 一旦在内存中成功建立文件系统元数据映射, 则创建一个新的 fsimage 文件 (这个操作不需要 SecondNameNode 来做) 与一个空的编辑日志;
此刻 namenode 运行在安全模式, 即 namenode 的文件系统对于客户端来说是只读的, 显示目录, 显示文件内容等, 写, 删除, 重命名都会失败;
在此阶段 namenode 搜集各个 datanode 的报告, 当数据块达到最小副本数以上时, 会被认为是 "安全" 的, 在一定比例的数据块被认为是安全的以后(可设置), 再过若干时间, 安全模式结束;
当检测到副本数不足数据块时, 该块会被复制, 直到达到最小副本数, 系统中数据块的位置并不是由 namenode 维护的, 而是以块列表形式存储在 datanode 中.
特别的 -- 当 namenode 节点的磁盘满了, HDFS 也会进入安全模式, 并且此时无法退出安全模式
数据组织
数据块
HDFS 被设计成支持大文件, 适用 HDFS 的是那些需要处理大规模的数据集的应用. 这些应用都是只写入数据一次, 但却读取一次或多次, 并且读取速度应能满足流式读取的需要. HDFS 支持文件的 "一次写入多次读取" 语义. 一个典型的数据块大小是 256MB. 因而, HDFS 中的文件总是按照 256M 被切分成不同的块, 每个块尽可能地存储于不同的 Datanode 中.
分段
客户端创建文件的请求其实并没有立即发送给 Namenode, 事实上, 在刚开始阶段 HDFS 客户端会先将文件数据缓存到本地的一个临时文件. 应用程序的写操作被透明地重定向到这个临时文件. 当这个临时文件累积的数据量超过一个数据块的大小, 客户端才会联系 Namenode.Namenode 将文件名插入文件系统的层次结构中, 并且分配一个数据块给它. 然后返回 Datanode 的标识符和目标数据块给客户端. 接着客户端将这块数据从本地临时文件上传到指定的 Datanode 上. 当文件关闭时, 在临时文件中剩余的没有上传的数据也会传输到指定的 Datanode 上. 然后客户端告诉 Namenode 文件已经关闭. 此时 Namenode 才将文件创建操作提交到日志里进行存储. 如果 Namenode 在文件关闭前宕机了, 则该文件将丢失.
上述方法是对在 HDFS 上运行的目标应用进行认真考虑后得到的结果. 这些应用需要进行文件的流式写入. 如果不采用客户端缓存, 由于网络速度和网络堵塞会对吞估量造成比较大的影响. 这种方法并不是没有先例的, 早期的文件系统, 比如 AFS, 就用客户端缓存来提高性能. 为了达到更高的数据上传效率, 已经放松了 POSIX 标准的要求.
管道复制
当客户端向 HDFS 文件写入数据的时候, 一开始是写到本地临时文件中. 假设该文件的副本系数设置为 3, 当本地临时文件累积到一个数据块的大小时, 客户端会从 Namenode 获取一个 Datanode 列表用于存放副本. 然后客户端开始向第一个 Datanode 传输数据, 第一个 Datanode 一小部分一小部分 (4 KB) 地接收数据, 将每一部分写入本地仓库, 并同时传输该部分到列表中第二个 Datanode 节点. 第二个 Datanode 也是这样, 一小部分一小部分地接收数据, 写入本地仓库, 并同时传给第三个 Datanode. 最后, 第三个 Datanode 接收数据并存储在本地. 因此, Datanode 能流水线式地从前一个节点接收数据, 并在同时转发给下一个节点, 数据以流水线的方式从前一个 Datanode 复制到下一个.
如果复制过程中某个 DataNode 出现问题, 并不会导致该次写入过程失败, 问题 DataNode 将排出这个管道, 其他节点正常写入, 只要有(dfs.namenode.repliction.min 默认为 1), 个节点写入成功, 那么本次写入过程就是成功的. 后续 NameNode 在检查文件副本数的时候, 会帮助恢复正常
读写流程
读数据流程
hdfs 读数据流程. jpg
使用 HDFS 提供的客户端 Client, 通过 DistributedFileSystem 向远程的 Namenode 发起 RPC 请求;
Namenode 会视情况返回文件的部分或者全部 block 列表, 对于每个 block, Namenode 都会返回有该 block 副本的 DataNode 地址;
客户端 Client 会选取离客户端最近的 DataNode 来读取 block; 如果客户端本身就是 DataNode, 那么将从本地直接获取数据;
读取完当前 block 的数据后, 关闭当前的 DataNode 链接, 并为读取下一个 block 寻找最佳的 DataNode;
当读完列表 block 后, 且文件读取还没有结束, 客户端会继续向 Namenode 获取下一批的 block 列表;
以上这些步骤对于客户端来说都是透明的. 客户端只需通过 DistributedFileSystem 返回的 FSDataInputStream 读取数据即可
特别的 -- 如果客户端和所连接的 DataNode 在读取时出现故障, 那么它就会去尝试连接存储这个块的下一个最近的 DataNode, 同时它会记录这个节点的故障, 以免后面再次连接该节点. 客户端还会验证从 DataNode 传送过来的数据校验和. 如果发现一个损坏块, 那么客户端将再尝试从别的 DataNode 读取数据块, 并且会告诉 NameNode 这个信息, NameNode 也会更新保存的文件信息, 进行数据修复.
写数据流程
hdfs 写流程. jpg
客户端调用 create 来新建文件.
DistributedFileSystem 通过 RPC 调用在 NameNode 的文件系统命名空间中创建一个新文件, 此时还没有相关的 DataNode 与之相关.
NameNode 会通过多种验证保证新的文件不存在文件系统中, 并且确保请求客户端拥有创建文件的权限. 当所有验证通过时, NameNode 会创建一个新文件的记录, 如果创建失败, 则抛出一个 IOException 异常; 如果成功 namenode 能够掌握集群 DataNode 整体状况, 分配数据块后, 连同 DataNode 列表信息返回给客户端;
当客户端写入数据时, DFSOutputStream 会将文件分割成数据包, 然后放入一个内部队列, 我们称为 "数据队列(data queue)".DataStreamer 会将这些小的文件包放入数据流中, DataStreamer 的作用是请求 NameNode 为新的文件包分配合适的 DataNode 存放副本. 返回的 DataNode 列表形成一个 "管道", 假设这里的副本数是 3, 那么这个管道中就会有 3 个 DataNode.DataStreamer 将文件包以流的方式传送给队列中的第一个 DataNode. 第一个 DataNode 会存储这个包, 然后将它推送到第二个 DataNode 中, 随后照这样进行, 直到管道中的最后一个 DataNode.
DFSOutputStream 同时也会保存一个包的内部队列, 用来等待管道中的 DataNode 返回确认信息, 这个队列被称为确认队列(ask queue). 只有当所有的管道中的 DataNode 都返回了写入成功的信息文件包, 才会从确认队列中删除.
客户端完成数据写入后, 对数据流调用 close 方法. 该操作将剩余的所有数据写入 dataNode 管线, 并联系到 namenode 告知其文件写入管道完成, 等待确认.
特别的 -- 当出现写入某个 DataNode 失败时, HDFS 会作出以下反应:
首先管道会被关闭, 任何在 确认队列 中的文件包都会被添加到数据队列的前端, 以确保故障节点下游的 datanode 不会漏掉任何一个数据包.
为存储在另一个正常的 datanode 的当前数据块指定一个新的标识, 并将标识传送给 nameNode, 当 dataNode 在恢复后, 会发现其原来的标识是过时的, 于是就可以删除存储的不完整的那部分数据块.
从管线中删除故障 datanode, 基于两个正常的 datanode 构建新的管线. 余下的数据库写入管线中正常的 datanode.
namenode 在注意到副本不足时, 会在另一个节点上创建一个新的副本. 后续的数据块继续正常的接受处理.
如果有多个节点的写入失败了, 如果满足了最小备份数的设置(dfs.namenode.repliction.min), 写入也将会成功
写入一致性
新建一个文件后, 它能够在文件系统命名空间中立即可见
写入文件的内容不保证立即可见 (即逝数据流已经调用 flush() 方法刷新并存储)
当前正在写入的块对其他 reader 不可见.
通过 hflush()方法后, 数据被写入 datanode 的内存中. 可保证对所有 reader 可见
通过 hsync()方法后, 数据被写入到磁盘上.
如果没有调用 hflush 或者 hsync()方法. 客户端在故障的情况下就会存在数据块丢失
后记
本文主要是记录了个人在复习过程看到的一些知识点, 可能有点东拼西凑的感觉, 但是作为复习来看看还是不错的. 当然关于 HDFS 肯定不止这么一点东西, 不过作为开发, 运维相关的其实很少用到, 不过还是有一些需要补充的, 比如: 高可用, HDFS shell 相关... 诶, 其实前面有几篇文章说后续会补上, 但是因为各种原因, 还在那里欠着, 还好没人计较, 也就是自己在自娱自乐... 哈哈
来源: http://www.jianshu.com/p/f7fc7efa481a