1 HDFS概述
1.1 HDFS产出背景及定义
1.1.1 HDFS产生背景
随着数据量越来越多,在一个操作系统存不下所有的数据,那么就分配到更多的操作系统管理的磁盘中,但是不方便管理和维护,迫切需要一种系统来管理多台机器上的文件,这就是分布式文件管理系统。HDFS只是分布式文件管理系统中的一种。
1.1.2 HDFS定义
HDFS(Hadoop Distributed File System),它是一个文件系统,用于存储文件,通过目录树来定义文件;其次,它是分布式的,由很多服务器联合起来实现其功能,集群中的服务器有各自的角色。
HDFS的使用场景:适合一次写入,多次读出的场景,且不支持文件的修改。适合用来做数据分析,并不适合用来做网盘应用。
1.2 HDFS优缺点
1.2.1 优点
- 高容错性:数据自动保存多个副本,如果某个副本丢失可以其它节点自动恢复
- 适合处理大数据:能够处理TB、PB级别的数据,能处理百万规模以上的文件数量。
- 低成本:可构建在廉价机器上,通过多副本机制提高可靠性
1.2.2 缺点
- 不合适低延时数据访问,比如毫秒级的存储数据。
- 无法高效的对大量小文件进行存储。
(1)存储大量小文件的话,它会占用Namenode大量内存来存储文件目录和块信息,而Namenode的内存是有限的。
(2)小文件存储的寻址时间会超过读取时间,它违反了HDFS的设计目标。 - 不支持并发写入、文件随机修改。
(1)一个文件只能有一个写,不允许多个线程同时写。
(2)仅支持数据append(追加),不支持文件的随机修改,因为HDFS没有提供对文件的在线寻址(打开)功能,文件以块形式存储,修改了一个块中的内容,就会影响当前块之后所有的块,效率低。
1.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
1.4 HDFS文件块
1.4.1 文件块大小
HDFS中的文件在物理上是分块存储(Block)的,块的大小可以通过配置参数dfs.blocksize
来设定,Hadoop1.x中默认是64M,Hadoop2.x默认是128M。HDFS块的大小设置主要取决于磁盘传输速率。
不论对磁盘的文件进行读还是写,都需要先进行寻址,根据最佳传输损耗理论:在一次传输中,寻址时间占用总传输时间的1%时,本次传输的损耗最小,为最佳性价比传输,目前硬件的发展条件,普通磁盘写的速率大概为100M/S, 寻址时间一般为10ms!
10ms / 1% = 1s
1s * 100M/S=100M
块在传输时,每64K还需要校验一次,因此块大小,必须为2的n次方,最接近100M的就是128M
Block的大小不能设置太小或太大:
- HDFS的块设置太小,会增加寻址时间
- HDFS的块设置太大,从磁盘传输数据的时间会明显大于寻址时间
1.4.2 文件块属性
- length: 块的实际大小
- offset: 块的便宜量,这个块从文件的哪部分开始保存数据
1.5 副本
副本数的概念指的是最大副本数,具体存放几个副本需要参考DN节点的数量,每个DN节点最多只能存储一个副本。
2 HDFS的Shell操作
shell操作命令:
bin/hdfs dfs command params
:只操作分布式的HDFSbin/hadoop fs command params
:既可以操作本地模式的HDFS也可以操作分布式的HDFS
[yut@hadoop101 hadoop-2.7.2]$ hadoop fs
Usage: hadoop fs [generic options]
[-appendToFile <localsrc> ... <dst>]
[-cat [-ignoreCrc] <src> ...]
[-checksum <src> ...]
[-chgrp [-R] GROUP PATH...]
[-chmod [-R] <MODE[,MODE]... | OCTALMODE> PATH...]
[-chown [-R] [OWNER][:[GROUP]] PATH...]
[-copyFromLocal [-f] [-p] [-l] <localsrc> ... <dst>]
[-copyToLocal [-p] [-ignoreCrc] [-crc] <src> ... <localdst>]
[-count [-q] [-h] <path> ...]
[-cp [-f] [-p | -p[topax]] <src> ... <dst>]
[-createSnapshot <snapshotDir> [<snapshotName>]]
[-deleteSnapshot <snapshotDir> <snapshotName>]
[-df [-h] [<path> ...]]
[-du [-s] [-h] <path> ...]
[-expunge]
[-find <path> ... <expression> ...]
[-get [-p] [-ignoreCrc] [-crc] <src> ... <localdst>]
[-getfacl [-R] <path>]
[-getfattr [-R] {-n name | -d} [-e en] <path>]
[-getmerge [-nl] <src> <localdst>]
[-help [cmd ...]]
[-ls [-d] [-h] [-R] [<path> ...]]
[-mkdir [-p] <path> ...]
[-moveFromLocal <localsrc> ... <dst>]
[-moveToLocal <src> <localdst>]
[-mv <src> ... <dst>]
[-put [-f] [-p] [-l] <localsrc> ... <dst>]
[-renameSnapshot <snapshotDir> <oldName> <newName>]
[-rm [-f] [-r|-R] [-skipTrash] <src> ...]
[-rmdir [--ignore-fail-on-non-empty] <dir> ...]
[-setfacl [-R] [{-b|-k} {-m|-x <acl_spec>} <path>]|[--set <acl_spec> <path>]]
[-setfattr {-n name [-v value] | -x name} <path>]
[-setrep [-R] [-w] <rep> <path> ...]
[-stat [format] <path> ...]
[-tail [-f] <file>]
[-test -[defsz] <path>]
[-text [-ignoreCrc] <src> ...]
[-touchz <path> ...]
[-truncate [-w] <length> <path> ...]
[-usage [cmd ...]]
Generic options supported are
-conf <configuration file> specify an application configuration file
-D <property=value> use value for given property
-fs <local|namenode:port> specify a namenode
-jt <local|resourcemanager:port> specify a ResourceManager
-files <comma separated list of files> specify comma separated files to be copied to the map reduce cluster
-libjars <comma separated list of jars> specify comma separated jar files to include in the classpath.
-archives <comma separated list of archives> specify comma separated archives to be unarchived on the compute machines.
The general command line syntax is
bin/hadoop command [genericOptions] [commandOptions]
常用命令实操
1.-help:命令帮助
[yut@hadoop101 hadoop-2.7.2]$ hadoop fs -help ls
2.-ls:显示目录信息
#递归
[yut@hadoop101 hadoop-2.7.2]$ hadoop fs -ls -R -h /
#目录作为普通文件列出
[yut@hadoop101 hadoop-2.7.2]$ hadoop fs -ls -d /
3.-mkdir:在HDFS上创建目录
#目录已存在不发生错误
[yut@hadoop101 hadoop-2.7.2]$ hadoop fs -mkdir -p /user/yut/input
4.-moveFromLocal:从本地剪切粘贴到HDFS
[yut@hadoop101 hadoop-2.7.2]$ hadoop fs -moveFromLocal ./wcinput/wc.input /user/yut/input/
5.-appendToFile:追加一个文件到已经存在的文件末尾
[yut@hadoop101 hadoop-2.7.2]$ hadoop fs -appendToFile wcinput/wc1.input /user/yut/input/wc.input
6.-cat:显示文件的内容
[yut@hadoop101 hadoop-2.7.2]$ [yut@hadoop101 hadoop-2.7.2]$ hadoop fs -cat /user/yut/input/wc.input
7.-chgrp、-chmod、-chown:Linux文件系统中的用法一样,修改文件所属权限
[yut@hadoop101 hadoop-2.7.2]$ hadoop fs -chmod 666 /user/yut/input/wc.input
[yut@hadoop101 hadoop-2.7.2]$ hadoop fs -chgrp root /user/yut/input/wc.input
[yut@hadoop101 hadoop-2.7.2]$ hadoop fs -chown yut:yut /user/yut/input/wc.input
8.-copyFromLocal:从本地文件系统中copy文件到HDFS
[yut@hadoop101 hadoop-2.7.2]$ hadoop fs -copyFromLocal input/core-site.xml /user/yut/input/
9.-copyToLocal:从HDFS copy文件到本地
[yut@hadoop101 hadoop-2.7.2]$ hadoop fs -copyToLocal /user/yut/input/wc.input ./
10.-cp :从HDFS的一个路径copy到HDFS的另一个路径
[yut@hadoop101 hadoop-2.7.2]$ hadoop fs -cp /user/yut/input/hadoop-2.7.2.tar.gz /user/
3 HDFS客户端操作
1.创建maven工程,引入以下依赖
<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>2.7.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>2.7.2</version>
</dependency>
2.案例
package com.yt.hdfs;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*;
import org.apache.hadoop.fs.permission.FsAction;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.io.IOUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
/**
1. FileSystem: 文件系统的抽象基类
FileSystem的实现取决于fs.defaultFS的配置!
有两种实现!
LocalFileSystem: 本地文件系统 fs.defaultFS=file:///
DistributedFileSystem: 分布式文件系统 fs.defaultFS=hdfs://xxx:9000
声明用户身份:
FileSystem fs = FileSystem.get(new URI("hdfs://hadoop101:9000"), conf, "yut");
2. Configuration : 功能是读取配置文件中的参数
Configuration在读取配置文件的参数时,根据文件名,从类路径按照顺序读取配置文件!
先读取 xxx-default.xml,再读取xxx-site.xml
Configuration类一加载,就会默认读取8个配置文件!
将8个配置文件中所有属性,读取到一个Map集合中!
也提供了set(name,value),来手动设置用户自定义的参数!
3. FileStatus
代表一个文件的状态(文件的属性信息)
4. offset和length
offset是偏移量: 指块在文件中的起始位置
length是长度,指块大小
sts.zip 390M
length offset
blk1: 0-128M 128M 0
blk2: 128M-256M 128M 128M
...
blk4: 384M-390M 6M 384M
5. LocatedFileStatus
LocatedFileStatus是FileStatus的子类,除了文件的属性,还有块的位置信息!
*/
public class TestHDFS {
private FileSystem fs;
private Configuration conf = new Configuration();
//Linux权限为754
private FsPermission fsPermission = new FsPermission(FsAction.ALL, FsAction.READ_EXECUTE, FsAction.READ);
@Before
public void init() {
//通过API设置副本数
conf.set("dfs.replication", "2");
try {
//获取文件系统,并声明用户身份
fs = FileSystem.get(new URI("hdfs://172.31.140.220:9000"), conf, "yut");
} catch (IOException | InterruptedException | URISyntaxException e) {
e.printStackTrace();
}
}
@After
public void close() throws IOException {
//关闭资源
if (fs != null) {
fs.close();
}
}
/**
* 创建目录
*/
@Test
public void testMkdirs() throws IOException {
// 创建目录,具有Unix{mkdir -p}的语义
fs.mkdirs(new Path("/client/input"), fsPermission);
}
/**
* 上传本地文件到HDFS(层级目录不存在会创建)
*/
@Test
public void CopyFromLocal() throws IOException {
fs.copyFromLocalFile(false, true, new Path("C:\\Users\\yutyi\\Desktop\\id_rsa_47.105.150.128"), new Path("/client/input/id_rsa_47.105.150.128"));
}
/**
* 从HDFS下载文件到本地
*/
@Test
public void CopyToLocal() throws IOException {
fs.copyToLocalFile(false, new Path("/client/input/id_rsa_47.105.150.128"), new Path("C:\\Users\\yutyi\\Desktop\\id_rsa_47.105.150.128"), true);
}
/**
* 文件(夹)删除
*/
@Test
public void delete() throws IOException {
//递归删除,如果路径是文件夹必须设置为true
fs.delete(new Path("/client/input"), true);
}
/**
* 文件更名或移动到已存在目录(mv)
*/
@Test
public void rename() throws IOException {
boolean rename = fs.rename(new Path("/client/input/id_rsa_47.105.150.128"), new Path("/client/id_rsa_47.105.150.128"));
}
/**
* 查询目录下所有文件详情
*/
@Test
public void listFile() throws IOException {
//递归列出所有档案(不包含目录)
RemoteIterator<LocatedFileStatus> files = fs.listFiles(new Path("/"), true);
while (files.hasNext()) {
LocatedFileStatus file = files.next();
if (!file.isFile()) {
System.out.println(file.getPath() + "是目录");
} else {
//档案完整路径
Path path = file.getPath();
//拥有者
String owner = file.getOwner();
//拥有组
String group = file.getGroup();
//档案字节数
long len = file.getLen();
//mtime
long mTime = file.getModificationTime();
//副本数
short replication = file.getReplication();
//档案权限
FsPermission permission = file.getPermission();
System.out.println(permission + " " + path +
" " + owner + ":" + group + " " +
len + " " + mTime + " " + replication);
//档案块所属位置
BlockLocation[] blockLocations = file.getBlockLocations();
for (BlockLocation blockLocation : blockLocations) {
//块主机名称(hostname)集合
String[] hosts = blockLocation.getHosts();
for (String host : hosts) {
System.out.println(host);
}
//块位置(IP:xferPort)集合
String[] names = blockLocation.getNames();
for (String name : names) {
System.out.println(name);
}
}
}
}
}
/**
* 判断某个路径是档案还是目录
*/
@Test
public void listStatus() throws IOException {
RemoteIterator<FileStatus> listStatus = fs.listStatusIterator(new Path("/"));
while (listStatus.hasNext()) {
FileStatus fileStatus = listStatus.next();
if (fileStatus.isFile()) {
System.out.println("f:" + fileStatus.getPath());
} else {
System.out.println("d:" + fileStatus.getPath());
}
}
}
/**
* 文件流上传文件
*/
@Test
public void upStream() throws IOException {
//获取输入流
FileInputStream is = new FileInputStream("C:\\Users\\yutyi\\Desktop\\hadoop-2.7.2.tar.gz");
//获取输出流
FSDataOutputStream fsDataOutputStream = fs.create(new Path("/client/upload/hadoop-2.7.2.tar.gz"));
//流的对拷
IOUtils.copyBytes(is, fsDataOutputStream, conf);
//关闭流
IOUtils.closeStream(is);
IOUtils.closeStream(fsDataOutputStream);
}
/**
* 文件流下载文件
*/
@Test
public void downStream() throws IOException {
//获取输出流
FileOutputStream is = new FileOutputStream("C:\\Users\\yutyi\\Desktop\\hadoop-2.7.2.tar.gz");
//获取输入流
FSDataInputStream fsDataInputStream = fs.open(new Path("/client/upload/hadoop-2.7.2.tar.gz"));
IOUtils.copyBytes(fsDataInputStream, is, conf);
IOUtils.closeStream(is);
IOUtils.closeStream(fsDataInputStream);
}
/**
* 定位读取文件(分块下载)
*/
@Test
public void readFileSeek() throws IOException {
//获取输入流
FSDataInputStream fis = fs.open(new Path("/client/upload/hadoop-2.7.2.tar.gz"));
RemoteIterator<LocatedFileStatus> listFiles = fs.listFiles(new Path("/client/upload/hadoop-2.7.2.tar.gz"), false);
if (listFiles.hasNext()) {
LocatedFileStatus fileStatus = listFiles.next();
//获取块大小
long blockSize = fileStatus.getBlockSize();
//获取块信息
BlockLocation[] blockLocations = fileStatus.getBlockLocations();
System.out.println(blockLocations.length);
for (int i = 0; i < blockLocations.length; i++) {
//获取输出流
FileOutputStream fos = new FileOutputStream("C:\\Users\\yutyi\\Desktop\\hadoop-2.7.2.tar.gz.part" + i);
if (i == blockLocations.length - 1) {
//最后一个块数据直接copy出去
IOUtils.copyBytes(fis, fos, conf);
IOUtils.closeStream(fos);
IOUtils.closeStream(fis);
IOUtils.closeStream(fos);
fs.close();
} else {
byte[] buf = new byte[1024];
for (int j = 0; j < blockSize / 1024; j++) {
fis.read(buf);
fos.write(buf);
}
//偏移量
fis.seek(blockSize);
fos.close();
}
}
}
}
}
4 HDFS数据流
4.1 HDFS写数据流程
- 客户端创建DistributedFileSystem(分布式文件系统)对象,通过该对象向NameNode请求上传文件。
- NameNode检查父目录和目标文件是否存在。
- NameNode返回是否可以上传文件。
- 客户端通过FSDataOutputStream上传数据,FSDataOutputStream根据客户端参数将文件分块(默认块大小128M)。
- 客户端请求第一个 Block上传到哪几个DataNode服务器上。
- NameNode根据客户端上传文件的副本数(默认为3),根据机架感知策略选取指定数量的DataNode节点返回,并将相关信息存到元数据中。
- 客户端接收到NameNode返回的DataNode服务器列表。
- 客户端根据返回的DN节点,请求建立传输通道,客户端向最近(网络距离最近)的DN节点发起通道建立请求,由这个DN节点依次向通道中的(距离当前DN距离最近)下一个节点发送建立通道请求,各个节点发送响应 ,通道建立成功。
- 客户端每读取64K的数据,封装为一个packet(数据包,传输的基本单位),将packet发送到通道的下一个节点,通道中的节点收到packet之后,落盘(检验)存储,将packet发送到通道的下一个节点,每个节点在收到packet后,向客户端发送ack确认消息。
- 当一个block传输完成之后,客户端再次请求NameNode上传第二个block的服务器(重复执行5-10步)。
异常情况:
客户端每读取64K的数据,封装为一个packet,封装成功的packet,放入到一个队列中,这个队列称为dataQuene(待发送数据包);在发送时,先将dataQuene中的packet按顺序发送,发送后再放入到ackquene(正在发送的队列)。每个节点在收到packet后,向客户端发送ack确认消息!如果一个packet在发送后,已经收到了所有DN返回的ack确认消息,这个packet会在ackquene中删除!假如一个packet在发送后,在收到DN返回的ack确认消息时超时,传输中止,ackquene中的packet会回滚到dataQuene。重新建立通道,剔除坏的DN节点。建立完成之后,继续传输!只要有一个DN节点收到了数据,DN上报NN已经收完此块,NN就认为当前块已经传输成功。副本数如果暂时不满足条件,之后NN会自动检查,维护副本数。
4.2 HDFS读数据流程
- 客户端创建DistributedFileSystem(分布式文件系统)对象,通过该对象向NameNode请求读取文件。
- NameNode查询元数据,查找目标文件是否存在。
- NameNode返回目标文件Block所在的DataNode地址等。
- 挑选一台DataNode(就近原则,然后随机)服务器,请求读取数据。
- DataNode开始传输数据给客户端(从磁盘里面读取数据输入流,以packet为单位来做校验)。
- 客户端以packet为单位接收,先在本地缓存,然后写入目标文件。
4.3 节点选择(机架感知)
在HDFS写数据的过程中,NameNode会选择距离待上传数据最近距离的DataNode接收数据。
节点距离:两个节点到达最近的共同祖先的距离总和。
Hadoop2.7.2的默认的机架感知策略: 在本地机架挑选一个节点,保存第一个副本!如果本地机架没有DN节点,挑选距离本地机架最近的一个节点。在本机机架挑选另一个节点,保存第二个副本,如果本地机架没有DN节点,挑选距离本地机架最近的一个节点。在其他机架选择一个节点,保存第三个副本(为了安全性)。
机器感知(副本存储节点选择)说明:http://hadoop.apache.org/docs/r2.7.2/hadoop-project-dist/hadoop-hdfs/HdfsDesign.html#Data_Replication
5 Namenode与SecondaryNamenode
5.1 工作机制
思考:NameNode中的元数据是存储在哪里的?
首先,我们做个假设,如果存储在NameNode节点的磁盘中,因为经常需要进行随机访问,还有响应客户请求,必然是效率过低。因此,元数据需要存放在内存中。但如果只存在内存中,一旦断电,元数据丢失,整个集群就无法工作了。因此产生在磁盘中备份元数据的FsImage。这样又会带来新的问题,当在内存中的元数据更新时,如果同时更新FsImage,就会导致效率过低,但如果不更新,就会发生一致性问题,一旦NameNode节点断电,就会产生数据丢失。因此,引入Edits文件(只进行追加操作,效率很高)。每当元数据有更新或者添加元数据时,修改内存中的元数据并追加到Edits中。这样,一旦NameNode节点断电,可以通过FsImage和Edits的合并,合成元数据。
但是,如果长时间添加数据到Edits中,会导致该文件数据过大,效率降低,而且一旦断电,恢复元数据需要的时间过长。因此,需要定期进行FsImage和Edits的合并,如果这个操作由NameNode节点完成,又会效率过低。因此,引入一个新的节点SecondaryNamenode,专门用于FsImage和Edits的合并。
- 第一阶段:NameNode启动
(1)第一次启动NameNode格式化后,创建Fsimage和Edits文件。如果不是第一次启动,直接加载编辑日志(edits_inprogress)和镜像文件(fsimage)到内存合并得到最新的元数据,并将元数据持久化到磁盘生成新的fsimage文件。
(2)客户端对元数据进行增删改的请求。
(3)NameNode记录操作日志,更新滚动日志。
(4)NameNode在内存中对元数据进行增删改。 - 第二阶段:Secondary NameNode工作
(1)SecondaryNameNode询问NameNode是否需要CheckPoint,直接带回NameNode是否需要CheckPoint的结果。
(2)SecondaryNameNode请求执行CheckPoint。
(3)NameNode滚动正在写的Edits日志(edits_inprogress)。
(4)将滚动前的编辑日志和镜像文件拷贝到SecondaryNameNode。
(5)SecondaryNameNode加载编辑日志和镜像文件到内存,并合并。
(6)生成新的镜像文件fsimage.chkpoint。
(7)拷贝fsimage.chkpoint到NameNode。
(8)NameNode将fsimage.chkpoint重新命名成fsimage
NN和2NN工作机制详解:
Fsimage:NameNode内存中元数据序列化后形成的文件。
Edits:记录客户端更新元数据信息的每一步操作(可通过Edits运算出元数据)。
NameNode启动时,先滚动Edits并生成一个空的edits.inprogress,然后加载Edits和Fsimage到内存中,此时NameNode内存就持有最新的元数据信息。Client开始对NameNode发送元数据的增删改的请求,这些请求的操作首先会被记录到edits.inprogress中(查询元数据的操作不会被记录在Edits中,因为查询操作不会更改元数据信息),如果此时NameNode挂掉,重启后会从Edits中读取元数据的信息。然后,NameNode会在内存中执行元数据的增删改的操作。
由于Edits中记录的操作会越来越多,Edits文件会越来越大,导致NameNode在启动加载Edits时会很慢,所以需要对Edits和Fsimage进行合并(所谓合并,就是将Edits和Fsimage加载到内存中,照着Edits中的操作一步步执行,最终形成新的Fsimage)。SecondaryNameNode的作用就是帮助NameNode进行Edits和Fsimage的合并工作。
SecondaryNameNode首先会询问NameNode是否需要CheckPoint(触发CheckPoint需要满足两个条件中的任意一个,定时时间到和Edits中数据写满了)。直接带回NameNode是否检查结果。SecondaryNameNode执行CheckPoint操作,首先会让NameNode滚动Edits并生成一个空的edits.inprogress,滚动Edits的目的是给Edits打个标记,以后所有新的操作都写入edits.inprogress,其他未合并的Edits和Fsimage会拷贝到SecondaryNameNode的本地,然后将拷贝的Edits和Fsimage加载到内存中进行合并,生成fsimage.chkpoint,然后将fsimage.chkpoint拷贝给NameNode,重命名为Fsimage后替换掉原来的Fsimage。NameNode在启动时就只需要加载之前未合并的Edits和Fsimage即可,因为合并过的Edits中的元数据信息已经被记录在Fsimage中。
5.2 fsimage和edits
#删除logs和data目录后初始化
[yut@hadoop101 hadoop-2.7.2]$ bin/hdfs namenode -format
[yut@hadoop101 hadoop-2.7.2]$ ll data/tmp/dfs/name/current/
-rw-rw-r--. 1 yut yut 350 Dec 19 11:12 fsimage_0000000000000000000
-rw-rw-r--. 1 yut yut 62 Dec 19 11:12 fsimage_0000000000000000000.md5
-rw-rw-r--. 1 yut yut 2 Dec 19 11:12 seen_txid
-rw-rw-r--. 1 yut yut 206 Dec 19 11:12 VERSION
[yut@hadoop101 hadoop-2.7.2]$ sbin/start-dfs.sh
[yut@hadoop101 hadoop-2.7.2]$ ll data/tmp/dfs/name/current/
-rw-rw-r--. 1 yut yut 1048576 Dec 19 11:14 edits_inprogress_0000000000000000001
-rw-rw-r--. 1 yut yut 350 Dec 19 11:12 fsimage_0000000000000000000
-rw-rw-r--. 1 yut yut 62 Dec 19 11:12 fsimage_0000000000000000000.md5
-rw-rw-r--. 1 yut yut 2 Dec 19 11:14 seen_txid
-rw-rw-r--. 1 yut yut 206 Dec 19 11:12 VERSION
- fsimage:HDFS文件系统元数据的一个永久性的检查点,其中包含HDFS文件系统的所有目录和文件inode的序列化信息。默认保留最近的二个fsimage。
- edits:存放HDFS文件系统的所有更新操作的路径,文件系统客户端执行的所有写操作首先会被记录到edits文件中。
- seen_txid:保存的是一个数字,即edits_inprogress文件最后的序号
- 每次Namenode启动的时候都会将序号最大的fsimage文件读入内存,将edits滚动生成新的edits_inprogress,并加载滚动前的edits里面的更新操作,保证内存中的元数据信息是最新的、同步的。
5.2.1 fsimage
hdfs oiv -p 文件类型 -i镜像文件 -o 转换后文件输出路径
5.2.2 edits
hdfs oev -p 文件类型 -i编辑日志 -o 转换后文件输出路径
5.3 CheckPoint设置
<!-- secondarynamenode checkpoint存储位置 -->
<property>
<name>dfs.namenode.checkpoint.dir</name>
<value>file://${hadoop.tmp.dir}/dfs/namesecondary</value>
</property>
<!-- secondarynamenode edits存储位置 -->
<property>
<name>dfs.namenode.checkpoint.edits.dir</name>
<value>${dfs.namenode.checkpoint.dir}</value>
</property>
<!-- checkpoint触发满足的间隔时间,默认3600s -->
<property>
<name>dfs.namenode.checkpoint.period</name>
<value>3600</value>
</property>
<!-- checkpoint触发满足的操作次数 -->
<property>
<name>dfs.namenode.checkpoint.txns</name>
<value>1000000</value>
</property>
<!-- 每隔60s检查一次操作次数,与上面配置搭配使用 -->
<property>
<name>dfs.namenode.checkpoint.check.period</name>
<value>60</value>
</property>
<!-- checkpoint失败重试次数 -->
<property>
<name>dfs.namenode.checkpoint.max-retries</name>
<value>3</value>
</property>
<!--namenode和secondarynamenode保留的fsimage数量,edits都将会被保留-->
<property>
<name>dfs.namenode.num.checkpoints.retained</name>
<value>2</value>
</property>
5.4 Namenode故障处理
方法一:将SecondaryNameNode中数据拷贝到NameNode元数据的目录
#kill namenode进程
[yut@hadoop101 hadoop-2.7.2]$ jps
8817 Jps
8483 DataNode
8377 NameNode
[yut@hadoop101 hadoop-2.7.2]$ kill -9 8377
#删除namenode元数据
[yut@hadoop101 hadoop-2.7.2]$ rm -rf /opt/module/hadoop-2.7.2/data/tmp/dfs/name/*
#将secondarynamenode中数据copy到namenode
[yut@hadoop101 hadoop-2.7.2]$ scp -r yut@hadoop103:/opt/module/hadoop-2.7.2/data/tmp/dfs/namesecondary/* /opt/module/hadoop-2.7.2/data/tmp/dfs/name/
#启动namenode
[yut@hadoop101 hadoop-2.7.2]$ sbin/hadoop-daemon.sh start namenode
方法二:使用-importCheckpoint选项启动NameNode守护进程,从而将SecondaryNameNode中数据拷贝到NameNode目录中
#kill namenode进程
[yut@hadoop101 hadoop-2.7.2]$ jps
8817 Jps
8483 DataNode
8377 NameNode
[yut@hadoop101 hadoop-2.7.2]$ kill -9 8377
#删除namenode元数据
[yut@hadoop101 hadoop-2.7.2]$ rm -rf /opt/module/hadoop-2.7.2/data/tmp/dfs/name/*
#将secondarynamenode中namesecondary目录及文件copy过来
[yut@hadoop101 hadoop-2.7.2]$ scp -r yut@hadoop103:/opt/module/hadoop-2.7.2/data/tmp/dfs/namesecondary ./data/tmp/dfs/
[yut@hadoop101 hadoop-2.7.2]$ rm -rf data/tmp/dfs/namesecondary/in_use.lock
[yut@hadoop101 hadoop-2.7.2]$ bin/hdfs namenode -importCheckpoint
[yut@hadoop101 hadoop-2.7.2]$ sbin/hadoop-daemon.sh start namenode
5.5 集群安全模式
5.5.1 安全模式概述
Namenode启动
Namenode启动时,首先将镜像文件(fsimgae)载入内存,并执行编辑日志(edits)中的各项操作。一旦在内存中成功建立文件系统元数据的映像,则创建一个新的fsimage文件和一个空的编辑日志。此时,Namenode开始监听Datanode请求。这个过程期间,Namenode一直运行在安全模式,即Namenode的文件系统对于客户端来说是只读的。
DataNode启动
系统中的数据块的位置并不是由Namenode维护的,而是以块列表的形式存储在Datanode中。在系统的正常操作期间,Namenode会在内存中保留所有块位置的映射信息。在安全模式下,各个Datanode会向Namenode发送最新的块列表信息,Namenode了解到足够多的块位置信息之后,即可高效运行文件系统。
安全模式退出判断
如果满足“最小副本条件”,Namenode会在30s之后就退出安全模式。所谓的最小副本条件指的是在整个文件系统中99.9%的块满足最小副本级别(默认值:dfs.replication.min=1)。在启动一个刚刚格式化的HDFS集群时,因为系统中还没有任何块,所以Namenode不会进入安全模式。
DN上报的块的最小副本数总和 / 块的总数 > 0.999,自动在30s离开安全模式!
5.5.2 安全模式模拟
bin/hdfs dfsadmin -safemode get
(查看安全模式状态)bin/hdfs dfsadmin -safemode enter
(进入安全模式)bin/hdfs dfsadmin -safemode leave
(离开安全模式)bin/hdfs dfsadmin -safemode wait
(等待安全模式结束)
案例:
[yut@hadoop101 hadoop-2.7.2]$ bin/hdfs dfsadmin -safemode get #查看是否处于安全模式
Safe mode is OFF
[yut@hadoop101 hadoop-2.7.2]$ bin/hdfs dfsadmin -safemode enter #开启安全模式
Safe mode is ON
[yut@hadoop101 hadoop-2.7.2]$ vim safemode.sh #编辑脚本
#!/bin/bash
#等待安全模式结束
hdfs dfsadmin -safemode wait
#上传文件
hdfs dfs -put /opt/module/hadoop-2.7.2/README.txt /
[yut@hadoop101 hadoop-2.7.2]$ sh safemode.sh #执行脚本
[yut@hadoop101 hadoop-2.7.2]$ bin/hdfs dfsadmin -safemode leave #开启另一个窗口执行离开安全模式
5.6 Namenode多目录配置
Namenode的本地目录可以配置成多个,且每个目录存放内容相同,增加了可靠性。
<property>
<name>dfs.namenode.name.dir</name>
<value>file:///${hadoop.tmp.dir}/dfs/name1,file:///${hadoop.tmp.dir}/dfs/name2</value>
</property>
配置完成需要重新格式化Namenode重启集群。
5.7 总结
-
NN的作用:
①NN保存HDFS上所有文件的元数据 ②NN负责接受客户端的请求 ③NN负责接受DN上报的信息, ④和DN保持心跳,给DN分配任务(维护副本数)
-
元数据的存储
元数据存储在fsiamge文件+edits文件中! fsimage(元数据的快照文件) edits(记录所有写操作的文件) NN负责集群中所有客户端的请求和所有DN的请求。在一个集群中,通常NN需要一个高配置,保证NN 可以及时处理客户端或DN的请求,一旦NN无法及时处理请求,HDFS就已经瘫痪!
-
存储的元数据分为两种
①inodes : 记录文件的属性和文件由哪些块组成,记录到edits和fsimage文件中 ②块的位置映射信息: 由NN启动后,接收DN的上报,动态生成!
-
NN的启动过程
①先加载fsimage_000000xx文件 ②将fsimage文件xx之后的edits文件加载 ③合并生成最新的元数据,记录checkpoint,如果满足要求,执行saveNamespace操作,不满足等满足后执行saveNamespace操作必须在安全模式执行 ④自动进入安全模式,等待DN上报块,DN上报的块的最小副本数总和 / 块的总数 > 0.999,自动在30s离开安全模式!安全模式只能有限读,不能写!
6 Datanode
6.1 工作机制
- 一个数据块在Datanode上以文件形式存储在磁盘上,包括两个文件(blk_xxx,blk_xxx_xxx.meta),一个是数据本身,一个是元数据包括数据块的长度、块数据的校验和和时间戳。
- Datanode启动后向Namenode注册,通过后,周期性(1小时)的向NameNode上报所有的块信息。
- 心跳是每3秒一次,心跳返回结果带有Namenode给该Datanode的命令如复制块数据到另一台机器,或删除某个数据块。如果超过10分钟没有收到某个Datanode的心跳,则认为该节点不可用。
- 集群运行中可以安全加入和退出一些机器。
CheckSum(校验和)的目的在于验证文件的完整性,Datanode在其文件创建后周期验证CheckSum,当Datanode读取Block的时候,它会计算CheckSum,如果计算后的CheckSum,与Block创建时值不一样,说明Block已经损坏,Client会读取其它DataNode上的Block。
心跳和过期datanode时间设置通过以下二个属性:
<!-- 检查过期datanode节点的时间间隔,默认300000ms -->
<property>
<name>dfs.namenode.heartbeat.recheck-interval</name>
<value>300000</value>
</property>
<!-- 心跳间隔时间,默认3s -->
<property>
<name>dfs.heartbeat.interval</name>
<value>3</value>
</property>
datanode的超时时间(默认10min+30s)计算公式如下:
Timeout = 2 *
dfs.namenode.heartbeat.recheck-interval
+ 10 *dfs.heartbeat.interval
6.2 节点服役与退役
6.2.1 节点服役
克隆一台新机器,所有配置通过xsync.sh同步,直接启动datanode即可。
[yut@hadoop104 hadoop-2.7.2]$ sbin/hadoop-daemon.sh start datanode
[yut@hadoop104 hadoop-2.7.2]$ sbin/yarn-daemon.sh start nodemanager
#数据不均衡,可以执行以下命令实现集群的再平衡
[yut@hadoop104 hadoop-2.7.2]$ sh sbin/start-balancer.sh
6.2.2 节点退役
退役节点有添加白名单和黑名单二种方式。
[yut@hadoop101 hadoop-2.7.2]$ vim etc/hadoop/dfs.hosts #设置白名单
hadoop101
hadoop102
hadoop103
[yut@hadoop101 hadoop-2.7.2]$ vim etc/hadoop/dfs.hosts.exclude #设置黑名单
hadoop104
[yut@hadoop101 hadoop-2.7.2]$ vim hdfs-site.xml #配置白名单和黑名单路径
<property>
<name>dfs.hosts</name>
<value>/opt/module/hadoop-2.7.2/etc/hadoop/dfs.hosts</value>
</property>
<property>
<name>dfs.hosts.exclude</name>
<value>/opt/module/hadoop-2.7.2/etc/hadoop/dfs.hosts.exclude</value>
</property>
[yut@hadoop101 hadoop-2.7.2]$ xsync etc/hadoop/hdfs-site.xml #同步配置文件
[yut@hadoop101 hadoop-2.7.2]$ bin/hdfs dfsadmin -refreshNodes #刷新datanode
[yut@hadoop101 hadoop-2.7.2]$ bin/yarn rmadmin -refreshNodes #刷新nodemanager
#数据不均衡,可以执行以下命令实现集群的再平衡
[yut@hadoop101 hadoop-2.7.2]$ sh sbin/start-balancer.sh
注意:
- 通过浏览器查看节点退役状态为
decommission in progress
,代表数据节点正在复制块到其它节点,等待退役节点状态为decommissioned
,再停止该节点及节点资源管理器。 - 如果服务节点数量小于等于副本数是不能退役成功的,需要修改副本数后才能退役。
- 不允许白名单和黑名单中同时出现同一个主机名称。
6.3 Datanode多目录配置
DataNode也可以配置成多个目录,每个目录存储的数据不一样。即:数据不是副本。
<property>
<name>dfs.datanode.data.dir</name>
<value>file:///${hadoop.tmp.dir}/dfs/data1,file:///${hadoop.tmp.dir}/dfs/data2</value>
</property>
7 HDFS 2.x新特性
7.1 集群间数据拷贝
[yut@hadoop102 hadoop-2.7.2]$ bin/hadoop
Usage: hadoop [--config confdir] [COMMAND | CLASSNAME]
CLASSNAME run the class named CLASSNAME
or
where COMMAND is one of:
fs run a generic filesystem user client
version print the version
jar <jar> run a jar file
note: please use "yarn jar" to launch
YARN applications, not this command.
checknative [-a|-h] check native hadoop and compression libraries availability
distcp <srcurl> <desturl> copy file or directories recursively
archive -archiveName NAME -p <parent path> <src>* <dest> create a hadoop archive
classpath prints the class path needed to get the
credential interact with credential providers
Hadoop jar and the required libraries
daemonlog get/set the log level for each daemon
trace view and modify Hadoop tracing settings
Most commands print help when invoked w/o parameters.
两个Hadoop集群之间的递归数据复制
[yut@hadoop102 hadoop-2.7.2]$ bin/hadoop distcp hdfs://haoop101:9000/user/yut/hello.txt hdfs://hadoop103:9000/user/yut/hello.txt
7.2 小文件存档
HDFS存储小文件弊端:
每个文件均按块存储,每个块的元数据存储在Namenode的内存中,因此HDFS存储小文件会非常低效。因为大量的小文件会耗尽Namenode的大部分内存。但注意,存储小文件所需的磁盘容量和数据块的大小无关。
解决方式一:
HDFS存档文件或HAR文件,是一个更高效的文件存档工具,它将文件存入HDFS块,在减少Namenode内存使用的同时,允许对文件进行透明的访问。具体来说,HDFS存档文件对内还是一个个独立文件,对Namenode而言却是一个整体减少了Namenode的内存。
#需要启动yarn
[yut@hadoop102 hadoop-2.7.2]$ sbin/start-yarn.sh
#可以预先在/user/yut/input目录下上传多个小文件,将该目录归档成input.har
[yut@hadoop102 hadoop-2.7.2]$ bin/hadoop archive -archiveName input.har –p /user/yut/input /user/yut/output
#查看归档文件归档的小文件
[yut@hadoop102 hadoop-2.7.2]$ hadoop fs -lsr har:///user/yut/output/input.har
#解归档文件
[yut@hadoop102 hadoop-2.7.2]$ hadoop fs -cp har:///user/yut/output/input.har/* /user/yut
7.3 回收站
开启回收站功能,可以将删除的文件在不超时的情况下,恢复原数据,起到防止误删除、备份等作用。
在core-site.xml
中添加以下配置:
<!-- 文件的存活时间(分钟),0表示禁用回收站 -->
<property>
<name>fs.trash.interval</name>
<value>1</value>
</property>
<!-- 检查回收站的间隔时间(分钟),如果是0则值默认和fs.trash.interval相同 -->
<property>
<name>fs.trash.checkpoint.interval</name>
<value>0</value>
</property>
<!-- 进入垃圾回收站用户名称,访问WEB页面用户 -->
<property>
<name>hadoop.http.staticuser.user</name>
<value>yut</value>
</property>
回收站在集群中的路径:/user/yut/.Trash/….
注意:
fs.trash.checkpoint.interval
<=fs.trash.interval
。- 通过程序删除的文件不会经过回收站,需要调用Trash对象的moveToTrash()方法才进入回收站。
- 过期删除的文件会加上时间戳重新命名。
#删除/client/input目录
[yut@hadoop101 hadoop-2.7.2]$ hadoop fs -rm -R /cient/input
#查看回收站
[yut@hadoop101 hadoop-2.7.2]$ hadoop fs -ls -R /user/yut/.Trash
#恢复删除的目录
[yut@hadoop101 hadoop-2.7.2]$ hadoop fs -mv /user/yut/.Trash/Current/client/input /client/input
#清空回收站
[yut@hadoop101 hadoop-2.7.2]$ hadoop fs -expunge
7.4 快照管理
快照相当于对目录做一个备份,并不会立即复制所有文件,而是记录文件变化。
hdfs dfsadmin -allowSnapshot PATH
:开启指定目录的快照功能hdfs dfsadmin -disallowSnapshot PATH
:禁用指定目录的快照功能,默认是禁用hdfs dfs -createSnapshot PATH
:对目录创建快照hdfs dfs -createSnapshot PATH SNAPSHOTNAME
:指定快照名称创建快照hdfs dfs -renameSnapshot PATH old_SNAPSHOTNAME new_SNAPSHOTNAME
:重命名快照hdfs lsSnapshottableDir
:列出当前用户所有可快照目录hdfs snapshotDiff PATH1 PATH2
:比较二个快照目录的不同之处hdfs dfs -deleteSnapshot <PATH> <SNAPSHOTNAME>
:删除快照
案例:
[yut@hadoop101 hadoop-2.7.2]$ hdfs dfsadmin -allowSnapshot /client/input #开启快照
[yut@hadoop101 hadoop-2.7.2]$ hdfs lsSnapshottableDir #查看开启快照的目录
[yut@hadoop101 hadoop-2.7.2]$ hdfs dfs -createSnapshot /client/input #创建快照
Created snapshot /client/input/.snapshot/s20191221-143452.083
[yut@hadoop101 hadoop-2.7.2]$ hdfs dfs -ls -R /client/input/.snapshot/ #快照目录,该目录不显示在HDFS的web页面
[yut@hadoop101 hadoop-2.7.2]$ hdfs dfs -createSnapshot /client/input input-snapshot #指定快照名称创建快照
Created snapshot /client/input/.snapshot/input-snapshot
[yut@hadoop101 hadoop-2.7.2]$ hdfs snapshotDiff /client/input/ . .snapshot/input-snapshot #比较当前目录与input-snapshot快照的不同
[yut@hadoop101 hadoop-2.7.2]$ hdfs snapshotDiff /client/input/ .snapshot/s20191221-143452.083 .snapshot/input-snapshot #比较二个快照之间的不同
[yut@hadoop101 hadoop-2.7.2]$ hdfs dfs -cp /client/input/.snapshot/input-snapshot /user #恢复快照