内容:HDFS介绍、HDFS原理

HDFS特点

1、典型的 Master/Slave 架构

​ NameNode是集群的主节点,DataNode是集群的从节点。

​ HDFS集群往往是一个NameNode(HA架构会有两个NameNode,联邦机制)+ 多个DataNode组成。

2、分块存储(block机制)

​ HDFS 中的文件在物理上是分块存储(block)的,块的大小可以通过配置参数来规定。

​ Hadoop2.x版本中默认的block大小是128M;

3、副本机制

​ 为了容错,文件的所有 block 都会有副本。

​ 每个文件的 block 大小和副本系数都是可配置的。应用 程序可以指定某个文件的副本数目。副本系数可以在文 件创建的时候指定,也可以在之后改变。 副本数量默认是3个。

4、一次写入,多次读出

​ HDFS 是设计成适应一次写入,多次读出的场景,且不支持文件的随机修改。支持追加写入,不支持随机更新

​ 正因为如此,HDFS 适合用来做大数据分析的底层存储服务,并不适合用来做网盘等应用(修改不 方便,延迟 大,网络开销大,成本太高)

HDFS架构

  • 客户端:
    • 上传文件到HDFS的时候,Client负责将文件切分成Block,然后进行上传
    • 请求NameNode交互,获取文件的位置信息
    • 读取或写入文件,与DataNode交互
    • Client可以使用一些命令来管理HDFS或者访问HDFS
  • NameNode:
    • 维护管理Hdfs的名称空间(NameSpace)
    • 维护副本策略
    • 记录文件块(Block)的映射信息
    • 负责处理客户端读写请求
  • DataNode:
    • 保存实际的数据块
    • 负责数据块的读写
  • SecondaryNameNode
    • 备份NameNode中的元数据信息
    • 提高NameNode的重启速度
    • 必要的时候可作为新的NameNode

HDFS命令

执行位置:$HADOOP_HOME/bin

命令头:hdfs dfs\hadoop fs

常用命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- 查看目录:hadoop fs -ls /
-- 创建目录:hadoop fs -mkdir -p /aaa/bbb
-- 从本地移动到HDFS:hadoop fs  -moveFromLocal ./xxx.txt /aaa/bbb
-- 追加一个文件(本地)到已经存在的文件(HDFS)末尾:hadoop fs -appendToFile xxx.txt /aaa/bbb/yyy.txt
-- 显示文件内容:hadoop fs -cat /aaa/bbb/yyy.txt
-- 修改文件所属权限: (Linux文件系统中的用法一样)
hadoop fs  -chmod  666 /lagou/bigdata/hadoop.txt
hadoop fs  -chown root:root
-- 从本地文件系统中拷贝文件到HDFS路径去:hadoop fs -copyFromLocal xxx.txt /aaa/bbb/
-- 从HDFS拷贝到本地: hadoop fs -copyToLocal /aaa/bbb/yyy.txt ./
-- 从HDFS的一个路径拷贝到HDFS的另一个路径:hadoop fs -cp /aaa/bbb/yyy.txt /zzz.txt
-- 在HDFS目录中移动文件:hadoop fs -mv /yyy.txt /aaa/bbb/
-- 从HDFS下载文件到本地:(等同于copyToLocal) hadoop fs -get /aaa/bbb/yyy.txt ./
-- 从本地上传到HDFS:(等同于copyFromLocal) hadoop fs -put ./xxx.txt /aaa/bbb/
-- 显示一个文件的末尾:hadoop fs -tail /aaa/bbb/xxx.txt
-- 删除文件或文件夹:hadoop fs -rm /aaa/bbb/xxx.txt
-- 删除空目录:hadoop fs -rmdir /ccc
-- 统计文件夹的大小信息:hadoop fs -du -h /aaa/bbb/
-- 设置HDFS中文件的副本数量:hadoop fs -setrep 10 /aaa/bbb/xxx.txt

HDFS重要概念

block

HDFS3.x上的文件会按照128M为单位切分成一个个的block,分散存储在集群的不同的数据节点datanode上,需要注意的是,这个操作是HDFS自动完成的

机架存储策略

实际机房中,会有若干机架,每个机架上会有若干台服务器

一般来说我们会把一个block的3个副本分别按照下述方法进行存储:

  • 第一个副本就存储在一个机架A上

  • 第二个副本存储在和这个block块不同机架(比如机架B)的一个服务器上

  • 第三个副本存储在机架B的另外一个服务器上(注意副本2,3都存储在了机架B)

1
2
我们存储第2个副本时会优先把副本存储在不同的机架上,这是为了防止出现一个机架断电的情况,如果副本也存储在同机架上的不同服务器上,这时候数据就可能丢失了。
如果我们把副本3也放在另外一个机架C上,副本2和副本3之间的通信就需要副本2通过它的交换机去联系总交换机,然后总交换机去联系机架C的交换机,需要走的路线非常长,而且机房中的带宽资源非常宝贵,如果处于高并发的情况,很容易就把机房的带宽打满,此时整一个集群的响应速度会急剧下降,这时候服务就会出现问题了。

HDFS读写流程

写流程

流程解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
--1.客户端调用HDFS的create方法
调用的是Distributed FileSystem的create方法,通过远程调用NameNode的create方法,此时NameNode会进行的举措
1.检测自己是否正常运行
2.判断要创建的文件是否存在
3.client是否有创建文件的权限
4.对HDFS做状态的更改需要在edits log写日志记录

--2.客户端调用输出流的write方法
create方法的返回值是一个OutputStream对象,为什么是output,因为是由HDFS去往DataNode去写数据,此时HDFS会调用这个OutputStream的write方法
但是有个问题,此时我们还不知道我们的这些block块要分别存放于哪些节点上,所以此时FSData OutputStream就要再和NameNode交互一下,远程过程调用NameNode的addBlock方法,这个方法返回的是各个block块分别需要写在哪3个DataNode上面。
此时OutputStream就完整得知了数据和数据该往哪里去写了

--3.具体的写流程分析
流程4.1,chunk是一个512字节大小的数据块,写数据的过程中数据是一字节一字节往chunk那里写的,当写满一个chunk后,会计算一个checkSum,这个checkSum是4个字节大小,计算完成后一并放入chunk,所以整一个chunk大小其实是512字节+4字节=516字节。
上述步骤结束后,一个chunk就会往package里面放,package是一个64kb大小的数据包,我们知道64kb = 64 * 1024字节,所以这个package可以放非常多的chunk。
此时一个package满了之后,会把这个packjage放到一个data queue队列里面,之后会陆续有源源不断的package传输过来,图中用p1,p2···等表示,这时候开始真正的写数据过程

data queue中的package往数据节点DataNode上传输,传输的顺序按照NameNode的addBlock()方法返回的列表依次传输
(ps:传输的类为一个叫做dataStreamer的类,而且其实addBlock方法返回的列表基本是按照离客户端物理距离由近到远的顺序的)
往DataNode上传输的同时也往确认队列ack queue上传输针对DataNode中传输完成的数据做一个checkSum,并与原本打包前的checkSum做一个比较,校验成功,就从确认队列ack queue中删除该package,否则该package重新置入data queue重传

补充:
1.以上逻辑归属于FSData OutputStream的逻辑
2.虽然本身一个block为128M,而package64Kb,128M对于网络传输过程来说算是比较大,拆分为小包是为了可靠传输
3.网络中断时的举措:HDFS会先把整个pineline关闭,然后获取一个已存在的完整的文件的version,发送给NameNode后,由NameNode通过心跳机制对未正确传输的数据下达删除命令
4.如果是某个DataNode不可用,在1中我们也提到过了,通过心跳机制会通知其余的可用DataNode的其中一个进行copy到一个可用节点上

--4.写入结束后的行动
完成后通过心跳机制NameNode就可以得知副本已经创建完成,再调用addBlock()方法写之后的文件。

--5.流程总结
1.client端调用Distributed FileSystem的create,此时是远程调用了NameNode的create,此时NameNode进行4个操作,检测自己是否正常,文件是否存在,客户端的权限和写日志
2.create的返回值为一个FSData OutputStream对象,此时client调用流的write方法,和NameNode进行连接,NameNode的addBlock方法返回块分配的DataNode列表
3.开始写数据,先写在chunk,后package,置入data queue,此时两个操作,向DataNode传输,和放入ackqueue,DataNode传输结束会检测checkSum,成功就删除ack queue的package,否则放回data queue重传
4.结束后关闭流,告诉NameNode,调用complete方法结束

读流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
--1.HDFS client调用文件系统的open方法
通过RPC的方式远程过程调用NameNode里的open方法,获取要读的文件的file block locations,也就是文件的block的位置。
同时在执行open方法时,客户端会产生一个FSData InputStream的一个输入流对象(客户端读数据是从外部读回来的)
--2.FSData InputStream读数据
HDFS client调用FSData InputStream的read方法,同上也是远程过程调用DataNode的read方法,此时的读取顺序是由近到远,就是DataNode和client node的距离,这里所指的距离是一种物理距离,判定可以参考上一篇文章
中机架的概念。
在联系上DataNode并成功读取后,关闭流就走完了一个正常的流程。
而且补充一下就是,上面Distributed FileSystem所调用的get block locations的方法只会返回部分数据块,get block locations会分批次地返回block块的位置信息。读block块理论上来说是依次读,当然也可以通过多线程的方式实现同步读。
--3.容错机制
1. 如果client从DataNode上读取block时网络中断了如何解决?
此时我们会找到block另外的副本(一个block块有3个副本,上一篇已经说过了),并且通过FSData InputStream进行记录,以后就不再从中断的副本上读了。
2. 如果一个DataNode挂掉了怎么办?
在上一篇中我们提到了一个HDFS的心跳机制,DataNode会隔一小时向NameNode汇报blockReport,比如现在的情况是,block1的三个副本分别存储在DataNode1,2,3上,此时DataNode1挂掉了。NameNode得知某个block还剩2个副本,此时携带这block的其余两个副本的DataNode2,3在向NameNode报告时,NameNode就会对它们中的某一个返回一个指令,把block1复制一份给其他正常的节点。让block1恢复成原本的3个副本。
3. client如何保证读取数据的完整性
因为从DataNode上读数据是通过网络来读取的,这说明会存在读取过来的数据是不完整的或者是错误的情况。DataNode上存储的不仅仅是数据,数据还附带着一个叫做checkSum检验和(CRC32算法)的概念,针对于任何大小的数据块计算CRC32的值都是324个字节大小。此时我们的FSData InputStream向DataNode读数据时,会将与这份数据对应的checkSum也一并读取过来,此时FSData InputStream再对它读过来的数据做一个checkSum,把它与读过来的checkSum做一个对比,如果不一致,就重新从另外的DataNode上再次读取。
然后,FSData InputStream会告诉NameNode,这个DataNode上的这个block有问题了,NameNode收到消息后就会再通过心跳机制通知这个DataNode删除它的block块,然后再用类似2的做法,让正常的DataNode去copy一份正常的block数据给其它节点,保证副本数为3

HDFS的元数据管理机制

元数据概述

HDFS元数据,按类型分,主要包括以下几个部分:

  • 文件、目录自身的属性信息,例如文件名,目录名,修改信息等。

  • 文件记录的信息的存储相关的信息,例如存储块信息,分块情况,副本个数等。

  • 记录 HDFS 的 Datanode 的信息,用于 DataNode 的管理。

按形式分为内存元数据和元数据文件两种,分别存在内存和磁盘上。

HDFS 磁盘上元数据文件分为两类,用于持久化存储:

fsimage 镜像文件:是元数据的一个持久化的检查点,包含 Hadoop 文件系统中的所有目录和文件元数据信息,但不包含文件块位置的信息。文件块位置信息只存储在内存中,是在 datanode 加入集群的时候,namenode 询问 datanode 得到的,并且间断的更新。

Edits 编辑日志:存放的是 Hadoop 文件系统的所有更改操作(文件创建,删除或修改)的日志,文件系统客户端执行的更改操作首先会被记录到 edits 文件中。

1
2
3
fsimage 和 edits 文件都是经过序列化的,在 NameNode 启动的时候,它会将 fsimage文件中的内容加载到内存中,之后再执行 edits 文件中的各项操作,使得内存中的元数据和实际的同步,存在内存中的元数据支持客户端的读操作,也是最完整的元数据。
  当客户端对 HDFS 中的文件进行新增或者修改操作,操作记录首先被记入 edits 日志文件中,当客户端操作成功后,相应的元数据会更新到内存元数据中。因为 fsimage 文件一般都很大(GB 级别的很常见),如果所有的更新操都往 fsimage 文件中添加,这样会导致系统运行的十分缓慢。
  HDFS 这种设计实现着手于:一是内存中数据更新、查询快,极大缩短了操作响应时间;二是内存中元数据丢失风险颇高(断电等),因此辅佐元数据镜像文件(fsimage)+编辑日志文件(edits)的备份机制进行确保元数据的安全。

NameNode 维护整个文件系统元数据。因此,元数据的准确管理,影响着 HDFS 提供文件存储服务的能力。

元数据目录相关文件

在 Hadoop 的 HDFS 首次部署好配置文件之后,并不能马上启动使用,而是先要对文件系统进行格式化。需要在 NameNode(NN)节点上进行如下的操作:

1
$HADOOP_HOME/bin/hdfs namenode –format

在这里要注意两个概念,一个是文件系统,此时的文件系统在物理上还不存在;二就是此处的格式化并不是指传统意义上的本地磁盘格式化,而是一些清除与准备工作。

格式化完成之后,将会在$dfs.namenode.name.dir/current 目录下创建如下的文件结构,这个目录也正是 namenode 元数据相关的文件目录:

其中的 dfs.namenode.name.dir 是在 hdfs-site.xml 文件中配置的,默认值如下:

dfs.namenode.name.dir 属性可以配置多个目录,各个目录存储的文件结构和内容都完全一样,相当于备份,这样做的好处是当其中一个目录损坏了,也不会影响到 Hadoop 的元数据,特别是当其中一个目录是 NFS(网络文件系统 Network File System,NFS)之上,即使你这台机器损坏了,元数据也得到保存。

下面对$dfs.namenode.name.dir/current/目录下的文件进行解释。

VERSION

示例:

namespaceID=934548976
clusterID=CID-cdff7d73-93cd-4783-9399-0a22e6dce196
cTime=0
storageType=NAME_NODE
blockpoolID=BP-893790215-192.168.24.72-1383809616115
layoutVersion=-47

  • namespaceID/clusterID/blockpoolID

    这些都是 HDFS 集群的唯一标识符。标识符被用来防止 DataNodes 意外注册到另一个集群中的 namenode 上。这些标识在联邦(federation)部署中特别重要。联邦模式下,会有多个 NameNode 独立工作。每个的 NameNode 提供唯一的命名空间(namespaceID),并管理一组唯一的文件块池(blockpoolID)。clusterID 将整个集群结合在一起作为单个逻辑单元,在集群中的所有节点上都是一样的。

  • storageType

    说明这个文件存储的是什么进程的数据结构信息(如果是 DataNode,storageType=DATA_NODE)

  • cTime

    NameNode 存储系统创建时间,首次格式化文件系统这个属性是 0,当文件系统升级之后,该值会更新到升级之后的时间戳;

  • layoutVersion

    表示 HDFS 永久性数据结构的版本信息,是一个负整数。

  • 补充说明:

    格式化集群的时候,可以指定集群的 cluster_id,但是不能与环境中其他集群有冲突。
    如果没有提供 cluster_id,则会自动生成一个唯一的 ClusterID。

    1
    $HADOOP_HOME/bin/hdfs namenode -format -clusterId <cluster_id>

seen_txid

$dfs.namenode.name.dir/current/seen_txid 非常重要,是存放 transactionId 的文件,format 之后是 0,它代表的是 namenode 里面的 edits_*文件的尾数,namenode 重启的时候,会按照 seen_txid 的数字,循序从头跑 edits_0000001~到 seen_txid 的数字。所以当你的 hdfs 发生异常重启的时候,一定要比对 seen_txid 内的数字是不是你 edits 最后的尾数。

Fsimage & edits

  $dfs.namenode.name.dir/current 目录下在 format 的同时也会生成 fsimage 和 edits文件,及其对应的 md5 校验文件。

SecondaryNamenode的作用

NameNode 职责是管理元数据信息,DataNode 的职责是负责数据具体存储,那么SecondaryNameNode 的作用是什么?对很多初学者来说是非常迷惑的。它为什么会出现在HDFS 中。从它的名字上看,它给人的感觉就像是 NameNode 的备份。但它实际上却不是。

大家猜想一下,当 HDFS 集群运行一段事件后,就会出现下面一些问题:

  • edit logs 文件会变的很大,怎么去管理这个文件是一个挑战。

  • NameNode 重启会花费很长时间,因为有很多改动要合并到 fsimage 文件上。

  • 如果 NameNode 挂掉了,那就丢失了一些改动。因为此时的 fsimage 文件非常旧。

  因此为了克服这个问题,我们需要一个易于管理的机制来帮助我们 减小 edit logs 文件的大小和得到一个最新的fsimage 文件,这样也会减小在 NameNode 上的压力。这跟Windows 的恢复点是非常像的,Windows 的恢复点机制允许我们对 OS 进行快照,这样当系统发生问题时,我们能够回滚到最新的一次恢复点上。

SecondaryNameNode 就是来帮助解决上述问题的,它的职责是合并 NameNode 的 editlogs 到 fsimage 文件中。

Checkpoint

每达到触发条件,会由 secondary namenode 将 namenode 上积累的所有 edits 和一个最新的 fsimage 下载到本地,并加载到内存进行 merge(这个过程称为 checkpoint),如下图所示:

Checkpoint 触发条件

Checkpoint 操作受两个参数控制,可以通过 core-site.xml 进行配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<property>
  <name> dfs.namenode.checkpoint.period</name>
  <value>3600</value>
  <description>
    两次连续的 checkpoint 之间的时间间隔。默认 1 小时
  </description>
</property>
<property>
  <name>dfs.namenode.checkpoint.txns</name>
  <value>1000000</value>
  <description>
    最大的没有执行 checkpoint 事务的数量,满足将强制执行紧急 checkpoint,即使尚未达到检查点周期。默认设置为 100 万。
  </description>
</property>

Checkpoint 详细步骤

  • NameNode 管理着元数据信息,其中有两类持久化元数据文件:edits 操作日志文件和fsimage 元数据镜像文件。新的操作日志不会立即与 fsimage 进行合并,也不会刷到NameNode 的内存中,而是会先写到 edits 中(因为合并需要消耗大量的资源),操作成功之后更新至内存。

  • 有 dfs.namenode.checkpoint.period 和 dfs.namenode.checkpoint.txns 两个配置,只要达到这两个条件任何一个,secondarynamenode 就会执行 checkpoint 的操作。

  • 当触发 checkpoint 操作时,NameNode 会生成一个新的 edits 即上图中的 edits.new 文件,同时 SecondaryNameNode 会将 edits 文件和 fsimage 复制到本地(HTTP GET 方式)。

  • secondarynamenode 将下载下来的 fsimage 载入到内存,然后一条一条地执行 edits 文件中的各项更新操作,使得内存中的 fsimage 保存最新,这个过程就是edits 和fsimage文件合并,生成一个新的 fsimage 文件即上图中的 Fsimage.ckpt 文件。

  • secondarynamenode 将新生成的 Fsimage.ckpt 文件复制到 NameNode 节点。

  • 在 NameNode 节点的 edits.new 文件和 Fsimage.ckpt 文件会替换掉原来的 edits 文件和 fsimage 文件,至此刚好是一个轮回,即在 NameNode 中又是 edits 和 fsimage 文件。

  • 等待下一次 checkpoint 触发 SecondaryNameNode 进行工作,一直这样循环操作。

从上面的描述我们可以看出,SecondaryNamenode 根本就不是 Namenode 的一个热备,其只是将 fsimage 和 edits 合并。其拥有的 fsimage 不是最新的,因为在他从 NameNode 下载 fsimage 和 edits 文件时候,新的更新操作已经写到 edit.new 文件中去了。而这些更新在 SecondaryNamenode 是没有同步到的!当然, 如果 NameNode 中的 fsimage 真的出问题了,还是可以用SecondaryNamenode 中的 fsimage 替换一下NameNode 上的 fsimage ,虽然已经不是最新的 fsimage ,但是我们可以将损失减小到最少!