Hadoop(三)HDFS
1.HDFS概述
1.1 定义
HDFS(Hadoop Distributed File System),它是一个文件系统,用于存储文件,通过目录树来定位文件;其次,它是分布式的,由很多服务器联合起来实现其功能,集群中的服务器有各自的角色
HDFS的使用场景:适合一次写入,多次读出的场景
。一个文件经过创建、写入和关闭之后就不需要改变
1.2 优缺点
优点
- 数据自动保存多个副本,提高容错性,某一个副本丢失后,它可以自动恢复
- 适合处理大数据,如TB、PB级别的数据,能够处理百万规模以上的文件数量
- 可构建在廉价机器上
缺点
- 不适合低延迟数据访问,比如毫秒级的存储数据,不能达到MySQL的访问速度
- 无法高效的对大量小文件进行存储
- 不支持并发写入、文件随机修改,一个文件只能有一个写,只支持文件的追加,不能修改历史数据
1.3 HDFS构成
Namenode:管理者
- 管理HDFS的名称空间
- 配置副本策略
- 管理数据块映射信息
- 处理客户端读写请求
Datanode:执行者
- 存储实际的数据块
- 执行数据块的读写操作
Client:客户端
- 文件切分,Client负责将上传到HDFS的文件分成一个一个Block
- 与Namenode交互,获取文件的位置信息
- 与Datanode交互,读取或者写入数据
- Client提供一些命令管理HDFS并对数据进行增删改查操作
1.4 HDFS文件块大小
默认大小为128M,可以通过dfs.blocksize设置大小
如果设置太小,会增加寻址时间,导致程序一直找块的开始位置
如果设置太大,从磁盘传输数据的时间会明显大于定位这个块开始位置所需要的时间,导致程序处理这块数据时,会很慢
HDFS块大小的设置主要取决于磁盘传输速率,寻址时间为传输时间的1%时(经验),最好
2.HDFS的Shell操作
命令基本格式:hadoop fs 命令
上传到HDFS
-moveFromLocal
:从本地剪切
粘贴到HDFS
[gzhu@hadoop102 hadoop-3.1.3]$ hadoop fs -mkdir /sanguo
[gzhu@hadoop102 hadoop-3.1.3]$ vim shuguo.txt 文件里面输入shuguo
[gzhu@hadoop102 hadoop-3.1.3]$ hadoop fs -moveFromLocal ./shuguo.txt /sanguo
-put
:从本地文件系统中拷贝
文件到HDFS路径去
[gzhu@hadoop102 hadoop-3.1.3]$ vim weiguo.txt 文件里面输入weiguo
[gzhu@hadoop102 hadoop-3.1.3]$ hadoop fs -put ./weiguo.txt /sanguo
-appendToFile
:追加一个文件到已经存在的文件末尾
[gzhu@hadoop102 hadoop-3.1.3]$ vim liubei.txt 输入liubei
[gzhu@hadoop102 hadoop-3.1.3]$ hadoop fs -appendToFile liubei.txt /sanguo/shuguo.txt
下载到本地
-get
:从HDFS拷贝到本地 等同于copyToLocal
[gzhu@hadoop102 hadoop-3.1.3]$ hadoop fs -get /sanguo/shuguo.txt ./
HDFS直接操作
-ls
: 显示文件目录信息
[gzhu@hadoop102 hadoop-3.1.3]$ hadoop fs -ls /sanguo
-cat
:显示文件内容
[gzhu@hadoop102 hadoop-3.1.3]$ hadoop fs -cat /sanguo/shuguo.txt
-chgrp、-chmod、-chown
:Linux文件系统中的用法一样,修改文件所属权限
chmod [{ugoa}{+-=}{rwx}] [文件或目录]
ugoa:
u:所有者
g:所属组
o:其他人
a:所有人
+-=:
+:针对文件或目录增加某个权限
-:针对文件或目录减少某个权限
=:赋予文件或目录全新的权限,以此刻的权限为准
示例:chomd u+x ./test.txt
chgrp 所属组 文件
chown [用户] [文件或目录]
在linux中只有root能改变文件所有者,即便是创建者都不可以
-cp
:从HDFS的一个路径拷贝到HDFS的另一个路径
[gzhu@hadoop102 hadoop-3.1.3]$ hadoop fs -mkdir /jinguo
[gzhu@hadoop102 hadoop-3.1.3]$ hadoop fs -cp /sanguo/shuguo.txt /jinguo
-mv
:在HDFS目录中剪切移动
文件
-tail
:显示一个文件的末尾1kb的数据
-du
:统计文件夹的大小信息
[gzhu@hadoop102 hadoop-3.1.3]$ hadoop fs -du -h /sanguo
[gzhu@hadoop102 hadoop-3.1.3]$ hadoop fs -du -s -h /sanguo
添加-s输出文件总和
7表示文件大小;21表示7*3个副本
-rm
:删除文件或文件夹
-rm -r
:递归删除目录及目录里面内容
3.IDEA操作hadoop
3.1 环境准备
将windows下的hadoop文件放在一个非中文目录下
配置环境变量
测试,双击,如果一闪而过,说明正常!
3.2 IDEA测试demo
package com.gzhu.hdfs;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
public class HdfsClient {
FileSystem fs;
@Before
public void init() throws URISyntaxException, IOException, InterruptedException {
// 连接集群的nn地址,8020是内部通信端口而非web端口
final URI uri = new URI("hdfs://hadoop102:8020");
//创建配置文件
Configuration configuration = new Configuration();
//用户
String user = "gzhu";
// 获取客户端对象
fs = FileSystem.get(uri,configuration,user);
}
@After
public void close() throws IOException {
// 关闭资源
fs.close();
}
// 创建文件夹
@Test
public void testMkdir() throws IOException, URISyntaxException, InterruptedException {
fs.mkdirs(new Path("/gzhu"));
}
// 上传文件
@Test
public void testPut() throws IOException, URISyntaxException, InterruptedException {
// 参数一:是否删除源文件 参数二:是否覆盖 参数三:本地文件路径 参数四:HDFS文件路径
fs.copyFromLocalFile(false,true,new Path("F:/hadoop_test/computer.txt"),new Path("/gzhu"));
}
// 文件下载
@Test
public void testGet() throws IOException {
// 参数一:是否删除源文件 参数二:HDFS路径 参数三:本地路径 参数四:是否循环校验
fs.copyToLocalFile(false,new Path("/gzhu"),new Path("F:\\"),true);
}
// 文件删除
@Test
public void testRm() throws IOException {
// 参数一:要删除的路径 参数二:是否递归删除
fs.delete(new Path("/gzhu/computer.txt"),false);
}
// 文件目录名称修改
@Test
public void testMv() throws IOException {
fs.rename(new Path("/gzhu/开发基本.md"),new Path("/gzhu/程序开发.md"));
}
// 文件剪切移动
@Test
public void testGo() throws IOException {
fs.rename(new Path("/gzhu/程序开发.md"),new Path("/java"));
}
// 获取文件详情信息
@Test
public void fileDetail() throws IOException {
// 参数一:路径 参数二:是否递归
RemoteIterator<LocatedFileStatus> files = fs.listFiles(new Path("/"), true);
// 遍历文件
while(files.hasNext()){
LocatedFileStatus fileStatus = files.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.getModificationTime());
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 testfile() throws IOException {
FileStatus[] listFiles = fs.listStatus(new Path("/"));
for (FileStatus file : listFiles
) {
if(file.isFile()){
System.out.println("文件:"+file.getPath().getName());
}else{
System.out.println("目录:"+file.getPath().getName());
}
}
}
}
参数优先级
参数优先级排序:(1)客户端代码中设置的值 >(2)ClassPath下的用户自定义配置文件 >(3)然后是服务器的自定义配置(xxx-site.xml) >(4)服务器的默认配置(xxx-default.xml)
4.HDFS写数据原理
(1)客户端创建Distributed FileSystem模块
(该模块封装了与HDFS文件系统操作相关的方法)通过Distributed FileSystem模块的create()
方法通过RPC请求
向NameNode请求上传文件,NameNode检查目标文件是否已存在,父目录是否存在,权限是否允许
(2)NameNode返回是否可以上传,若可以,NameNode会为本次请求记录,返回FSDataOutStream
输出流对象给客户端用于写数据
(3)客户端请求第一个Block(0-128M)
上传到哪几个DataNode服务器上
(4)NameNode返回3个DataNode节点,分别为dn1、dn2、dn3(通过负载量和距离来选择dn)
(5)客户端通过FSDataOutputStream模块请求dn1上传数据,dn1收到请求会继续调用dn2,然后dn2调用dn3,将这个通信管道
建立完成
(6)dn1、dn2、dn3逐级应答客户端确保可以传输副本
(7)客户端开始往dn1上传第一个Block(先从磁盘读取数据放到一个本地内存缓存),以Packet(64k)为单位,dn1收到一个Packet就会传给dn2,dn2传给dn3;dn1每传一个packet会放入一个应答队列(应答队列在客户端)等待应答,等待DataNode的收到确认回执,称为“确认队列”(ack queue)。收到管道中所有DataNode确认信息后,该数据包才会从确认队列删除。Write ID: 当客户端发送一个packet时,它会分配一个唯一的write ID给这个packet。DataNodes会使用这个write ID来识别和跟踪packet。这样客户端重新发送同一个packet,DataNodes也能够识别出来并避免重复写入。
(8)当一个Block传输完成之后,客户端再次请求NameNode上传第二个Block的服务器(重复执行3-7步)
(9)全部传输完成后关闭FSDataOutStream,告知NameNode传输完成
上传的时候,有一个DN挂了怎么办?
客户端收不到ack,会通知NN,NN检查DN不符合要求,会通知其他DN复制挂掉的DN,并下线挂掉的DN
5.DataNode选择
在HDFS写数据的过程中,NameNode会选择距离待上传数据最近距离的DataNode接收数据
节点距离:两个节点到达最近的共同祖先的距离总和
如图,n-1和n-2的共同祖先为集群d1
n-1到其机架r2的距离为1,机架r2到集群d1的距离为1
n-2到其机架r3的距离为1,机架3到集群d1的距离为1
所以,两节点之间的距离为1+1+1+1=4
副本节点选择
第一个副本在Client所处的节点上,如果客户端在集群外,随机选择一个
第二个副本在另一个机架的随机节点
第三个副本在第二个副本所处机架的随机节点
6.HDFS读数据流程
(1)客户端通过DistributedFileSystem向NameNode请求下载文件,NameNode首先会判断权限并检查是否有对应的数据,如果符合,NameNode通过查询元数据,找到文件块所在的DataNode地址返回个客户端
(2)创建FSDataInputStream,挑选一台DataNode(就近原则)服务器,如果该服务器访问量太大,会随机访问其他节点,请求读取数据
(3)DataNode开始传输数据给客户端(从磁盘里面读取数据输入流,以Packet为单位来做校验)
(4)客户端以Packet为单位接收,先在本地缓存,然后写入目标文件,是串行读,即读取文件的某一块后,再读其他的部分进行追加组成完整的数据
读的时候,DN挂了怎么办
校验和不一致,客户端通知NN,NN会再选择一个
7.NN和2NN工作机制
Fsimage镜像文件和Edits编辑日志文件
fsimage(镜像文件)
:是Hadoop文件系统元数据的一个永久性的检查点,含了整个HDFS文件系统的所有目录和文件的信息,是NameNode在内存中元数据的在磁盘上的备份
Edits(编辑日志)
:存放的是Hadoop文件系统的所有更新操作的路径,客户端执行的所有写操作首先会被记录到edits文件中
1.2NN定时到NameNode去获取Edits(或者Edits文件太多,必须进行合并),并更新到fsimage上(Secondary NameNode自己的fsimage)
2.一旦它有了新的fsimage文件,它将其拷贝回NameNode中
3.NameNode在下次重启时会使用这个新的fsimage文件,从而减少重启的时间
SecondaryNameNode每隔一小时查询NN的追加日志一次
一分钟检查一次操作次数,当操作次数达到1百万时,SecondaryNameNode执行一次
8.DataNode工作机制
(1)一个数据块在DataNode上以文件形式存储在磁盘上,包括两个文件,一个是数据本身,一个是元数据包括数据块的长度,块数据的校验和,以及时间戳
(2)DataNode启动后向NameNode注册,通过后,周期性(6小时)的向NameNode上报所有的块信息
(3)心跳DN向NN发送,每3秒一次,收到心跳返回带有NameNode给该DataNode的命令如复制块数据到另一台机器,或删除某个数据块。如果超过10分钟+30秒没有收到某个DataNode的心跳,则认为该节点不可用
(4)集群运行中可以安全加入和退出一些机器
DataNode数据完整性
如果电脑磁盘里面存储的数据是控制高铁信号灯的红灯信号(1)和绿灯信号(0),但是存储该数据的磁盘坏了,一直显示是绿灯,是否很危险?同理DataNode节点上的数据损坏了,却没有发现,是否也很危险,那么如何解决呢?
DataNode节点保证数据完整性的方法如下
(1)当DataNode读取Block的时候,它会计算CheckSum,HDFS写入的时候计算出校验和,然后每次读的时候再计算校验和
(2)如果计算后的CheckSum,与Block创建时值不一样,说明Block已经损坏
(3)Client读取其他DataNode上的Block
(4)常见的校验算法crc(32),md5(128),sha1(160)
(5)DataNode在其文件创建后周期验证CheckSum