数据量越来越大,一个操作系统中存不下所有的数据,那就需要分配到多个操作系统的磁盘上,但是由不好进行管理,因此就需要一个系统来管理多个机器上的文件,这就是分布式文件管理系统,HDFS是一种分布式管理系统。
HDFS就是Hadoop Distribute File System,他适合一次写入,多次读出的场景,且不支持文件的修改,适合用来做数据分析和大数据,可以构建在廉价机器上。但HDFS不适合存储小文件,也不适合低延时数据访问,不允许多个线程同时写入同一个数据。
组件名称 | 作用 |
NameNode(nn) | 存储文件的元数据,如文件名,文件的目录结构,以及每个文件的块列表和块所在的DataNode、处理客户端读写请求、配置副本策略、 |
DataNode(dn) | 存储文件的块信息和块数据的校验和,执行数据块的读写操作 |
SecondaryNameNode(2nn) | 用来监控HDFS状态的辅助后台程序,每隔一段时间获取HDFS的元数据快照,辅助NameNode工作,比如定期合并镜像文件和编辑日志,帮助恢复NameNode |
客户端 | 文件上传时,客户端将文件切分为一个个的块,与NameNode交互,获取文件的位置信息,与DataNode交互,进行读写操作,提供命令管理HDFS |
文件快的默认大小为128m,本文件占不满其他文件仍然可以用。不能设置的太小也不能太大。
设置的太小会增加寻址时间,也就是找到这个块开始位置的时间
设置的太大,磁盘传输时间会明显大于寻址时间,导致程序在处理这个块时特别的慢。
HDFS块的大小设置主要取决于磁盘的读取速率,寻址时间为传输时间的1%时最佳。
HDFS的Shell操作
基本语法:bin/hadoop fs 具体命令 或者bin/hdfs dfs 具体命令,其中dfs是fs的实现类
命令 | 作用 |
| 显示目录信息 |
| 创建目录 |
| 将本地文件剪切到HDFS中 |
| 将本地文件复制到HDFS中 |
| 将HDFS文件复制到本地目录 |
| 追加本地文件内容到HDFS中的文件中 |
| 显示文件信息 |
| 使用和Linux的命令相同 |
| HDFS上将文件从目录1拷贝到目录2 |
| HDFS上将文件从目录1剪切到目录2 |
| 等同于copyToLocal命令 |
| 将HDFS的文件合并下载为一个文件 |
| 等同于copyFromLocal |
| 显示文件的末尾 |
| 删除文件 |
| 删除空文件夹 |
| 统计文件的大小 |
| 设置文件的副本数 |
HDFS客户端下载与环境配置
下载对应的window版本的hadoop压缩包(下载链接),解压到非中文目录,配置环境变量。
- 在系统变量中增加HADOOP_HOME变量,值设为hadoop包的解压目录。
- 配置Path环境变量,增加一条记录%HADOOP_HOME%\bin
- 点击winutils.exe,如果黑屏一闪而过,未出现异常则表示配置成功。产生异常可能是缺少微软运行库
- 创建一个Maven工程HdfsClientDemo
- 导入相应的依赖坐标
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>3.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>3.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>3.1.3</version>
</dependency>
<dependency>
<groupId>jdk.tools</groupId>
<artifactId>jdk.tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${JAVA_HOME}/lib/tools.jar</systemPath>
</dependency>
</dependencies>
- resources目录下添加log4j.properties日志文件
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
- 编写测试类
public class HdfsClient{
@Test
public void testMkdirs()throwsIOException,InterruptedException,URISyntaxException{
//1获取文件系统
Configuration configuration=new Configuration();
//配置在集群上运行
//configuration.set("fs.defaultFS","hdfs://NameNode所在虚拟机的IP地址:9000");
//FileSystemfs=FileSystem.get(configuration);
FileSystem fs=FileSystem.get(new URI("hdfs://NameNode所在虚拟机的IP地址:8020"),configuration,"root");
//2创建目录
fs.mkdirs(newPath("/test"));
//3关闭资源
fs.close();
}
}
HDFS的API操作
配置的优先级: 代码>resources目录下配置文件(有配置文件的话会自动识别)>Linux中服务器配置文件>默认
操作 | 作用 |
copyFromLocalFile(new Path(‘windows路径’),new Path(HDFS路径)) | 上传文件到HDFS |
copyToLocalFile(new Path(‘HDFS路径’),new Path(windows路径)) | 下载文件到本地 |
delete(newPath(“/0508/”),是否递归删除) | 删除文件 |
fs.rename(newPath(原名字),new Path(目标名字)) | 修改文件名称、移动文件 |
listFiles(newPath(“/”),是否递归查看) | 查看文件详情,从详情中可以调用方法查看文件名等 |
listStatus(newPath(“/”)) | 查看文件状态,继而查看是否为文件 |
以下为获取文件详情的代码:
RemoteIterator<LocatedFileStatus> listFiles=fs.listFiles(newPath("/"),true);
while(listFiles.hasNext()){
LocatedFileStatus status=listFiles.next();
//输出详情
//文件名称
System.out.println(status.getPath().getName());
//长度
System.out.println(status.getLen());
//权限
System.out.println(status.getPermission());
//分组
System.out.println(status.getGroup());
//获取存储的块信息
BlockLocation[] blockLocations = status.getBlockLocations();
for(BlockLocationblockLocation:blockLocations){
//获取块存储的主机节点
String[]hosts=blockLocation.getHosts();
for(Stringhost:hosts){
System.out.println(host);
}
}
}
HDFS的I/O流操作
- 文件上传
@Test
public void putFileToHDFS() throws IOException, InterruptedException, URISyntaxException{
//1获取文件系统
Configurationconfiguration=new Configuration();
FileSystem fs=FileSystem.get(new
URI("hdfs://NameNode所在服务器IP地址:9000"),configuration,"root");
//2创建输入流
FileInputStream fis=new FileInputStream(new File("要上传的文件路径"));
//3获取输出流
FSDataOutputStream fos=fs.create(new Path("HDFS中的路径"));
//4流对拷
IOUtils.copyBytes(fis,fos,configuration);
//5关闭资源
IOUtils.closeStream(fos);
IOUtils.closeStream(fis);
fs.close();
}
- 文件下载
@Test
public void getFileFromHDFS()throws IOException,InterruptedException,URISyntaxException{
//1获取文件系统
Configuration configuration=new Configuration();
FileSystem fs=FileSystem.get(new URI("hdfs://NameNode所在服务器IP地址:9000"),configuration,"root");
//2获取输入流
FSDataInputStream fis=fs.open(new Path("HDFS中的文件"));
//3获取输出流
FileOutputStream fos=new FileOutputStream(new
File("保存到的文件路径"));
//4流的对拷
IOUtils.copyBytes(fis,fos,configuration);
//5关闭资源
IOUtils.closeStream(fos);
IOUtils.closeStream(fis);
fs.close();
}
- 分块读取HDFS上的大文件 下载第一块文件
public void readFileSeek1()throws IOException,InterruptedException,URISyntaxException{
//1获取文件系统
Configuration configuration=new Configuration();
FileSystem fs=FileSystem.get(new URI("hdfs://NameNode所在服务器IP地址:9000"),configuration,"root");
//2获取输入流
FSDataInputStream fis=fs.open(new
Path("/hadoop-2.10.0.tar.gz"));
//3创建输出流
FileOutputStream fos=new FileOutputStream(new File("e:/hadoop-2.7.2.tar.gz.part1"));
//4流的拷贝
byte[] buf=new byte[1024];
for(int i=0;i<1024*128;i++){
fis.read(buf);
fos.write(buf);
}
//5关闭资源
IOUtils.closeStream(fis);
IOUtils.closeStream(fos);
fs.close();
}
下载第二块:
@Test
public void readFileSeek2()throws IOException,InterruptedException,URISyntaxException{
//1获取文件系统
Configuration configuration=new Configuration();
FileSystem fs=FileSystem.get(new
URI("hdfs://NameNode所在服务器IP地址:9000"),configuration,"root");
//2打开输入流
FSDataInputStream fis=fs.open(new Path("/hadoop-2.7.2.tar.gz"));
//3定位输入数据位置
fis.seek(1024*1024*128);
//4创建输出流
FileOutputStream fos=new FileOutputStream(new File("e:/hadoop-2.7.2.tar.gz.part2"));
//5流的对拷
IOUtils.copyBytes(fis,fos,configuration);
//6关闭资源
IOUtils.closeStream(fis);
IOUtils.closeStream(fos);
}
合并两个块: 在windows的cmd命令中输入type 下载的第二个块 >> 下载的第一个块,这样就完成了拼接。
HDFS的数据流操作
- HDFS写数据流程:
- 客户端通过DistributedFileSystem模块向NameNode请求上传文件,NameNode检查目标文件是否已存在,父目录是否存在。
- NameNode返回是否可以上传
- 客户端请求第一个Block上传到哪几个DataNode服务器上。
- NameNode返回3个DataNode节点,分别为dn1、dn2、dn3。
- 客户端通过FSDataOutputStream模块请求dn1上传数据,dn1收到请求会继续调用dn2,然后dn2调用dn3,将这个通信管道建立完成。
- dn1、dn2、dn3逐级应答客户端
- 客户端开始往dn1上传第一个Block(先从磁盘读取数据放到一个本地内存缓存),以Packet为单位(每个packet由一系列chunk组成,攒够一个packet就发送),dn1收到一个Packet就会传给dn2,dn2传给dn3;dn1每传一个packet会放入一个应答队列等待应答。
- 当一个Block传输完成之后,客户端再次请求NameNode上传第二个Block的服务器
- 网络拓扑节点距离计算
NameNode在给客户端返回DataNode时会让距离近的DataNode优先存数据。
比如该图中机架1的的n-0和n-1的距离为2,路径为n-0->机架->n-1 机架1的的n-0和机架4的n-0的距离为6,路径为机架1的n-0->机架1->集群d1->集群d1和集群d2的父节点->集群d2->机架4->n1
- 机架感知(副本存储节点选择)
如果存三个副本,本地机架随机选一个节点存一个副本,本地机架其他节点存一个副本,第二个副本所在机架的另外一个随机一个节点存一个副本
- HDFS读数据流程: 假设以下场景,客户端要从服务器读数据,该数据有两个块,分别被存储在三个DataNode中,每个DataNode都存了两个块。具体的读取流程如下:
- 客户端通过DistributedFileSystem向NameNode请求下载文件,NameNode通过查询元数据,找到文件块所在的DataNode地址。
- 挑选一台DataNode(就近原则,然后随机)服务器,请求读取第一个数据块,但是当前服务器如果达到一定的负载的话就需要换另外一台。
- DataNode开始传输数据给客户端(从磁盘里面读取数据输入流,以Packet为单位来做校验)。
- 客户端以Packet为单位接收,先在本地缓存,然后写入目标文件。每个数据块串行读取,读完第一个再读第二个。
NameNode和SecondaryNameNode工作机制
NameNode的元数据是存在内存中的。但如果只存在内存中,一旦断电,元数据丢失,整个集群就无法工作了。因此产生在磁盘中备份元数据的FsImage。但是只要内存中的元数据发生变化FsImage就需要跟着变化,造成了性能的下降。因此引入Edits文件(编辑日志,只进行追加操作,效率很高)。每当元数据有更新或者添加元数据时,将对应的操作追加到Edits中。这样,一旦NameNode节点断电,可以通过FsImage和Edits的合并,合成元数据。如果长时间添加数据到Edits中,会导致该文件数据过大,效率降低,需要定期进行FsImage和Edits的合并,因此,引入一个新的节点SecondaryNamenode,专门用于FsImage和Edits的合并。
- NameNode启动
- 第一次启动NameNode格式化后,创建Fsimage和Edits文件。如果不是第一次启动,直接加载Edits和FsImage到内存
- 客户端对元数据进行增删改的请求
- NameNode记录操作日志,更新滚动日志
- NameNode在内存中对数据进行增删改
- SecondaryNameNode工作
- SecondaryNameNode询问NameNode是否需要CheckPoint(即是否需要将Edits合并到FsImage中,CheckPoint触发点为定点时间到了或者Edits存满)。直接带回NameNode是否检查结果。
- SecondaryNameNode请求执行CheckPoint。
- NameNode滚动正在写的Edits日志,即保留现在的Edits,新的写入操作写到新的Edits日志中。
- 将滚动前的编辑日志和镜像文件拷贝到SecondaryNameNode。
- SecondaryNameNode加载编辑日志和镜像文件到内存并合并。生成新的镜像文件fsimage.chkpoint。
- 拷贝fsimage.chkpoint到NameNode。NameNode将fsimage.chkpoint重新命名成fsimage。
FsImage和Edits解析
FsImage文件:包含了文件目录和文件inode序列化信息
Edits文件:包含了所有更新的操作,客户端所有的写操作会首先被记录在编辑日志中
seen_txid文件:保存的是最后一个编辑日志的序号,即edit_数字
- FsImage和Edits文件路径:/opt/module/hadoop-2.10.0/data/tmp/dfs/name/current
- 将FsImage转换为xml格式的文件以供查看:hdfs oiv -p 文件类型 -i 镜像文件 -o 转换后文件输出路径
- 将Edits转换为xml格式的文件以供查看:hdfs oev -p 文件类型 -i 编辑日志 -o 转换后文件输出路径
- FsImage中没有记录对应的DataNode,因为DataNode是每次启动集群时动态上报的,而且间隔一段时间后会继续上报
CheckPoint时间设置
- 通过时间设置 通常情况下,SecondaryNameNode每隔一小时执行一次。在hdfs-default.xml配置文件中修改或添加以下配置
<property>
<name>dfs.namenode.checkpoint.period</name>
<value>3600</value>
</property>
- 通过次数设置 以下配置当操作次数达到1百万时,SecondaryNameNode执 行一次。并且一分钟检查一次是否到达一百万次。
<property>
<name>dfs.namenode.checkpoint.txns</name>
<value>1000000</value>
<description>操作动作次数</description>
</property>
<property>
<name>dfs.namenode.checkpoint.check.period</name>
<value>60</value>
<description>1分钟检查一次操作次数</description>
</property>
NameNode故障处理
NameNode产生故障后,可以通过以下两种方式处理:
- 直接将SecondaryNameNode中数据拷贝到NameNode存储数据的目录
- NameNode产生故障停掉后,删除NameNode所在虚拟机该目录下的所有文件/hadoop-2.10.0/data/tmp/dfs/name/*
- 拷贝SecondaryNameNode中数据到当前NameNode存储数据目录,scp -r root@2NN所在虚拟机:/usr/local/hadoop-2.10.0/data/tmp/dfs/names econdary/* /usr/local//hadoop-2.10.0/data/tmp/dfs/name/
- 重启NameNode服务
- 使用-importCheckpoint选项启动NameNode守护进程
- 修改hdfs-site.xml
<!--120秒检查一次NameNode情况-->
<property>
<name>dfs.namenode.checkpoint.period</name>
<value>120</value>
</property>
<property>
<name>dfs.namenode.name.dir</name>
<value>/usr/local/hadoop-2.10.0/data/tmp/dfs/name</value>
</property>
- 删除NameNode存储的数据 用命令:rm -rf /usr/local/hadoop-2.10.0/data/tmp/dfs/name/*
- 将SecondaryNameNode存储数据的目录拷贝到NameNode存储数据的平级目录,并删除in_use.lock文件 拷贝命令:scp -r root@2NN所在虚拟机:/usr/local/hadoop-2.10.0/data/tmp/dfs/namesecondary /usr/local//hadoop-2.10.0/data/tmp/dfs/ 删除namesecondary目录中的in_use.lock文件:rm in_use.lock
- 导入检查点,等待2分钟(配置文件配置的是2分钟)后ctrl + c结束bin/hdfs namenode -importCheckpoint
- 重启NameNode
- NameNode配置多目录 每个目录存放的内容完全相同,多目录保证了NameNode的可靠性,在hdfs-site.xml中修改配置文件
<property>
<name>dfs.namenode.name.dir</name>
<value>file:///${hadoop.tmp.dir}/dfs/name1,file:///${hadoop.t
mp.dir}/dfs/name2</value>
</property>
集群的安全模式
- NameNode启动时,首先将FsImage载入内存,然后执行编辑文件中的各项操作,在内存中建立好了文件系统的元数据后,则会创建一个新的FsImage文件和空的Eidt文件,此时,namenode开始监听datanode请求。这个过程中,集群处于安全模式,即对于客户端来说是只读的。
- DataNode启动时,系统中数据块的位置并不是由NameNode来记录和维护的,而是以块列表的形式存储在datanode中。正常运行时,NameNode中确实有DataNode的位置信息,但该信息是在安全模式时,DataNode向NamoNode发送的信息
- 安全模式退出的条件:满足最小副本条件后,30秒后即可退出。最小副本条件指的是集群中的文件有99.9%都至少有1个副本(默认配置为1),刚刚格式化完的HDFS集群没有安全模式。
命令 | 说明 |
| 查看安全模式状态 |
| 进入安全模式,禁止写操作 |
| 离开安全模式 |
| 等待安全模式结束 |
DataNode工作机制
- 一个数据块在DataNode上以文件形式存储在磁盘上,包括两个文件,一个是数据本身,一个是元数据包括数据块的长度,块数据的校验和,以及时间戳。
- DataNode启动后向NameNode注册通过后,周期性(6小时)的向NameNode上报所有的块信息。
- NameNode会收到DataNode的心跳,心跳是每3秒一次。心跳返回结果带有NameNode给该DataNode的命令,如复制块数据到另一台机器,或删除某个数据块。如果超过10分钟30秒没有收到某个DataNode的心跳,则认为该节点不可用。
- 集群运行中可以安全加入和退出一些机器
当DataNode因为故障断开了与NameNode的连接,那么NameNode不会立即判定该DataNode为死亡,只有超过了设置的阈值后才会断定其已死亡。该阈值默认为10分钟30秒。计算公式为:2*dfs.namenode.heartbeat.recheck-interval+10*dfs.heartbeat.interval 这两项在hfds-site.xml中默认的配置如下:
<!--单位毫秒-->
<property>
<name>dfs.namenode.heartbeat.recheck-interval</name>
<value>300000</value>
</property>
<!--单位秒-->
<property>
<name>dfs.heartbeat.interval</name>
<value>3</value>
</property>
- 添加DataNode节点 如果存储空间不够用时,需要对DataNode进行扩容,需要在原有集群基础上动态添加新的数据节点。
- 复制虚拟机中Hadoop目录下的全部内容到另一台新虚拟机
- 在新虚拟机中删除原来HDFS文件系统留存的data和log文件
- 直接启动DataNode和NodeManager,即可关联到集群,命令分别为sbin/hadoop-daemon.sh start datanode和sbin/yarn-daemon.sh start node manager
可能产生的问题就是随便一台机器,只要加入集群就能获取集群中的数据,那么这种操作是危险的,所以应该为集群设立白名单,即所有的数据只存入白名单中的地址。
- 配置白名单(新增节点使用)
- 在路径hadoop-2.10.0/etc/hadoop下增加配置文件dfs.host,并将允许的主机名称放入其中。
- 在hdfs.site.xml中配置dfs.host的路径
<property>
<name>dfs.hosts</name>
<value>/usl/local/hadoop-2.10.0/etc/hadoop/dfs.hosts</value>
</property>
- 将集群中的每一台虚拟机的配置都改为此 可以用数据分发的命令:xsync hdfs-site.xml,该命令将其他虚拟机上的对应文件也变为本虚拟机的文件
- 刷新NameNodehdfs dfs admin-refreshNodes
- 刷新ResourceManageryarn rmadmin-refreshNodes
- 配置黑名单(退役节点使用)
- 在NameNode的/opt/module/hadoop-2.7.2/etc/hadoop目录下创建 dfs.hosts.exclude文件,并将禁止的主机名称放入其中。
- 在NameNode的hdfs-site.xml配置文件中增加dfs.hosts.exclude属性
<property>
<name>dfs.hosts.exclude</name>
<value>/usr/local/hadoop-2.10.0/etc/hadoop/dfs.hosts.exclude</value>
</property>
- 刷新NameNode、刷新ResourceManagerhdfs dfsadmin-refreshNodesyarn rmadmin-refreshNodes
- 检查Web浏览器,退役节点的状态为decommissioninprogress(退役中), 说明数据节点正在复制块到其他节点
- 等待退役节点状态为decommissioned(所有块已经复制完成),停止该节点及节点资源管理器。(如果副本数是3,服役的节点小于等于3,是不能退 役成功的,需要修改副本数后才能退役)
- Datanode多目录配置 同一份数据只会在一个目录下存在,不能起到数据备份的作用。通过修改hdfs-site.xml文件中的内容
<property>
<name>dfs.datanode.data.dir</name>
<value>file:///${hadoop.tmp.dir}/dfs/data1,file:///${hadoop.tmp.dir}/dfs/data2</value>
</property>
HDFS新特性
- 集群间的数据拷贝 除了之前讲的scp命令之外,还可以用bin/hadoop distcp 源文件 目标文件命令实现集群间的数据拷贝
- 集群存储小文件 存储小文件的弊端:每个文件按块存储,每个块的元数据存储到NameNode的内存中,大量小文件会耗尽NameNode所在机器的内存,因此HDFS存储小文件的效率会非常低。 解决方法之一:HDFS存档文件(HAR文件)
- 启动yarn:start-yarn.sh
- 小文件归档:bin/hadoop archive -archiveName 归档后名称.har –p 归档文件所在路径 回档后路径
- 查看归档:需要用到har://协议,具体命令是hadoop fs -lsr har://归档后路径
- 解归档:hadoop fs -cp har://回档后文件路径/* 解档后的文件位置
- HDFS回收站 跟windows回收站作用类似,默认是关闭的,需要在core-site.xml的配置文件中启动。
- 启用回收站fs.trash.interval表示60秒内文件是存活的,可以通过回收站恢复。fs.trash.checkpoint.interval表示每10秒检查一次60秒的时间是否已经到了。
<property>
<name>fs.trash.interval</name>
<value>1</value>
</property>
<property>
<name>fs.trash.checkpoint.interval</name>
<value>10</value>
</property>
- 修改访问垃圾回收站用户名称 默认的用户是dr.who
<property>
<name>hadoop.http.staticuser.user</name>
<value>root</value>
</property>
- 查看回收站的路径 该路径在HDFS中查看,一般是在删除文件的那个目录下。比如我要删除的input文件夹在/user/root下,那么回收站的路径一般为:/user/root/.Trash/,其中.Trash文件夹即为回收站
- 恢复文件 将回收站中的文件移动到正常的目录下即可。hadoop fs -mv /user/root/.Trash/Current/user/root/input /user/root/input
- HDFS快照管理
命令 | 说明 |
| 对某个路径允许快照 |
| 对某个路径禁止快照 |
| 给路径创建快照 |
| 给路径创建快照 |
| 比较两个快照的不同 |
下一篇介绍
Yarn学习笔记