02 HDFS
1 HDFS 概述
1.1 HDFS产出背景及定义
产出背景
● 随着数据量变大,系统存不下,分配更多系统磁盘,但是管理不方便。
● 需要一种系统来管理多态机器上的文件,这就是分布式文件管理系统。
● HDFS — 分布式文件管理系统
定义:
● HDFS(Hadoop Distributed File System):文件系统、分布式的
● HDFS的使用场景:一次写入,多次读出,不能修改。
● 适合用来做数据分析,不适合用来做网盘应用。
1.2 HDFS优缺点
优点:
● 高容错性
● 适合处理大数据
● 可构建在廉价机器上
缺点:
● 不适合低延时数据访问
● 无法高效 的对大量小文件进行存储
● 不支持并发写入、文件随机修改
(1) 一个文件只能有一个写,不允许多个线程同时写
(2) 仅支持数据append(追加),不支持文件的随机修改。
1.3 HDFS组成架构
构架图:
1)NameNode(nm):就是Master,它是一个主管、管理者
2)DataNode(dn):就是Slave,NameNode下达命令,DataNode执行实际的操作。
3)Client:客户端
4)Secondary NameNode:并非NameNode热备,当NameNode挂掉时,它并不能马上替换NameNode并提供鼓舞。
1.4 HDFS文件块大小(面试重点)
● HDFS中的文件在物理上是分块存储(Block),快的大小通过配置参数(dfs.blocksize)来规定
● 默认大小:Hadoop2.x 为128M,老版本为64M。
思考:为什么块的大小不能设置太小,也不能设置太大?
太小 — 会增加寻址时间,程序一直在找块的开始位置
太大 — 从磁盘传输数据的时间会明显大于定位这个块开始位置的所需时间。导致程序在处理这块数据时,会非常慢。
总结:HDFS块的大小设置主要取决于磁盘传输速度。
2 HDFS的Shell操作(开发重点)
2.1 基本语法
hadoop fs 具体命令 <=> hdfs dfs 具体命令
2.2 常用命令
2.2.1 准备工作
1) 启动集群
# start-dfs.sh
# start-yarn.sh
2) -help :输出命令参数
# Hadoop fs -help rm
2.2.2 上传
指令 | 说明 | 实例 |
-moveFromLocal | 本地剪切到HDFS | hadoop fs -moveFromLocal [本地] [HDFS] |
-copyFromLocal | 本地拷贝到HDFS | hadoop fs -copyFromLocal [本地] [HDFS] |
-appendToFile | 追加文件 | hadoop fs -appendToFile [本地] [HDFS] |
-put | 等同于copyFromLocal | hadoop fs -put [本地] [HDFS] |
2.2.3 下载
指令 | 说明 | 实例 |
-copyToLocal | HDFS拷贝到本地 | hadoop fs -copyToLocal [HDFS] [本地] |
-get | 等同于 copyToLocal | hadoop fs -get [HDFS] [本地] |
d-getmerge | 合并下载多个文件 | hadoop fs -getmerge [HDFS] [本地] |
2.2.4 HDFS直接操作
指令 | 说明 | 实例 |
-ls | 在HDFS上创建目录 | hadoop fs -ls / |
-mkdir | 显示目录信息 | hadoop fs -mkdir -p /input |
-cat | 显示文件内容 | hadoop fs -cat /input/a.txt |
-chgrp -chmod -chown | 修改文件所属权限 | hadoop fs -chmod 666 /input/a.txt |
-cp | HDFS中文件拷贝 | haddoop fs -cp /input/a.txt /input2 |
-mv | 在HDFS目录中移动文件 | hadoop fs -mv /input2/a.txt /input |
-tail | 显示文件的末尾 | hadoop fs -tail /input/a.txt |
-rm | 删除文件或文件夹 | hadoop fs -rm /input2/a.txt |
-rmdir | 删除空目录 | hadoop fs -mkdir /input3 |
-du | 统计文件夹的大小信息 | hadoop fs -du -s -h /input/a.txt |
-setrep | 设置HDFS中文件的副本数量 | hadoop fs -setrep 10 /input/a.txt |
3 HDFS客户端操作(开发重点)
3.1 客户端环境准备
1) 资料目录下的Windows依赖目录,打开
2) 配置HADOOP_HOME环境变量。
3) 配置Path环境变量。然后重启电脑(如果上述操作后还有问题可以将bin目录下hadoop.dll和winutils.exe放到 C:/windows/system32目录下)
4) 创建一个Maven工程HdfsClientDemo,并导入相应的依赖坐标+日志添加
5) 在项目的src/main/resources目录下,新建一个文件,命名为“log4j2.xml”,在文件中填入
6) 创建包名:com.atguigu.hdfs
7) 创建HdfsClient类
8) 运行时需要配置用户名称
补充:
【案例】注解@Before和@After
public class HDFSDemo {
@Before
public void before(){
System.out.println("1.brefor");
}
@After
public void after(){
System.out.println("3.after");
}
@Test
public void test(){
System.out.println("2.test");
}
}
运行结果:
1.brefor
2.test
3.after
【案例2】模板准备
1.通过代码操作HDFS
2.具体操作(上传、下载、删除...)
3.关闭资源
public class HDFSDemo {
private FileSystem fs;
@Before
public void before() throws Exception {
//1.创建客户端对象
/*
get(final URI uri, final Configuration conf, final String user)
uri:NameNode的地址(通信地址,不是web端地址)
conf:配置参数的对象
user:操作HDFS的用户名
*/
URI uri = new URI("hdfs://hadoop102:8082");
Configuration conf = new Configuration(); //虽然不用,但是要FileSystem,所以还是要new一个出来
fs = FileSystem.get(uri,conf,"atguigu");
}
@After
public void after(){
//3.关闭资源
try {
if (fs != null) {
fs.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//2.具体操作(上传、下载、删除...)
@Test
public void test(){
}
}
3.2 HDFS的API操作
3.2.1 HDFS文件上传
● 函数介绍
copyFromLocalFile(boolean delSrc, boolean overwrite, Path src, Path dst) | |
delSrc | 是否删除源文件(本地) |
overwrite | 如果目标文件存在是否覆盖 [true-覆盖 false-不覆盖,如果目标文件存在则报错] |
src | 源文件路径(本地) |
dst | 目标文件路径(HDFS) |
【案例】上传( 本地->HDFS )
@Test
public void test() throws IOException {
fs.copyFromLocalFile(true, true, new Path("D:\\OT\\yezi.txt"), new Path("/"));
}
3.2.2 HDFS文件下载
● 函数介绍
copyToLocalFile(boolean delSrc, Path src, Path dst, boolean useRawLocalFileSystem) | |
delSrc | 是否删除源文件(HDFS) |
src | 源文件路径(HDFS) |
dst | 目标文件路径(本地) |
useRawLocalFileSystem | 是否使用RawLocalFileSystem文件系统 |
true —— 本地不会生成crc校验和文件 | |
false —— 本地会生成校验和文件 |
【案例】下载 ( HDFS -> 本地)
@Test
public void test2() throws IOException {
fs.copyToLocalFile(false, new Path("/yezi.txt"), new Path("D:\\OT"), false);
}
3.2.3 HDFS文件夹删除
● 函数介绍
delete(Path f, boolean recursive) | |
f | 路径 |
recursive | 是否递归 |
文件— true和false都可以 | |
目录 — ture 空目录 — false也可以 |
【案例】删除
@Test
public void test3() throws IOException {
fs.delete(new Path("/abc"),false);
}
3.2.4 HDFS文件夹更改
● 函数介绍
rename(Path src, Path dst) | |
src | 源文件路径 |
dst | 目标文件路径 |
【案例1】更名
@Test
public void test4() throws IOException {
fs.rename(new Path("/input/a.txt"),new Path("/input/b.txt"));
}
【案例2】移动
@Test
public void test4() throws IOException {
fs.rename(new Path("/input/b.txt"),new Path("/input2"));
}
3.2.5 HDFS文件详情查看
● 函数介绍
listFiles(final Path f, final boolean recursive) | |
f | 路径 |
recursive | 是否递归 |
● 类介绍
类 | 说明 |
RemoteIterator | 迭代器--用来迭代所有的文件 |
LocatedFileStatus | 该类中封装了文件所有的详情 |
BlockLocation | 该类中封装了块的详情 |
【案例】
@Test
public void test5() throws IOException {
RemoteIterator<LocatedFileStatus> listFiles = fs.listFiles(new Path("/"), true);
while(listFiles.hasNext()){
//LocatedFileStatus : 该类中封装了文件所有的详情
LocatedFileStatus fileStatus = listFiles.next();
System.out.println("文件的名字:" + fileStatus.getPath().getName());
System.out.println("文件的副本数:" + fileStatus.getReplication());
System.out.println("文件的大小:" + fileStatus.getLen());
//获取块的信息
//BlockLocation :该类中封装了块的详情
BlockLocation[] blockLocations = fileStatus.getBlockLocations();
System.out.println(Arrays.toString(blockLocations));
}
}
运行结果:
文件的名字:b.txt
文件的副本数:3
文件的大小:56
[0,56,hadoop102,hadoop103,hadoop104]
文件的名字:job_1639697652307_0001-1639698054115-atguigu-MRDemo2%2D1.0%2DSNAPSHOT.jar-1639698066271-1-1-SUCCEEDED-default-1639698058806.jhist
文件的副本数:3
文件的大小:22208
[0,22208,hadoop102,hadoop103,hadoop104]
文件的名字:job_1639697652307_0001_conf.xml
文件的副本数:3
文件的大小:215082
[0,215082,hadoop102,hadoop103,hadoop104]
3.2.6 HDFS文件和目录判断
● FileStatus : 在该类中封装了文件或目录的详情
【案例】
@Test
public void test6() throws IOException {
/*
FileStatus : 在该类中封装了文件或目录的详情
*/
FileStatus[] fileStatuses = fs.listStatus(new Path("/"));
//遍历
for (FileStatus fileStatus : fileStatuses) {
if (fileStatus.isFile()){//判断是否是文件
System.out.println(fileStatus.getPath().getName() + "是一个文件");
}else if(fileStatus.isDirectory()){//判断是否是目录
System.out.println(fileStatus.getPath().getName() + "是一个目录");
}
}
}
运行结果:
input是一个目录
input2是一个目录
tmp是一个目录
yezi.txt是一个文件
3.2.7 通过流实现文件的上传
● 函数介绍
copyBytes(InputStream in, OutputStream out,int buffSize, boolean close) | |
in | 输入流 |
out | 输出流 |
buffSize | 缓冲区大小 byte[] b = new byte[buffSize]; |
close | 是否关闭资源 |
【案例】
@Test
public void test7() throws IOException {
//读
FileInputStream fis = new FileInputStream("D:\\io\\hdfs\\longge.txt");
//写
FSDataOutputStream fos = fs.create(new Path("/longge.txt"));
//一边读一边写
/*
copyBytes(InputStream in, OutputStream out,int buffSize, boolean close)
in : 输入流
out : 输出流
buffSize : 缓冲区大小 byte[] b = new byte[buffSize];
close : 是否关闭资源
*/
// byte[] b = new byte[1024];
// int len = 0;
// while((len=fis.read(b)) != -1){
// fos.write(b,0,len);
// }
IOUtils.copyBytes(fis,fos,1024,false);
//关资源
IOUtils.closeStream(fis);
IOUtils.closeStream(fos);
}
3.2.8 通过流实现文件的下载
【案例】
@Test
public void test8() throws IOException {
//读
FSDataInputStream fis = fs.open(new Path("/longge.txt"));
//写
FileOutputStream fos = new FileOutputStream("D:\\io\\hdfs\\longge.txt");
//文件对拷
IOUtils.copyBytes(fis,fos,1024,true);
}
4 HDFS的数据流(面试重点)
4.1 HDFS写数据流程
4.1.1 剖析文件写入
(1) 客户端 --- Distribute FileSystem模块(请求上传文件)---> NameNode NameNode检查目标文件、父目录是否存在
(2) NameNode 答复 (是否可以上传)
(3) 客户端 ---第一个Block传到哪?--->Namenode
(4) Namenode返回3个DataNode节点(dn1,dn2,dn3)
(5) 客户端 ---FSDataOutputStream模块(请求上传数据)--->dn1 ---调用--->dn2 ---调用--->dn3 通信管道建立完成
(6) dn1 ---应答成功---> dn2 ---应答成功---> dn3
(7) 客户端 ---上传(第一个block,单位Packet)--->dn1 ---分享Package--->dn2 ---分享Package--->dn3
(8) 当第一个Block传输完成后,客户端再次请求NameNode上传第二个Block的服务器(重复执行3-7)。
4.1.2 网络拓扑-节点距离计算
在HDFS写数据的过程中,NameNode会选择距离待上传数据最近距离的DataNode接收数据。
节点距离:两个节点到达最近的公共祖先的距离总和
4.1.3 机架感知-副本存储节点选择
1) 官方IP地址
2) Hadoop3.1.3副本节点选择
● 副本1:在Client所处节点上。如果客户端在集群外,随机选一个。
● 副本2:在另一个机架的随机一个节点
● 副本3:在副本2所在机架的随机节点
4.2 HDFS读数据流程
(1) 客户端 ---DIstributed FileSystem(请求下载文件)--->NameNode(权限匹配后,查询元数据,找到文件块的DataNode地址)
(2) 选择 ---(就近原则,随机)--->DataNode1 --->读取数据
(3) DataNode ---数据(传输)--->客户端 (从磁盘里面读取数据输入流,以packet为单位来校验)
(4) 客户端接收数据,在本地缓存,然后写入目标文件。(以Packet为单位)
5 NameNode和SecondaryNameNode(面试开发重点)
5.1 NN和2NN工作机制
1) 第一阶段:NameNode启动
(1) 第一次启动NameNode格式化后,创建Fsimage和Edits文件。如不是第一次启动,直接加载编辑日志和镜像文件到内存。
(2) 客户端---对元数据的增删改(请求)--->NameNode
(3) NameNode记录操作日志,更新滚动日志
(4) NameNode在内存中对元数据进行增删改
2)第二阶段:Secondary NameNode工作
(1) Secondary NameNode询问NameNode是否须要CheckPoint。直接带回NameNode是否检查结果。
(2) Secondary NameNode请求执行Checkoint。
(3) NameNode滚动正在写的Edits日志。
(4) 将滚动前的编辑日志和镜像文件拷贝到Secondary NameNode。
(5) Secondary NameNode加载编辑日志和镜像文件到内存中,进行合并。
(6) 生成新的镜像文件fsimage.chkpoint。
(7) 拷贝fsimage.chkpoint到NameNode
(8) NameNode将fsimage.chkpoint重命名为fsiamge。
总结:
CheckPoint请求(2nn)——滚动日志Edits(nn)——拷贝(nn-2nn)——加载到内存合并(2nn)——fsimage.chkpoint(2nn-nn)——重命名为fsimage(nn)
【实例】
5.2 Fsimage和Edits解析
NameNode被格式化后,将在/opt/module/hadoop-3.1.3/data/dfs/namesecondary/current目录下生成如下文件。
Fsimage文件:永久性的检查点,其中包含HDFS文件系统的所有目录和文件inode的序列化信息。
Edits:存放所有更新操作的路径。文件系统客户端执行的所有写操作首先会被记录到Edits文件中。
seen_txid:保存的是一个数字,就是最后一个edits_的数字。
每次NameNode启动,都会将Fsimage文件读入内存,加载Edits里面的更新操作,保证内存中的元数据信息是最新的、同步的,可以堪称NameNode启动的时候就将Fsimage和Edits文件进行了合并。
oiv 查看 Fsimage文件 | hdfs oiv -p 文件类型 -i镜像文件 -o 转换后文件输出路径 |
oev 查看 Edits文件 | hdfs oev -p 文件类型 -i编辑日志 -o 转换后文件输出路径 |
5.3 CheckPoint时间设置
5.4 NameNode故障处理
5.5 集群安全模式
6 DataNode(面试开发重点)
6.1 DataNode工作机制
(1) 一个数据块在DataNode以文件形式存储在磁盘上,包含两个文件:数据、元数据。
数据块:
● 数据本身
● 元数据 — 数据块的长度、校验和、时间戳。
(2) DataNode启动后向NameNode注册,通过后,周期性(1小时)的向NameNode上报所有的块信息。
(3) 心跳(3次/s,告诉nn我还或者)
● 心跳返回结果带有NameNode给该DataNode的命令块(如:复制块数据到另一台机器,删除某个数据块等)
● 如果超过10min+30s没有所有某个DataNode的心跳,则认为该节点不可用。
(4) 集权运行中可以安全加入和退出一些机器。
6.2 数据完整性
DataNode节点保证数据完整性的方法:
● 当DataNode读取Block的时候,它会计算CheckSum。
● 如果计算后的CheckSum,与Block创建时值不一样,说明Block已经损坏。
● C lient读取其他DataNode上的Block。
● DtaNode在其文件创建后周期验证CheckSum。
6.3 掉线时限参数设置
6.4 服役新数据节点
6.5 退役旧数据节点
6.5.1 添加白名单
6.5.2 黑名单退役
6.6 DataNode多目录配置