HDFS-hadoop的文件系统

hadoop 的文件存储系统成为 hdfs(hadoop distributed file system),它用于解决大数据的存储问题,可以管理 分布在多台服务器中数据。

1. 概述

它具有如下的 优缺点

优点:

  • 多副本存储文件,具有高容错性;
  • 分布式存储,适合处理大数据, 并且可将集群构建在廉价机器上。

缺点:

  • 不适合低延迟的数据访问;
  • 无法高效的对大量小文件进行存储(占用 NameNode 大量的内存来存储文件目录和块信息,并且小文件存储的寻址时间会超过读取时间,违反了 hdfs 的设计目标);
  • 不支持并发写入,文件随机修改。

HDFS主要由下面几个部分 组成

  • NameNode: 充当整个集群文件系统的目录功能,存储 文件的元数据信息(metadata, 包括文件名,副本数,权限等)。此外,它还作为整个集群文件的管理者, 管理数据块的映射信息, 处理来自客户端 的读写请求等。
  • Secondary NameNode: 它并非 NameNode 的热备(就是说 NameNode 如果挂掉,它并不能马上替换 NameNode 并提供服务)。它的主要工作是辅助 NameNode的工作,包括定期合并 FsimageEdits 文件,并推送给 NameNode。在紧急情况下,可用于恢复部分 NameNode 内容。
  • DataNode: 负责实际的数据存储。
  • Client: 可请求数据读写。

HDFS 数据 存储块的大小 默认为 128 M(源码中在 hdfs-default.xml 文件中通过参数 dfs.blocksize),该值主要取决于 系统磁盘读写的速率。

一般地认为,寻址时间为传输时间 1% 为最佳状态,如寻址时间约为 10ms,则可计算出最佳的传输时间为约 1s。若当前磁盘传输速率普遍为 100 MB/s,一个块文件需要一次寻址,则可获得 HDFS 块的推荐大小:1s * 100MB/s = 100 MB,近似为 128 M。所以,如果磁盘读写速度快(如 SSD),则推荐使用更大的 HDFS 块大小。

2. HDFS的数据读写

2.1 shell 操作

HDFS 提供相关的指令对 文件系统中的文件进行管理(如上传、下载、删除等)。具体可以使用 bin/hadoop fs 具体命令 or bin/hdfs dfs 具体命令,这两个指令差不多, hadoop fs 相当于是 hdfs dfs 的 “父类”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
hadoop fs -help rm # 查询 rm 指令的功能

hadoop fs -ls / # 查看根目录下的内容
hadoop fs -lsr / # 递归显示 根目录下的内容
hadoop fs -mkdir -p /user/lab1 # 穿件目录,创建多级目录需要 添加 -p
hadoop fs -moveFromLocal ${本地文件} ${hdfs目录} # 剪切文件
hadoop fs -copyFromLocal ${本地文件} ${hdfs目录} # 复制文件 -- 或者使用 -put
hadoop fs -copyToLocal ${hdfs文件} ${本地目录} # 从 hdfs 中下载文件到本地,或者使用 -get
hadoop fs -appendToFile ${追加的文件} ${hdfs中的文件目录}

hadoop fs -chgrp ${组名} # 修改组

hadoop fs -cp ${hdfs中原文件} ${hdfs中目的地址} # 拷贝
hadoop fs -mv ${hdfs中原文件} ${hdfs中目的地址} # 剪切
hadoop fs -getmerge ${待合并的文件内容} ${合并后的文件名} # 合并文件

hadoop fs -tail ${文件名} # 查看文件的最后一些行 -- log 文件常常以追加的形式更新,所以这个很常用
hadoop fs -rm ${文件名/文件夹} # 删除文件、文件夹, -R 参数,递归删除。
hadoop fs -du ${目录} # 查询对应目录的内容的大小,添加 -h 参数,添加单位例如 M;-s 参数,统计整个文件夹总大小

# 副本设置
hadoop fs -setrep ${副本数} ${文件名}

整体来说,hdfs 的操作指令与 linux 文件的操作非常类似。

2.2 客户端操作

可以在 windows 上建立客户端,然后通过 java 程序来操作 hdfs 的文件。首先需要在 windows 上搭建 hadoop 环境,参考 link

配置完毕后,可以使用 intellij 新建一个 maven 工程,具体可以参考 github 中的 代码

使用客户端可以进行对 hdfs 进行读写。其中 写数据 步骤如下:

  1. 客户端创建 distributed FileSystem 对象(含有上传到 hdfs 系统中的路径信息),向 NameNode 所在的服务器发送上传请求;
  2. NameNode 检查文件路径等信息,进行一些检查:例如文件是否存在,路径是否合法等。检查完毕后响应客户端的请求;
  3. 客户端得到可以上传的响应信息后,请求上传第一个 block;
  4. NameNode 收到该请求后进行分析,返回给客户端上传数据的 DataNode,e.g. dn1, dn2, dn3。数据结点的确定主要根据两个因素:1. 距离近的有限上传;2. 负载轻的优先上传;
  5. 客户端生成输出流 FSDataOutputStream,向对应的 DataNode 请求建立 block 的传输通道;
  6. DataNode 应答成功,客户端进行数据传输(一个个的 packets);
  7. DataNode 服务器中首先将数据放置在内存中,一边序列化到本地硬盘,一边传输到下个结点的内存中。

在上面的第四步中,NameNode 需要寻找存储数据的结点,这一结点的确定遵循距离最近的原则,即上传数据的结点与存储数据的结点距离尽可能近。这里,就需要定义 结点距离:为两个结点到达最近的公共祖先的距离总和。例如:

  • 同一结点上的进程:距离 = 0;
  • 同一机架上的不同结点(共一个端结点):距离 = 2;
  • 同一数据中心不同机架上的结点(同一数据中心的机架端结点接到同一个数据中心结点上):距离 = 4。

除了上述的距离最近原则,具体存储数据时 hadoop 还需考虑数据的安全可靠。对于常见的 3副本设置(参考官网),数据的放置一般首先放置在某一随机的结点(尽量考虑距离近,负载均衡),然后第二个副本放置在同一机架的不同 结点上,而第三个副本则放置在不同的机架上(为了安全可靠性)。

类似于写数据,读数据也需要客户端首先与 NameNode 进行通讯,然后再从具体的 DataNode 上读取数据。步骤如下:

  1. 客户端创建 distributed FileSystem 对象(包含 hdfs 文件目录信息),向 NameNode 发送下载请求;
  2. NameNode 返回目标文件的元数据,客户端获得数据存在的 DataNode 位置;
  3. 客户端创建 FSDataInputStream 对象,并逐块向对应的 DataNode 结点请求数据(如果多块文件在同一个服务器上,可以一次性返回);
  4. 获取完毕数据后本地拼接为完整文件。

3. 文件索引系统

NameNodeSecondaryNameNode 共同构成了 HDFS 的文件索引、管理系统。这里对他们的工作机制进行详细的介绍。

3.1 NameNode工作机制

在运行的 HDFS 系统中,NameNode 为实现运行的高效,总是在内存中运行。但内存具有掉电丢失的特点,为了保证数据的可靠性,HDFS 会在在磁盘中使用 FsImage 备份元数据(所谓元数据就是文件属性信息,比如修改日期,权限等)。

但频繁操作存储于磁盘中的数据,会降低系统性能。因此,为减少 FsImage 的修改,当需要更新 元数据时,会 首先 将更新的内容记录到 Edits 文件(也在磁盘中,但只进行追加操作,效率非常高),然后 再在 内存中更新(安全性)。

NameNode 服务器上电时,它将编辑日志(edits_inprogress_xxx)和 镜像文件(Fsimage) 加载到内存中,进行合并,恢复出整个集群的元数据信息 (通常内存至少128 G, 每个 block 占 150 bytes)。

3.2 SecondaryNameNode工作机制

由于 Edits文件 不能无限扩大,再它达到一定大小时,需要与 FsImage 文件进行合并。HDFS 使用 SecondaryNameNode,来实现这一功能。具体地,在 SecondaryNameNode 中,会按顺序执行下列操作:

  1. 请求询问 NameNode 是否需要进行 checkpoint 操作 (即日志和镜像文件合并),当下列两个任一个满足时,会触发 checkpoint:
    1. 定时时间到(默认是1小时,hdfs-site.xml 中配置 property dfs.NameNode.checkpoint.period 修改)
    2. Edits 中的数据条数超过阈值(以轮询方式查询,默认每隔1分钟轮询一次,默认阈值是100万条,hdfs-site.xml 中, dfs.NameNode.checkpoint.txns 配置阈值, dfs.NameNode.checkpoint.check.period 配置轮询时间间隔);
  2. 如果需要 checkpoint 操作,首先将 NameNode 中的 edit_inprogress_001 文件滚动为 edits_001 并同时生成 空的 edit_inprogress_002 文件,后序修改记录将会添加到 edit_inprogress_002 中。
  3. 然后将 edit_001FsImage 文件拷贝到 SecondaryNameNode 中,SecondaryNameNode 在内存中将上述两者进行合并,生成新的 FsImage 文件。
  4. 最后将合并生成的新的 FsImage 文件拷贝回 NameNode 中,替换旧的 Fsimage 文件。

3.3 Edit、FsImage 文件

在 NameNode 服务器上,EditFsImage 文件在 目录 hadoop-2.8.3/data/tmp/dfs/name/current 中。可以使用 hdfs oiv -p XML -i <要转换的fsimage 文件名> -o <输出的文件名.xml> 将 FsImage 转为 xml 文件进行展示。可以看到,在 fsimage 文件中,存放了文件的 文件名路径副本数权限创建时间 等 meta 信息。类似的,可以使用 hdfs oev -p XML -i <要转换的 edits 文件> -o <输出的文件名.xml>Edits 文件转为 xml 格式进行查看。

需要注意的是,FsImage 文件和 Edits 文件 并没有记录 块所对应的 DataNode。在 HDFS 中,这一信息 (DataNode 上的数据块信息) 将会在在集群启动后 由各个 DataNode 进行 动态上报。具体地,在集群启动时,所有 DataNode 会上报一次他们所持有的数据块信息,然后,后序每隔一段时间 DataNode 会再进行上报。这么做是因为数据块的位置信息可能会因为 DataNode 的状态而变化,因此使用动态的方式获取,保证数据位置信息的准确性和实时性。

3.4 集群的安全模式

根据上述的介绍,我们知道了 HDFS 文件索引系统保存更新索引的方式。在这样的机制下,HDFS 系统在 NameNode 启动时,会有一段特殊的安全模式期。

所谓的 安全模式 是指 NameNode 文件索引系统对于客户端来说是只读的,不允许执行写操作。当 HDFS 系统启动,便处于安全模式。

NameNode 首先将镜像文件 FsImage 载入内存,并执行编辑日志(Edits) 中的各项操作。合并完成后,创建一个新的 FsImage 文件和一个空的 编辑日志放回磁盘,同时,NameNode 会开始监听 DataNode 的请求。

对于各个 DataNode,他们以块列表的形式记录 各个存储在它们上的数据块。在系统正常操作期间, NameNode 会在内存中保留所有块的位置映射信息。但在 DataNode 和 NameNode 启动时,需由 DataNode 向 NameNode 发送最新的块列表信息。

最后,当 NameNode 完成 FsImage 的 合并操作,并且接收到 足够多的块位置信息 后,才会退出安全模式(达到最小副本条件后30s 退出安全模式)。

所谓 最小副本条件 是指: 整个文件系统中 99.9% 的块满足最小副本级别的要求(默认值:dfs.replication.min=1)。

最后,在刚刚格式化一个 hdfs 集群的时候,由于系统中还没有任何块,NameNode 不会进入安全模式。

4. 数据结点

DataNode 中存放一个个的 blocks, 数据 block 的内容包括:数据,数据长度,校验和,时间戳。通过上面对 NameNode 的介绍,我们知道数据的存储信息仅放置在 NameNode 内存中(即不做持久化处理),需要 DataNode 进行动态的上报、更新。

具体的,块信息分为 启动时 上报和 运行中 的上报。启动时,DataNode 启动后向 NameNode 注册,报告其中的块信息,注册成功后,NameNode 向 DataNode 返回注册成功信息。运行中, DataNode 会每隔固定时间(1小时)上报一次所有块信息。

4.1 可用性判断

除了数据块信息的上报,NameNode 需要知道 DataNode 的 可用性,即判断相关的 DataNode 网络是否顺畅连通,硬件是否运行正常。这一目标由 DataNode 和 NameNode 之间保持的 心跳通讯 来实现。DataNode 心跳每3秒1次,心跳返回结果带有 NameNode 给该 DataNode 的命令。

通常,如果超过 10 分钟(+30s)没有收到 DataNode 的心跳,NameNode 会认为该结点不可用,从而以后不会再向该 DataNode 中存放任何内容。

上述的 DataNode 的延迟下线时间称为 超时时长。hdfs 默认的超时时长 10分钟 + 30 秒 通过公式 timeout = 2 * dfs.NameNode.heartbeat.recheck-interval + 10 * dfs.heartbeat.interval 计算。其中 dfs.NameNode.heartbeat.recheck-interval 默认值为 5分钟(300’00 ms),dfs.heartbeat.interval 默认值为 3秒。

4.2 服役新节点、退役结点

如果想向集群中添加新的 datanode,只需要镜像集群中的一台 datanode 机器,修改 ip 和主机名称,并删除其中的旧数据 和 旧 logs: rm -rf /data /logs。最后在新的结点上单独启动 DataNode 以及 nodemanager,即可自动并入集群中。

如果想提升安全性,仅让有权限的机器加入到集群中,可以使用 “白名单” 功能(hdfs-site.xml 中添加 dfs.hosts 属性)。此外,还可以使用黑名单方式,强制集群中的某些服务器退出(退出结点上的内容会被拷贝到其他节点上)。

5. 其他

5.1 存档文件

小文件会影响 hdfs 系统的性能。因为每个小文件都占用一个文件块,而每个文件块都需要在 NameNode 中占有一条记录,故每个小文件都会占用 150 字节的记录空间(注意:不影响DataNode,数据实际存储空间仍是文件大小)。因此,有大量小文件时,会大量占用 NameNode 中的索引资源。

一种处理方案是 使用 hdfs 存档文件 或称 har 文件。原理上,hdfs 存档文件对内是一个个独立文件,但是对 NameNode 而言却是一个整体,因为减少了在 NameNode 中内存的占用。

1
2
3
hadoop archive -archivename <归档后的名字-.har 结尾> -p <src文件的路径> <输出文件的路径>
# 可以查看 har 存档文件,相当于一个单独的文件系统
hadoop fs -ls -R har://<har 文件路径>

5.2 快照管理

HDFS 支持快照备份。但 HDFS 快照并不会立即复制所有的文件,在产生快照时,仅对目录做一个复制备份,只有当后序有写入、修改发生时,才会产生新的文件。

1
2
3
4
5
6
# 开启指定目录的快照功能
hdfs dfsadmin -allowSnapshot <路径>
# 对目录创建快照
hdfs dfs -createSnapshot <路径>
# 比较两个快照目录的不同之处
hdfs snapshotDiff <路径1> <路径2>

参考

  1. 尚硅谷Hadoop 2.x教程(hadoop框架精讲):https://www.bilibili.com/video/BV1cW411r7c5