一, HDFS概述
1.1 HDFS的产生背景和定义
- 随着数据量越来越大,我们需要把文件分布存储到多台计算机上,分布式文件管理系统作为一种管理多台机器上文件的系统应运而生, HDFS是其中的一种.
- HDFS定义:
- Hadoop Distributed File System,
- 通过目录树来定位文件
- 分布式的. 很多服务器联合起来实现功能,集群中的服务器有各自的角色.
- 使用场景: 适合一次写入,多次读取的场景. 一个文件经过创建,写入和关闭后就不需要改变
1.2, HDFS的优缺点:
- 优点:
高容错性(多个副本, 自动恢复)
. 数据自动保存多个副本,通过增加副本的形式,提高容错性. 某一个副本丢失,它可以自动恢复.适合处理大数据(超大文件)
. GB,TB甚至于PB级别的数据或者是百万规模以上的文件数量.可构建在廉价机器上
. 通过多副本机制,提高可靠性.- 缺点:
不适合低延时数据访问
. 比如毫秒级的存储数据,是做不到的, HDFS是为高数据吞吐量应用优化的, 这可能会以提高时间延迟为代价, 目前来说, 对于低延迟的访问需求, HBase是更好的选择.无法高效的对大量小文件进行存储
.
- 存储大量小文件的话,它会占用NameNode大量的内存来存储文件系统的元数据. 由于NameNode的内存总是有限的,所以不可取.
- 小文件存储的寻址时间会超过读取时间,他违反了HDFS的设计目标.
不支持并发(多个用户)写入,也不支持在文件的任意位置进行修改
- 一个文件只允许一个用户写,不允许多个用户同时写
- 仅支持数据追加(append)到文件末尾, 不支持文件的随机修改.
1.3, HDFS组成架构
- NameNode(nn),即master,他是一个管理者.
- NameNode 执行的操作 主要分为三类, 如下表所示:
类别 | 说明 |
1. 维护管理HDFS的命名空间 | 维护整个文件系统的 |
2. 确定数据块-DataNode的映射关系 | client读取数据先访问NameNode, 由NN获取数据所在的DN, 然后client读取目标DN得到数据. 注意: 数据块-DataNode的映射关系是由NameNode根据DataNode上报的文件块信息 |
3. 管理DataNode结点的状态报告 | 包括DataNode的健康状态报道和其所在结点上数据块的状态报告, 以便能够及时处理失效的DataNode |
注意:
- FSImage-文件系统镜像, 在NameNode启动时对整个文件系统的快照;
- edits-编辑日志, 在NameNode启动之后, 记录对文件系统的改动序列;
- DataNode,即Slave,NameNode下达命令.DataNode执行实际的操作, 如下表所示:
类别 | 说明 |
1. 负责实际执行自身结点上 | 一般是文件系统客户端需要请求对NN指定的DataNode结点进行读写操作, DataNode作为数据结点的服务进程与文件系统打交道. |
2. 定期 | 每个DataNode会周期性地向NameNode发送心跳信号和文件块报告(心跳是每3秒一次,心跳返回结果带有namenode给该datanode的命令如复制块数据到另一台机器,或删除某个数据块。如果超过10分钟没有收到某个datanode的心跳,则认为该节点不可用) |
3. 执行 | 当文件系统客户端从NameNode服务器进程获取到要进行复制的数据块列表后, 由DataNode完成文件块和块副本的流水线复制 |
- Client,客户端
- 文件切分.文件上传HDFS的时候,Client将文件切分为一个个的Block,然后进行上传;
- 与NameNode交互,获取文件的位置信息;
- 与DataNode交互,读取或写入数据;
- Client提供一些命令来管理HDFS,比如NameNode格式化;
- Client可以通过一些命令来访问HDFS,比如对HDFS增删改查操作.
- Secondary NameNode: 并非NameNode的热备份,当NameNode挂掉后,并不会马上替换NameNode并提供服务
- 辅助NameNode,分担其工作量,比如定期合并Fsimage和Edits,并推送给Namenode(保持一个较新的文件系统快照);
- 在紧急情况下,可辅助恢复NameNode.
1.4 HDFS文件块大小(面试重点)
HDFS中的文件在物理上是分块存储的(Block),块的大小可以通过配置参数(hdfs-default.xml文件中的dfs.blocksize)来规定, 默认大小在Hadoop2.x/3.x版本中是128M, 1.x版本中是64M.
Q: 为什么块的大小不能设置太小,也不能设置太大?
- HDFS块的大小设置主要取决于磁盘传输速率;
- HDFS的块设置
太小,会增加寻址时间,程序一直在找块的开始位置
; - 如果块设置的
太大,从磁盘传输数据的时间会明显大于定位这个块开始位置所需的时间.
导致程序在处理这块数据时, 会非常慢
.
- 总结: HDFS的块大小主要取决于磁盘传输速率。
二, HDFS的Shell操作
2.1 HDFS的常用命令
- 格式:
hadoop fs 具体命令
ORhdfs dfs 具体命令
2.2, 上传文件
- 先在HDFS创建一个测试文件夹
hdfs dfs -mkdir shellTest
注意: 不要忘记带路径符号正斜杠, ‘/’
- 本地
剪切
文件到HDFS
-moveFromLocal ./文件名 /目标目录
(命令大小写严格区分噢, movefromlocal就不对)
示例:
进入HDFS web管理页面(bigdata01:9870), 点击可以查看目录和文件
- 本地
上传
文件到HDFS
-put ./文件名 /目标目录
或者是,-copyFromLocal ./文件名 /目标目录名
- 追加文件到另一个已经存在文件的末尾
-appendToFile ./文件名 /目录名/文件名
举个栗子:
在本地新建一个cal.txt, 然后把cal指令(显示本月日历)结果追加到cal.txt(其实追加命令自己可以新建文件哈), 之后在hdfs中-touch新建一个time.txt, cal.txt追加到hdfs的
/shellTest/time.txt
中, 并-cat打印输出结果.
2.3, 下载文件
- 从HDFS下载文件到本地
-get /目录/文件 ./
或者是,-copyToLocal /目录名/文件名
示例:
- 如果你下载文件同时
并改个名
,
2.4, 常用Shell命令
- -ls 查指定路径文件和目录的详细信息
注意:
HDFS的 -ls 操作等用于于的是 linux 的 ll操作
- -cat 打印输出文件内容
- -chmod(-chgrp -chown) 修改文件所属权限
- -mkdir 创建目录
- -cp, -mv 复制,移动文件;从HDFS的一个路径到另一个路径
- -rm 删除文件或目录(-rm -r)
- -tail 显示一个文件的末尾的1kb的数据
Q: 为什么要查看末尾? 因为末尾的文件内容随着追加操作不断更新,时效性较强.比如.日志文件.
- -du 统计文件夹的大小信息 ( -du -s 汇总加和, -h 以人类易读的方式显示)
第一列是文件的大小,第二列除以第一列的值是文件的副本数量;
如shuguo.txt这个文件大小是7B, 有14/7=2个副本;
-du -h -s 是文件夹的所有文件的信息
-du -h 是文件夹的总体信息
- -setrep (replication)设置HDFS中文件的副本数量
- 实际还是只有三个副本, 因为目前只有这三台DataNode节点
这里设置的副本数只是记录在NameNode 的元数据中
,是否真的会有这么多副本,还得看DataNode的数量。因为目前只有3台设备,最多也就3个副本,只有节点数的增加到8台时,副本数才能达到8。
!!! 一台机器一个副本,多了没意义!
三, 基于JAVA的HDFS的API操作
四, HDFS的读写流程(面试重点)
4.1, HDFS写数据流程
4.1.1 HDFS文件写入剖析(需要进一步深入)
具体步骤:
- 客户端通过 DistributedFileSystem模块向NameNode
请求上传文件
,NameNode检查目录结构(检查目标文件是否存在,父目录是否存在,权限是否满足); - NameNode
返回请求
是否可以上传; - 客户端对待传文件进行切分,分为若干个block (每个block的大小为64M 或者 128M),然后向NameNode
请求上传第一个Block
到哪几个DataNode服务器上; -
NameNode考虑每一个节点是否可用, 节点距离最近, 负载均衡等因素返回3个DataNode节点
,分别为dn1,dn2,dn3; - 客户端通过
FSDataOutputStream模块请求dn1上传数据
,dn1收到请求后会继续调用dn2,然后dn2调用dn3,将这个通信管道建立完成; - dn1,dn2,dn3
逐级应答客户端
; - 客户端开始往dn1上传第一个Block(先从磁盘读取数据放到一个本地内存缓存),
以Packet为单位
(大小为64k, 由多个chunk512字节+chunkSum4字节组成),dn1收到一个Packet就会传给dn2,dn2传给dn3; dn1每传一个packet会放入一个应答队列等待应答(); - 当一个Block传输完成之后,客户端再次请求NameNode上传第二个BLock的的服务器(重复执行3-7步).
4.1.2 网络拓扑-节点距离计算
在HDFS写数据的过程中,NameNode会选择离上传数据最近距离的DataNode接收数据,那么这个最近距离是如何计算的呢?
节点距离: 两个节点到达最近的共同祖先的距离总和
( 什么是共同祖先, 比如一个机架中两个结点的共同祖先就是这个机架; ab两个机架中的两个节点的共同祖先就是ab两个机架构成的集群)
示例:
4.1.3 机架感知(副本存储节点选择)
选择策略(假如集群有三个副本)
- 第一个副本在Client所处的节点上,如果客户端在集群外,随机选一个;(节点越近,上传速率越快)
- 第二个副本在另一个机架的随机一个节点上;(保证数据的可靠性)
- 第三个副本在第二个副本所在机架的随机节点;(同时兼顾效率)
三个副本的机架感知概括如下:
跟Client同机架的其他一个节点
,别的机架A上的一个节点
,别的机架A上一个随机节点
.
4.2, HDFS 读数据流程
具体步骤:
- 客户端通过 DistributedFileSystem
向 Namenode请求下载文件
, NameNode通过查询元数据,找到文件块所在的DataNode地址; -
挑选一台DataNode(节点最近,负载均衡 )服务器
,请求读取数据; -
DataNode开始传输数据给客户端
(从磁盘里面读取数据输入流,以Packet为单位来做校验); - 客户端
以Packet为 单位接收
,先在本地缓存,然后写入目标文件;
五, NameNode和Secondary NameNode
5.1, NN和2NN工作机制-引言
首先思考一个问题: NameNode中的元数据是存储在哪里的?
- 如果把元数据存放在NameNode节点的磁盘中,因为经常需要进行随机访问,还有响应客户请求,所以必然会效率过低; 因此我们要
把元数据存放到内存中
.- 但是,如果元数据只存在内存中,一旦断电,元数据丢失,整个集群就完犊子了, 所以我们引出了
在磁盘中备份元数据,叫FsImage
;- 然而新问题又来了,当在内存中的元数据更新时,如果同时去更新FsImage,又会导致效率过低, 但是不更新的话又会发生一致性问题, 一旦NameNode断电,数据又丢了个鸡儿的.因此我们引入了
Edits文件(只进行追加操作,效率很高),每当元数据有更新或者添加元数据时,修改内存中的元数据并追加到Edits中
.这样, 一旦NameNode节点断电,可以通过FsImage和Edits的合并,合并出当时的元数据
.- 但是! 如果长时间添加数据到Edits中而不及时合并,会导致该文件数据过大,效率降低,而且一旦断电,恢复元数据需要的时间过长.因此, 需要
定期进行FsImage和Edits的合并
, 如果这个操作由NameNode节点完成,又会导致效率过低,所以,我们又引入了一个新的节点Secondary NameNode, 专门用于FsImage和Edits的合并
.Fsimage 在磁盘中备份元数据;
Edits 记录对数据发生的修改;
Secondary NameNode定时合并上面两个文件;
参见此文: Secondary NameNode:它究竟有什么作用?
5.2, NN和2NN工作机制-详述
- 第一阶段: NameNode启动
- 第一次启动NameNode格式化后,
创建Fsimage和Edits文件
.如果不是第一次启动,则直接加载编辑日志和镜像文件到内存
; - 客户端对元数据进行
增删改的请求
; - NameNode
记录操作日志,更新滚动日志
(记录到edits_inprogress); - NameNode
在内存中对元数据进行增删改
;
- 第二阶段: Secondary NameNode工作
- Secondary NameNode
询问NameNode是否需要CheckPoint
. 直接带回NameNode是否检查结果; - Secondary NameNode
执行CheckPoint
; -
NameNode 滚动正在写的Edits日志
; - 将
滚动前
的编辑日志(Edits)和镜像文件(fsimage)拷贝到
Secondary NameNode; - Secondary NameNode
加载
编辑日志和镜像文件到内存,并进行合并
; -
生成新的镜像文件
fsimage.chkpoint; -
拷贝fsimage.chkpoint回
到NameNode; - NameNode将fsimage.chkpoint重新命名为 fsimage,覆盖原来的fsimage.
checkpoint的触发条件:
- 定时时间到
- Edit中的数据满了
5.3, Fsimage和Edits文件的解析
NameNode 被格式化以后,将在/hadoop安装目录/data/tmp/dfs/name/current 目录下产生如下文件:
- fsimage文件: HDFS文件系统元数据的一个永久的检查点,其中
包含HDFS文件系统的所有目录和inode的序列化信息
; - edits文件:
存放HDFS文件系统的所有更新操作的路径
,文件系统客户端执行的所有写操作首先
会被记录到Edits文件中;
每次NameNode启动的时候都会将Fsimage文件读入内存,加载Edits里面的更新操作,保证内存中的元数据是最新的,同步的,可以看成
NameNode启动的时候就将Fsimage和Edits文件进行了合并
.
- seen_txid文件: 保存的是一个数字,就是
最后一个edits_的数字
; - VERSION文件: 记录 NameNode节点对应的命名空间的id, HDFS 集群的唯一标识符cluster_id(这些标识符与DataNode一致, 用于防止 DataNode 意外注册到属于不同集群的不正确 NameNode。)
5.3.1, 使用oiv命令查看 fsimage文件
- oiv 和 oev
- 基本语法:
hdfs oiv -p 文件类型 -i镜像文件 -o\转换后文件输出路径
- 案例实操:
- 我们在xshell中使用
sz fsimage
把该文件下载到本地, 用BowPad打开查看, 比如 我们要查看hdfs中/user/win10/abb.txt
的目录信息: - 我们可以在其中的一个BLOCK中查找到这些文件或目录名.
- 在fsimage靠后的位置存储着, 上面文件和目录的parent-child 归属关系
通过查看fsimage我们可以清楚的知道, NN只负责记录文件的元数据信息, 并不知道文件的具体存储位置, 而是由DN负责告知的.
5.3.2, 使用oev查看Edits文件
- 基本语法
hdfs oev -p 文件类型 -i 编辑日志 -o 转换后文件输出路径
- 案例实操
- 我们先对HDFS执行一次操作, 这里我随便上传一个文件到HDFS中.
- 提取日志文件到Windows中, 用BowPad打开.
- 下面是我在操作HDFS上传文件后, edits中的记录
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<EDITS>
<EDITS_VERSION>-64</EDITS_VERSION>
<RECORD>
<OPCODE>OP_START_LOG_SEGMENT</OPCODE>
<DATA>
<TXID>1197</TXID>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_ADD</OPCODE>
<DATA>
<TXID>1198</TXID>
<LENGTH>0</LENGTH>
<INODEID>16614</INODEID>
<PATH>/apiTest/text.txt._COPYING_</PATH>
<REPLICATION>3</REPLICATION>
<MTIME>1625197340573</MTIME>
<ATIME>1625197340573</ATIME>
<BLOCKSIZE>134217728</BLOCKSIZE>
<CLIENT_NAME>DFSClient_NONMAPREDUCE_947767106_1</CLIENT_NAME>
<CLIENT_MACHINE>192.168.182.100</CLIENT_MACHINE>
<OVERWRITE>true</OVERWRITE>
<PERMISSION_STATUS>
<USERNAME>win10</USERNAME>
<GROUPNAME>supergroup</GROUPNAME>
<MODE>420</MODE>
</PERMISSION_STATUS>
<ERASURE_CODING_POLICY_ID>0</ERASURE_CODING_POLICY_ID>
<RPC_CLIENTID>6b6c7166-b01f-42bd-a859-d5963b730497</RPC_CLIENTID>
<RPC_CALLID>3</RPC_CALLID>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_ALLOCATE_BLOCK_ID</OPCODE>
<DATA>
<TXID>1199</TXID>
<BLOCK_ID>1073741964</BLOCK_ID>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_SET_GENSTAMP_V2</OPCODE>
<DATA>
<TXID>1200</TXID>
<GENSTAMPV2>1140</GENSTAMPV2>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_ADD_BLOCK</OPCODE>
<DATA>
<TXID>1201</TXID>
<PATH>/apiTest/text.txt._COPYING_</PATH>
<BLOCK>
<BLOCK_ID>1073741964</BLOCK_ID>
<NUM_BYTES>0</NUM_BYTES>
<GENSTAMP>1140</GENSTAMP>
</BLOCK>
<RPC_CLIENTID/>
<RPC_CALLID>-2</RPC_CALLID>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_CLOSE</OPCODE>
<DATA>
<TXID>1202</TXID>
<LENGTH>0</LENGTH>
<INODEID>0</INODEID>
<PATH>/apiTest/text.txt._COPYING_</PATH>
<REPLICATION>3</REPLICATION>
<MTIME>1625197342135</MTIME>
<ATIME>1625197340573</ATIME>
<BLOCKSIZE>134217728</BLOCKSIZE>
<CLIENT_NAME/>
<CLIENT_MACHINE/>
<OVERWRITE>false</OVERWRITE>
<BLOCK>
<BLOCK_ID>1073741964</BLOCK_ID>
<NUM_BYTES>21359</NUM_BYTES>
<GENSTAMP>1140</GENSTAMP>
</BLOCK>
<PERMISSION_STATUS>
<USERNAME>win10</USERNAME>
<GROUPNAME>supergroup</GROUPNAME>
<MODE>420</MODE>
</PERMISSION_STATUS>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_RENAME_OLD</OPCODE>
<DATA>
<TXID>1203</TXID>
<LENGTH>0</LENGTH>
<SRC>/apiTest/text.txt._COPYING_</SRC>
<DST>/apiTest/text.txt</DST>
<TIMESTAMP>1625197342157</TIMESTAMP>
<RPC_CLIENTID>6b6c7166-b01f-42bd-a859-d5963b730497</RPC_CLIENTID>
<RPC_CALLID>8</RPC_CALLID>
</DATA>
</RECORD>
</EDITS>
Q-A:
- NameNode如何确定下次集群启动时合并哪些信息?
- 当前的 fsImage文件我们是已知的, fsImage的文件名也是已知的, 所以
只需合并edits文件名中的序号大于 fsImage文件名序号的部分 edits文件即可
. - NN 和 2NN的差异文件?
- edits-inprogress
5.4, CheckPoint时间设置
定时
通常情况下,Secondary NameNode 每隔一小时执行一次定次数
一分钟检查一次操作次数,当操作次数达到一百万时,SecondaryNameNode执行一次;
六, DataNode
6.1, DataNode工作机制
- 一个数据块在DataNode上以文件形式存储在磁盘上,包括两个文件, 一个是
数据本身
, 另一个是元数据
(.meta文件)(包括数据块的长度,块数据的校验和,以及时间戳); - DataNode启动后向NameNode注册(报告块信息),NN返回注册成功的消息. 为避免中途宕机, DataNode会周期性(6小时)的向NameNode上传所有的块信息;
- 心跳(告诉NN本DN还活着)是每3秒一次,心跳返回结果带有NameNode给该DataNode的命令比如复制块数据到另一台机器,或是删除某个数据块. 如果超过十分钟没有收到某个DataNode的心跳(10分钟+ 30秒, 即十分钟再加上十次心跳),则认为该节点不可用;
- 集群运行中可以安全加入和退出一些机器.
6.2, 数据完整性
- DataNode节点保证数据完整性的方法:
- 当DataNode读取Block的时候,它会计算CheckSum;
- 如果计算后的CheckSum, 与Block创建时值不一样,说明Block已经损坏;
- Client读取其他DataNode上的Block;
- 常见的校验算法 crc(32), md5, sha1;
- DataNode在其文件创建后周期验证ChechSum;
6.3 掉线时限参数设置
10分钟 + 30s