前言
随着数据量越来越大,在一个操作系统存不下所有的数据,那么就分配到更多的操作系统管理的磁盘中,但是不方便管理和维护,迫切需要一种系统来管理多台机器上的文件,这就是分布式文件管理系统。HDFS只是分布式文件管理系统中的一种
一、概述
1. HDFS定义
🧰Hadoop Distributed File System,它是一个文件系统,用于存储文件,通过目录树来定位文件;它是分布式的,由很多服务器联合起来实现其功能,集群中的服务器有各自的角色。
2. HDFS的使用场景
🧰适合一次写入,多次读出的场景。一个文件经过创建、写入和关闭之后就不需要改变
HDFS优点
- 高容错性:数据自动保存多个副本。它通过增加副本的形式,提高容错性。某一个副本丢失以后,它可以自动恢复。
- 适合处理大数据:数据规模大,能够处理数据规模达到GB、TB、甚至PB级别的数据;文件规模大,能够处理百万规模以上的文件数量,数量相当之大。
- 可构建在廉价机器上,通过多副本机制,提高可靠性
HDFS缺点
- 不适合低延时数据访问,比如毫秒级的存储数据,是做不到的。
- 无法高效的对大量小文件进行存储。
存储大量小文件的话,它会占用NameNode大量的内存来存储文件目录和块信息,因为NameNode的内存总是有限的;小文件存储的寻址时间会超过读取时间,它违反了HDFS的设计目标。- 不支持并发写入、文件随机修改。
一个文件只能有一个写,不允许多个线程同时写;仅支持数据append(追加),不支持文件的随机修改
3. HDFS组成架构
🧰NameNode(nn):就是Master,它是一个主管、管理者
- 管理HDFS的名称空间。
- 配置副本策略。
- 管理数据块(Block)映射信息。
- 处理客户端读写请求。
🧰DataNode:就是Slave。NameNode下达命令,DataNode执行实际的操作。
- 存储实际的数据块。
- 执行数据块的读/写操作。
🧰Client:客户端。
- 文件切分。文件上传HDFS的时候,Client将文件切分成一个一个的Block,然后进行上传。
- 与NameNode交互,获取文件的位置信息。
- 与DataNode交互,读取或者写入数据。
- Client提供一些命令来管理HDFS,比如NameNode格式化。
- Client可以通过一些命令来访问HDFS,比如对HDFS增删查改操作。
🧰Secondary NameNode:并非NameNode的热备。当NameNode挂掉的时候,它并不能马上替换NameNode并提供服务。
- 辅助NameNode,分担其工作量,比如定期合并Fsimage和Edits,并推送给NameNode 。
- 在紧急情况下,可辅助恢复NameNode。
4. HDFS文件块大小
🧰HDFS中的文件在物理上是分块存储(Block),块的大小可以通过配置参数(dfs.blocksize)来规定,默认大小在Hadoop2.x/3.x版本中是128M,1.x版本中是64M
为什么块的大小不能设置太小,也不能设置太大?
- HDFS的块设置太小,会增加寻址时间,程序一直在找块的开始位置。
- 如果块设置的太大,不利于并发运算,从磁盘传输数据的时间会明显大于定位这个块开始位置所需的时间。导致程序在处理这块数据时,会非常慢。
HDFS块的大小设置主要取决于磁盘传输速率(由读写速度决定)
二、HDFS的Shell相关操作(开发重点)
1. 基本语法
🧰hadoop fs [选项] [对象]
或 hdfs dfs [选项] [对象]
🧰hadoop fs / hdfs dfs
:查看命令大全,hadoop fs -help 命令
对应命令的用法
# 启动hadoop集群
sbin/start-dfs.sh
# 创建测试文件夹/sanguo
hadoop fs -mkdir /sanguo
2. 上传(本地 --> HDFS):
🧰hadoop fs -moveFromLocal 要传的文件 要传入的目录下/文件名
从本地剪切文件到HDFS路径去
🧰hadoop fs -put/copyFromLocal 要传的文件 要传入的目录下/文件名
从本地复制文件到HDFS路径去
🧰hadoop fs -appendToFile 要追加的文件 要追加进的文件中/文件名
追加一个文件到已经存在的文件末尾
# 先在本地创建txt文件
touch shuguo.txt
echo "hello,shuguo" >> shuguo.txt
# 上传至/sanguo目录下(直接移动过去)
hadoop fs -moveFromLocal shuguo.txt /sanguo
# 复制过去
hadoop fs -put weiguo.txt /sanguo/caowei.txt
# 追加到末尾
hadoop fs -appendToFile liubei.txt /sanguo/shuguo.txt
3. 下载(HDFS --> 本地)
🧰hadoop fs -copyToLocal/get 要下载的文件 要下载的目录下/文件名
# 将shuguo.txt拷贝回本地,并更名为shu.txt
hadoop fs -get /sanguo/shuguo.txt shu.txt
4. HDFS直接操作
选项 | 功能 | 例子 |
---|---|---|
-ls | 显示目录信息 | hadoop fs -ls / |
-cat | 显示文件内容 | hadoop fs -cat /sanguo/shuguo.txt |
-chmod | 变更文件rwx | hadoop fs -chmod 777 /sanguo/shuguo.txt |
-chgrp | 改属组 | hadoop fs -charp ygy:ygy /sanguo/shuguo.txt |
-chown | 改属主 | hadoop fs -charp root:ygy /sanguo/shuguo.txt |
-mkdir | 创建路径 | hadoop fs -mkdir /jinguo |
-cp | 在HDFS的某路径拷贝到另一个路径 | hadoop fs -cp /shuguo/shuguo.txt /jinguo |
-mv | 在HDFS目录中移动文件 | hadoop fs -mv /sanguo/weiguo.txt /jinguo |
-tail | 显示文件末尾1kb的数据(最后最新) | hadoop fs -tail /jinguo/shuguo.txt |
-rm | 删除文件或文件夹 | hadoop -rm /sanguo/shuguo.txt |
-rm -r | 递归删除目录以及包含其中的文件 | hadoop fs -rm -r /sanguo |
-du | 统计文件夹大小信息 | hadoop fs -du -s -h /jinguo ,-s只显示总和,-h阅读友好 |
-setrep | 设置HDFS中文件的副本数量 | hadoop fs -setrep 10 /jinguo/shuguo.txt |
🧰注意:
# -du查看文件夹大小信息
hadoop fs -du -sh /jinguo
# 报错😇
-du: Illegal option -sh
# -sh不像shell中可以合起来写,但功能一致
hadoop fs -du -s -h /jinguo
# 输出
182 546 /sanguo
#文件大小 文件大小*副本数 查看的目录
这里设置的副本数只是记录在 NameNode 的元数据中,是否真的会有这么多副本,还得看 DataNode 的数量。
因为目前只有 3 台设备,最多也就 3 个副本,只有节点数的增加到10台时,副本数才能达到 10
三、HDFS的客户端API(1%重要)
上面是在hadoop集群内部用shell操控,我们希望在windows上对远程集群进行客户端访问
🧰操作步骤:
1️⃣点此👉下载Windows依赖,将bin
目录解压至一个英文文件中,随后打开系统属性➡环境变量➡系统变量➡新建➡变量名HADOOP_HOME
,变量值(bin目录所在路径)➡在Path
里新建%HADOOP_HOME%\bin
➡回到\bin
,点击winutils.exe,有短暂闪烁启动窗口即可
2️⃣点击下载Maven并配置环境👉Maven
环境变量的修改可能要重启电脑才行
3️⃣在 pom.xml
里的</project>
上面加上以下代码(对应版本要做修改):
<dependencies>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>3.1.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
</dependency>
</dependencies>
然后点击右上角的刷新标志,将报错消去
4️⃣在项目的src/main/resources
目录下创建log4j.properties
文件(file),在文件中填入:
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
5️⃣在src/main/java
里创建包com.ygy.hdfs
,并创建HdfsClient
类,调用API操控hdfs
package com.ygy.hdfs;
省略导包...
/**
* 客户端代码常用套路:
* 1. 获取一个客户端对象
* 2. 执行相关的操作命令
* 3. 关闭资源
* 其中1、3步相同重复操作多次,可以封装起来,提高复用
*/
public class HdfsClient {
private FileSystem fs;
// 执行顺序:before => test => after,不会有空指针异常
@Before
public void init() throws IOException, InterruptedException, URISyntaxException {
// 连接集群nn地址
URI uri = new URI("hdfs://hadoop102:8020");
// 创建一个配置文件
Configuration configuration = new Configuration();
configuration.set("dfs.replication", "2");
// 修改副本数为2(代码里面的配置,优先级最高)
// 用户
String user = "root";
// 1. 获取到了客户端对象
fs = FileSystem.get(uri, configuration, user);
}
// 2.执行相关操作
// ...
@After
public void close() throws IOException {
// 3. 关闭资源
fs.close();
}
🧰创建目录:
// 创建目录
@Test
public void testMkdir() throws URISyntaxException, IOException, InterruptedException {
// 2. 创建一个文件夹
fs.mkdirs(new Path("/xiyou/huaguoshang"));
}
🧰上传文件:
// 上传
@Test
public void testPut() throws IOException {
fs.copyFromLocalFile(false, true, new Path("F:\\sunwukong.txt"), new Path("/xiyou/huaguoshang/"));
}
参数解读:
参数一是否删除原数据
参数二表示是否允许覆盖
参数三表示原数据路径(本地)
参数四表示目的地路径
🧰参数优先级(低 => 高):hdfs-default.xml
=> hdfs-site.xml
=> 在项目资源目录下的配置文件 => 代码里面的配置
例如:
将hdfs-sitc.xml
拷贝到项目的resources资源目录下,修改生成副本数dfs.replication
为1,上传一个文件,副本数优先看在项目资源目录下hdfs-sitc.xml
中,即为1
🧰文件下载:
// 文件下载
@Test
public void testGet() throws IOException {
fs.copyToLocalFile(false, new Path("/xiyou/huaguoshang"), new Path("F:\\"), false);
}
参数解读:
参数一是否表示删除原数据
参数二:原文件路径HDFS
参数三:目标地址路径windows
参数四:是否开启本地校验(通过.crc
文件校验)
🧰文件删除:
// 删除
@Test
public void testRm() throws IOException {
// 删除文件
fs.delete(new Path("/xiyou/huaguoshang/sunwukong.txt"), false);
// 删除空目录
fs.delete(new Path("/xiyou"),false);
// 删除非空目录(非递归无法删除)
fs.delete(new Path("/jinguo"),true);
}
参数解读:
参数一:要删除的路径
参数二:是否递归删除
🧰文件更名和移动:
@Test
public void testMv() throws IOException {
// 移动
fs.rename(new Path("/sunwukong.txt"), new Path("/xiyou/huaguoshang/sunwukong.txt"));
// 重命名
fs.rename(new Path("/xiyou/huaguoshang/sunwukong.txt"), new Path("/xiyou/huaguoshang/wukong.txt"));
}
🧰获取文件详情
// 获取文件详细信息
@Test
public void testListFiles() throws IOException {
// 获取所有文件信息
RemoteIterator<LocatedFileStatus> listFiles = fs.listFiles(new Path("/"), true);
// 遍历文件
while (listFiles.hasNext()) {
LocatedFileStatus fileStatus = listFiles.next();
System.out.println("==========" + fileStatus.getPath() + "==========");
System.out.println(fileStatus.getPermission());
System.out.println(fileStatus.getOwner());
System.out.println(fileStatus.getGroup());
System.out.println(fileStatus.getLen());
System.out.println(fileStatus.getAccessTime());
System.out.println(fileStatus.getReplication());
System.out.println(fileStatus.getBlockSize());
System.out.println(fileStatus.getPath().getName());
// 获取块信息
BlockLocation[] blockLocations = fileStatus.getBlockLocations();
System.out.println(Arrays.toString(blockLocations));
}
}
🧰文件和文件夹判断
@Test
public void testFiles() throws IOException {
FileStatus[] fileStatuses = fs.listStatus(new Path("/"));
for (FileStatus fileStatus: fileStatuses) {
if (fileStatus.isFile()) System.out.println("文件:" + fileStatus.getPath().getName());
else System.out.println("文件目录:" + fileStatus.getPath().getName());
}
}
}
四、HDFS的读写流程
1. 写数据流程(上传)
🧰HDFS写数据流程图:
流程解析:
- 客户端通过 Distributed FileSystem 模块向 NameNode 请求上传文件,NameNode 检查目标文件是否已存在,父目录是否存在
- NameNode 返回是否可以上传
- 客户端请求第一个 Block 上传到哪几个 DataNode 服务器上
- NameNode 返回 3 个 DataNode 节点,分别为 dn1、dn2、dn3
- 客户端通过 FSDataOutputStream 模块请求 dn1 上传数据,dn1 收到请求会继续调用dn2,然后 dn2 调用 dn3,将这个通信管道建立完成
- dn1、dn2、dn3 逐级应答客户端
- 客户端开始往 dn1 上传第一个 Block(先从磁盘读取数据放到一个本地内存缓存),以 Packet 为单位,dn1 收到一个 Packet 就会传给 dn2,dn2 传给 dn3;dn1 每传一个 packet会放入一个应答队列等待应答
- 当一个 Block 传输完成之后,客户端再次请求 NameNode 上传第二个 Block 的服务器
在HDFS 写数据的过程中,NameNode会选择距离待上传数据最近距离的DataNode接收数据。
🧰网路拓扑–节点距离计算:节点距离即两个节点到达最近的共同祖先的距离之和
如上图出框线距离才加1
🧰机架感知(副本存储节点选择):官方说明文档点此👉前往
2. 读数据流程(下载)
🧰读数据流程图:
流程解析:
- 客户端通过 DistributedFileSystem 向 NameNode 请求下载文件,NameNode 通过查询元数据,找到文件块所在的 DataNode 地址
- 挑选一台 DataNode(就近原则,然后随机)服务器,请求读取数据
- DataNode 开始传输数据给客户端(从磁盘里面读取数据输入流,以 Packet 为单位来做校验)
- 客户端以 Packet 为单位接收,先在本地缓存,然后写入目标文件
五、NN和2NN
1.NN和2NN的工作机制
NameNode元数据存储:
💡FsImage镜像文件,在磁盘中备份元数据,采用不更新用Edits追加的方式,一旦 NameNode 节点断电,可以通过 FsImage 和 Edits 的合并,合成元数据。
😵但是,如果长时间添加数据到 Edits 中,会导致该文件数据过大,效率降低,而且一旦断电,恢复元数据需要的时间过长。
💡因此,需要定期进行 FsImage 和 Edits 的合并,如果这个操作由NameNode节点完成,又会效率过低。因此,引入一个新的节点SecondaryNamenode,专门用于 FsImage 和 Edits 的合并
🧰NameNode工作机制:
第一阶段:NameNode 启动
- 第一次启动 NameNode 格式化后,创建 Fsimage 和 Edits 文件。如果不是第一次启动,直接加载编辑日志和镜像文件到内存
- 客户端对元数据进行增删改的请求
- NameNode 记录操作日志,更新滚动日志
- NameNode 在内存中对元数据进行增删改
第二阶段:Secondary NameNode 工作
- Secondary NameNode 询问 NameNode 是否需要 CheckPoint。直接带回 NameNode是否检查结果
- Secondary NameNode 请求执行 CheckPoint。
- NameNode 滚动正在写的 Edits 日志。将滚动前的编辑日志和镜像文件拷贝到 Secondary NameNode
- Secondary NameNode 加载编辑日志和镜像文件到内存,并合并。生成新的镜像文件 fsimage.chkpoint
- 拷贝 fsimage.chkpoint 到 NameNode。NameNode 将 fsimage.chkpoint 重新命名成 fsimage
2. Fsimage和Edits解析
NameNode被格式化之后,将在
/opt/module/hadoop-3.1.3/data/dfs/name/current
目录中产生如下文件
🧰Fsimage文件:是HDFS文件系统元数据的一个永久性的检查点,其中包含HDFS文件系统的所有目录和文件inode的序列化信息
🧰Edits文件:存放HDFS文件系统的所有更新操作的路径(一切追加操作),文件系统客户端执行的所有写操作首先会被记录到Edits文件中
🧰seen_txid文件:保存的是一个数字,就是最后一个edits_的数字
每次NameNode启动的时候都会将Fsimage文件读入内存,加 载Edits里面的更新操作,保证内存中的元数据信息是最新的、同步的,可以看成NameNode启动的时候就将Fsimage和Edits文件进行了合并
🧰oiv查看Fsimage 文件:hdfs oiv -p 文件类型 -i 镜像文件 -o 转换后文件输出路径
# 查看fsimage_0000000000000000025的内容
hdfs oiv -p XML -i fsimage_0000000000000000025 -o /opt/module/hadoop/fsimage.xml
cat /opt/module/hadoop/fsimage.xml
# 再打开一个cmd,执行
sftp root@hadoop102
# 切换至要保存到本地的文件夹
lcd F:\
# 切换至要下载文件的所在位置
cd /opt/module/hadoop/fsimage.xml
# 下载到本地F盘
get fsimage.xml
存储有NN的镜像数据,文件信息等,但是没有文件块信息(其他服务器上),其他服务器DateNode会跟NN报告文件块
🧰oev查看Edits文件:hdfs oev -p 文件类型 -i 编辑日志 -o 转换后文件输出路径
# 下载过程
hdfs oev -p XML -i edits_0000000000000000012-0000000000000000013 -o /opt/module/hadoop/edits.xml
sftp root@hadoop102
lcd F:\
cd /opt/module/hadoop/edits.xml
get edits.xml
查看HDFS默认配置文件
more /opt/module/hadoop/share/doc/hadoop/hadoop-project-dist/hadoop-hdfs/hdfs-default.xml
六、Datanode
1. DataNode工作机制
流程解析:
- DataNode启动后向NameNode注册
- 注册成功后向DataNode返回信号
- DN开始每周期(6小时)上报所有块信息
- 心跳每3秒一次(两者通讯),心跳返回结果带有NameNode给DataNode的命令
- 超过10分钟+30秒没有收到DN的心跳,NN则认为该节点不可用
🧰一个数据块在 DataNode 上以文件形式存储在磁盘上,包括两个文件,一个是数据本身,一个是元数据包括数据块的长度,块数据的校验和,以及时间戳。
3个6小时:
- DataNode 启动后向 NameNode 注册,通过后,周期性(6 小时)的向 NameNode 上报所有的块信息;
- DN 向 NN 汇报当前解读信息的时间间隔,默认 6 小时;
- DN 扫描自己节点块信息列表的时间,默认 6 小时
2. 数据完整性
🧰DataNode 节点保证数据完整性的方法
- 当 DataNode 读取 Block 的时候,它会计算 CheckSum
- 如果计算后的 CheckSum,与 Block 创建时值不一样,说明 Block 已经损坏
- Client 读取其他 DataNode 上的 Block
- 常见的校验算法 crc(32),md5(128),sha1(160)
- DataNode 在其文件创建后周期验证 CheckSum
🧰校验法图例:
3. 掉线时限参数
🧰掉线时限说明:
需要注意的是 hdfs-site.xml配置文件中的heartbeat.recheck.interval的单位为毫秒,dfs.heartbeat.interval的单位为秒
🌰让hadoop104的DN挂掉,经过10min➕30s后:
# 重启hadoop104的DN即可恢复正常,Last contact不超过3s
hdfs --daemon start datanode
总结
重点掌握HDFS中shell命令部分,大概了解了HDFS,下一篇👉MapReduce