第 1 节 HDFS 简介

HDFS (全称:Hadoop Distribute File System,Hadoop 分布式文件系统)是 Hadoop 核心组成,是分布式存储服务。
分布式⽂件系统横跨多台计算机,在⼤数据时代有着广泛的应⽤前景,它们为存储和处理超⼤规模数据提供所需的扩展能力。
HDFS是分布式文件系统中的⼀种。

第 2 节 HDFS的重要概念

HDFS 通过统⼀的命名空间目录树来定位文件; 另外,它是分布式的,由很多服务器联合起来实现其功能,集群中的服务器有各⾃的⻆色(分布式本质是拆分,各司其职);

  • 典型的 Master/Slave 架构

HDFS 的架构是典型的 Master/Slave 结构。
HDFS集群往往是⼀个NameNode(HA架构会有两个NameNode,联邦机制)+多个DataNode组成;
NameNode是集群的主节点,DataNode是集群的从节点。

  • 分块存储(block机制)

HDFS 中的⽂件在物理理上是分块存储(block)的,块的⼤小可以通过配置参数来规定; Hadoop2.x版本中默认的block⼤小是128M;

  • 命名空间(NameSpace)

HDFS ⽀持传统的层次型⽂件组织结构。⽤户或者应⽤程序可以创建目录,然后将⽂件保存在这些目录里。⽂件系统名字空间的层次结构和⼤多数现有的⽂件系统类似:⽤户可以创建、删除、移动或重命名文件。
Namenode 负责维护文件系统的名字空间,任何对文件系统名字空间或属性的修改都将被 Namenode 记录下来。
HDFS提供给客户一个抽象目录树,访问形式:hdfs://namenode的hostname:port/test/input,hdfs://linux121:9000/test/input

  • NameNode元数据管理

我们把目录结构及文件分块位置信息叫做元数据。
NameNode的元数据记录每一个文件所对应的block信息(block的id,以及所在的DataNode节点的信息)

  • DataNode数据存储

文件的各个 block 的具体存储管理由 DataNode 节点承担。一个block会有多个DataNode来存储,DataNode会定时向NameNode来汇报自己持有的block信息。

  • 副本机制

为了容错,文件的所有 block 都会有副本。每个⽂件的 block ⼤小和副本系数都是可配置的。应⽤程序可以指定某个文件的副本数⽬。副本系数可以在⽂件创建的时候指定,也可以在之后改变。 副本数量默认是3个。

  • ⼀次写⼊,多次读出

HDFS是设计成适应一次写入,多次读出的场景,且不支持⽂件的随机修改。 (⽀持追加写入,不只⽀持随机更新)
正因为如此,HDFS 适合⽤来做大数据分析的底层存储服务,并不适合用来做⽹盘等应用(修改不⽅便,延迟大,⽹络开销⼤,成本太高)

第 3 节 HDFS 架构

image.png

  • NameNode(nn):Hdfs集群的管理者,Master
    • 维护管理Hdfs的名称空间(NameSpace)
    • 维护副本策略
    • 记录⽂件块(Block)的映射信息
    • 负责处理客户端读写请求
  • DataNode:NameNode下达命令,DataNode执行实际操作,Slave节点。
    • 保存实际的数据块
    • 负责数据块的读写
  • Client:客户端
    • 上传⽂文件到HDFS的时候,Client负责将⽂件切分成Block,然后进行上传
    • 请求NameNode交互,获取文件的位置信息
    • 读取或写⼊文件,与DataNode交互
    • Client可以使⽤一些命令来管理HDFS或者访问HDFS

image.png

第 4 节 HDFS 客户端操作

4.1 Shell 命令⾏操作HDFS

  1. 基本语法
    1. bin/hadoop fs 具体命令 OR bin/hdfs dfs 具体命令
  2. 命令⼤全 ```bash

[root@hadoop01 hadoop-2.9.2]# bin/hdfs dfs Usage: hadoop fs [generic options] [-appendToFile ] [-cat [-ignoreCrc] …] [-checksum …] [-chgrp [-R] GROUP PATH…] [-chmod [-R] PATH…]

[-chown [-R] [OWNER][:[GROUP]] PATH…] [-copyFromLocal [-f] [-p] [-l] [-d] ] [-copyToLocal [-f] [-p] [-ignoreCrc] [-crc] ] [-count [-q] [-h] [-v] [-t []] [-u] [-x] …] [-cp [-f] [-p | -p[topax]] [-d] ] [-createSnapshot []] [-deleteSnapshot ] [-df [-h] [ …]] [-du [-s] [-h] [-x] …] [-expunge] [-find …] [-get [-f] [-p] [-ignoreCrc] [-crc] ] [-getfacl [-R] ] [-getfattr [-R] {-n name | -d} [-e en] ] [-getmerge [-nl] [-skip-empty-file] ] [-help [cmd …]] [-ls [-C] [-d] [-h] [-q] [-R] [-t] [-S] [-r] [-u] [ …]] [-mkdir [-p] …] [-moveFromLocal ] [-moveToLocal ] [-mv ] [-put [-f] [-p] [-l] [-d] ] [-renameSnapshot ] [-rm [-f] [-r|-R] [-skipTrash] [-safely] …] [-rmdir [—ignore-fail-on-non-empty]

…] [-setfacl [-R] [{-b|-k} {-m|-x } ]|[—set

]] [-setfattr {-n name [-v value] | -x name} ] [-setrep [-R] [-w] …] [-stat [format] …] [-tail [-f] ] [-test -[defsz] ] [-text [-ignoreCrc] …] [-touchz …] [-truncate [-w] …] [-usage [cmd …]]

Generic options supported are: -conf specify an application configuration file -D define a value for a given property -fs file:///|hdfs://namenode:port specify default filesystem URL to use, overrides ‘fs.defaultFS’ property from configurations. -jt specify a ResourceManager -files specify a comma-separated list of files to be copied to the map reduce cluster -libjars specify a comma-separated list of jar files to be included in the classpath -archives specify a comma-separated list of archives to be unarchived on the compute machines

  1. 3.HDFS命令演示
  2. - 启动Hadoop集群(⽅便后续的测试)
  3. ```bash
  4. [root@hadoop01 hadoop-2.9.2]$ sbin/start-dfs.sh
  5. [root@hadoop02 hadoop-2.9.2]$ sbin/start-yarn.sh
  • -help:输出这个命令参数

    [root@hadoop01 hadoop-2.9.2]$ hadoop fs -help rm
    
  • -ls: 显示⽬录信息

    [root@hadoop01 hadoop-2.9.2]$ hadoop fs -ls /
    
  • -mkdir:在HDFS上创建目录

    [root@hadoop01 hadoop-2.9.2]$ hadoop fs -mkdir -p /bigdata
    
  • -moveFromLocal:从本地剪切粘贴到HDFS ```bash

[root@hadoop01 hadoop-2.9.2]$ touch hadoop.txt [root@hadoop01 hadoop-2.9.2]$ hadoop fs -moveFromLocal ./hadoop.txt /bigdata


- -appendToFile:追加⼀个文件到已经存在的⽂件末尾
```bash
[root@hadoop01 hadoop-2.9.2]$ touch hdfs.txt
[root@hadoop01 hadoop-2.9.2]$ vi hdfs.txt

输入
namenode datanode block replication
[root@hadoop01 hadoop-2.9.2]$ hadoop fs -appendToFile hdfs.txt /bigdata/hadoop.txt
  • -cat:显示文件内容

    [root@hadoop01 hadoop-2.9.2]$ hadoop fs -cat /bigdata/hadoop.txt
    
  • -chgrp 、-chmod、-chown:Linux⽂件系统中的⽤法一样,修改文件所属权限 ```bash

[root@hadoop01 hadoop-2.9.2]$ hadoop fs -chmod 666 /bigdata/hadoop.txt [root@hadoop01 hadoop-2.9.2]$ hadoop fs -chown root:root /bigdata/hadoop.txt


- -copyFromLocal:从本地⽂件系统中拷⻉文件到HDFS路径去
```bash
[root@hadoop01 hadoop-2.9.2]$ hadoop fs -copyFromLocal README.txt /
  • -copyToLocal:从HDFS拷贝到本地

    [root@hadoop01 hadoop-2.9.2]$ hadoop fs -copyToLocal /bigdata/hadoop.txt ./
    
  • -cp :从HDFS的⼀个路径拷⻉到HDFS的另一个路径

    [root@hadoop01 hadoop-2.9.2]$ hadoop fs -cp /bigdata/hadoop.txt /hdfs.txt
    
  • -mv:在HDFS⽬录中移动文件

    [root@hadoop01 hadoop-2.9.2]$ hadoop fs -mv /hdfs.txt /bigdata/
    
  • -get:等同于copyToLocal,就是从HDFS下载文件到本地

    [root@hadoop01 hadoop-2.9.2]$ hadoop fs -get /bigdata/hadoop.txt ./
    
  • -put:等同于copyFromLocal

    [root@hadoop01 hadoop-2.9.2]$ hadoop fs -mkdir -p /user/root/test/ #本地⽂文件系统创建yarn.txt
    [root@hadoop01 hadoop-2.9.2]$ vim yarn.txt
    resourcemanager nodemanager
    [root@hadoop01 hadoop-2.9.2]$ hadoop fs -put ./yarn.txt /user/root/test/
    
  • -tail:显示⼀个⽂件的末尾

    [root@hadoop01 hadoop-2.9.2]$ hadoop fs -tail /user/root/test/yarn.txt
    
  • -rm:删除⽂件或⽂件夹

    [root@hadoop01 hadoop-2.9.2]$ hadoop fs -rm /user/root/test/yarn.txt
    
  • -rmdir:删除空⽬录

    [root@hadoop01 hadoop-2.9.2]$ hadoop fs -mkdir /test
    [root@hadoop01 hadoop-2.9.2]$ hadoop fs -rmdir /test
    
  • -du统计⽂件夹的⼤小信息

    [root@hadoop01 hadoop-2.9.2]$ hadoop fs -du -s -h /user/root/test
    [root@hadoop01 hadoop-2.9.2]$ hadoop fs -du  -h /user/root/test
    
  • -setrep:设置HDFS中⽂件的副本数量

    [root@hadoop01 hadoop-2.9.2]$ hadoop fs -setrep 10 /bigdata/hadoop.txt
    

    HDFS副本数量
    这里设置的副本数只是记录在NameNode的元数据中,是否真的会有这么多副本,还得看DataNode的数量。因为⽬前只有3台设备,最多也就3个副本,只有节点数的增加到10台时,副本数才能达到10。
    image.png

    4.2 JAVA客户端

    4.2.1 客户端环境准备

  1. 将Hadoop-2.9.2安装包解压到非中⽂路径(例如:E:\hadoop-2.9.2)。
    2. 配置HADOOP_HOME环境变量
    image.png
    3. 配置Path环境变量。
    image.png
    4. 创建⼀个Maven工程ClientDemo
    5. 导入相应的依赖坐标+日志配置文件
    <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>2.9.2</version>
     </dependency>
     <!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-client -->
     <dependency>
         <groupId>org.apache.hadoop</groupId>
         <artifactId>hadoop-client</artifactId>
         <version>2.9.2</version>
     </dependency>
     <!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-hdfs -->
     <dependency>
         <groupId>org.apache.hadoop</groupId>
         <artifactId>hadoop-hdfs</artifactId>
         <version>2.9.2</version>
     </dependency>
    </dependencies>
    
    为了便于控制程序运行打印的⽇志数量,需要在项⽬的src/main/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
    
  2. 创建包名:com.yue.hdfs
    7. 创建HdfsClient类 ```java

public class HdfsClient{ @Test public void testMkdirs() throws IOException, InterruptedException, URISyntaxException { // 1 获取⽂件系统 Configuration configuration = new Configuration(); // 配置在集群上运行 // configuration.set(“fs.defaultFS”, “hdfs://hadoop01:9000”); // FileSystem fs = FileSystem.get(configuration); FileSystem fs = FileSystem.get(new URI(“hdfs://hadoop01:9000”), configuration, “root”); // 2 创建目录 fs.mkdirs(new Path(“/test”)); // 3 关闭资源 fs.close(); } }

遇到问题:<br />如果不指定操作HDFS集群的⽤户信息,默认是获取当前操作系统的⽤户信息,出现权限被拒绝的问题,报错如下:<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1597423/1606534406513-3574a082-6ba6-4ab1-bd8c-244843c4ba89.png#align=left&display=inline&height=109&margin=%5Bobject%20Object%5D&name=image.png&originHeight=218&originWidth=1326&size=150995&status=done&style=none&width=663)
<a name="W4CVx"></a>
#### 4.2.2 HDFS的API操作
**1 上传⽂件**
```java
@Test
public void testCopyFromLocalFile() throws IOException, InterruptedException, URISyntaxException {
    // 1 获取⽂文件系统
    Configuration configuration = new Configuration();
    configuration.set("dfs.replication", "2");
    FileSystem fs = FileSystem.get(new URI("hdfs://hadoop01:9000"),
    configuration, "root");
    // 2 上传⽂文件
    fs.copyFromLocalFile(new Path("e:/la.txt"), new Path("/la.txt"));
    // 3 关闭资源
    fs.close();
    System.out.println("end");
}
  • 将hdfs-site.xml拷⻉到项目的根⽬录下

    <?xml version="1.0" encoding="UTF-8"?>
    <?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
    <configuration>
    <property>
      <name>dfs.replication</name>
          <value>1</value>
    </property>
    </configuration>
    
  • 参数优先级

参数优先级排序:(1)代码中设置的值 >(2)⽤户⾃定义配置文件 >(3)服务器的默认配置
2 下载⽂件

@Test
public void testCopyToLocalFile() throws IOException, InterruptedException, URISyntaxException{
    // 1 获取文件系统
    Configuration configuration = new Configuration();
    FileSystem fs = FileSystem.get(new URI("hdfs://hadoop01:9000"),
    configuration, "root");
    // 2 执⾏下载操作
    // boolean delSrc 指是否将原⽂件删除
    // Path src 指要下载的⽂件路径
    // Path dst 指将文件下载到的路径
    // boolean useRawLocalFileSystem 是否开启⽂件校验 
    fs.copyToLocalFile(false, new Path("/la.txt"), new Path("e:/la_copy.txt"), true);
    // 3 关闭资源
    fs.close();
}

3 删除⽂件/文件夹

@Test
public void testDelete() throws IOException, InterruptedException, URISyntaxException{
    // 1 获取文件系统
    Configuration configuration = new Configuration();
    FileSystem fs = FileSystem.get(new URI("hdfs://hadoop01:9000"), configuration, "root");
    // 2 执行删除
    fs.delete(new Path("/api_test/"), true);
    // 3 关闭资源
      fs.close();
}

4 查看⽂件名称、权限、⻓度、块信息

@Test
public void testListFiles() throws IOException, InterruptedException, URISyntaxException{
    // 1获取⽂件系统
    Configuration configuration = new Configuration();
    FileSystem fs = FileSystem.get(new URI("hdfs://hadoop01:9000"), configuration, "root");
    // 2 获取文件详情
    RemoteIterator<LocatedFileStatus> listFiles = fs.listFiles(new Path("/"), 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 (BlockLocation blockLocation : blockLocations) {
            // 获取块存储的主机节点
            String[] hosts = blockLocation.getHosts();
            for (String host : hosts) {
                System.out.println(host);
            }
        }
        System.out.println("-----------华丽的分割线----------");
    }
    // 3 关闭资源
    fs.close();
}

5 文件夹判断

@Test
public void testListStatus() throws IOException, InterruptedException, URISyntaxException{
    // 1 获取文件配置信息
    Configuration configuration = new Configuration();
    FileSystem fs = FileSystem.get(new URI("hdfs://hadoop01:9000"), configuration, "root");
    // 2 判断是文件还是文件夹
    FileStatus[] listStatus = fs.listStatus(new Path("/"));
    for (FileStatus fileStatus : listStatus) {
        // 如果是文件
        if (fileStatus.isFile()) {
            System.out.println("f:"+fileStatus.getPath().getName());
        }else {
            System.out.println("d:"+fileStatus.getPath().getName());
        }
    }
    // 3 关闭资源
    fs.close();
}

6 I/O流操作HDFS
以上我们使用的API操作都是HDFS系统框架封装好的。我们⾃己也可以采⽤IO流的方式实现⽂件的上传和下载。
6.1 ⽂件上传
1. 需求:把本地e盘上的la.txt⽂文件上传到HDFS根⽬录
2. 编写代码

@Test
public void putFileToHDFS() throws IOException, InterruptedException, URISyntaxException {
    // 1 获取文件系统
    Configuration configuration = new Configuration();
    FileSystem fs = FileSystem.get(new URI("hdfs://hadoop01:9000"), configuration, "root");
    // 2 创建输入流
    FileInputStream fis = new FileInputStream(new File("e:/la.txt"));
    // 3 获取输出流
    FSDataOutputStream fos = fs.create(new Path("/la_io.txt"));
    // 4 流对拷
    IOUtils.copyBytes(fis, fos, configuration);
    // 5 关闭资源
    IOUtils.closeStream(fos);
    IOUtils.closeStream(fis);
    fs.close();
}

6.2 ⽂件下载
1. 需求:从HDFS上下载la.txt⽂件到本地e盘上
2. 编写代码

@Test
public void getFileFromHDFS() throws IOException, InterruptedException, URISyntaxException{
    // 1 获取文件系统
    Configuration configuration = new Configuration();
    FileSystem fs = FileSystem.get(new URI("hdfs://hadoop01:9000"), configuration, "root");
    // 2 获取输⼊流
      FSDataInputStream fis = fs.open(new Path("/la_io.txt"));
    // 3 获取输出流
    FileOutputStream fos = new FileOutputStream(new
    File("e:/la_io_copy.txt"));
    // 4 流的对拷
    IOUtils.copyBytes(fis, fos, configuration);
    // 5 关闭资源
    IOUtils.closeStream(fos);
    IOUtils.closeStream(fis);
    fs.close();
}

6.3 seek 定位读取
1. 需求:将HDFS上的la.txt的内容在控制台输出两次
2. 编写代码

@Test
public void readFileSeek2() throws IOException, InterruptedException, URISyntaxException{
    // 1 获取⽂件系统
    Configuration configuration = new Configuration();
    FileSystem fs = FileSystem.get(new URI("hdfs://hadoop01:9000"), configuration, "root");
    // 2 打开输⼊入流,读取数据输出到控制台
    FSDataInputStream in = null;
    try{
        in= fs.open(new Path("/la.txt"));
        IOUtils.copyBytes(in, System.out, 4096, false);
        in.seek(0);
        //从头再次读取
        IOUtils.copyBytes(in, System.out, 4096, false);
    }finally {
        IOUtils.closeStream(in);
    }
}

注意

  • windows解压安装Hadoop后,在调用相关API操作HDFS集群时可能会报错,这是由于Hadoop安装缺少windows操作系统相关⽂件所致,如下图:

image.png
解决方案: 拷贝winutils.exe放到windows系统Hadoop安装目录的bin目录下即可!!

  • HDFS⽂件系统权限问题

hdfs的⽂件权限机制与linux系统的文件权限机制类似!!
r:read w:write x:execute 权限x对于文件表示忽略,对于文件夹表示是否有权限访问其内容
如果linux系统⽤户zhangsan使用hadoop命令创建⼀个⽂件,那么这个文件在HDFS当中的owner就是zhangsan
HDFS⽂件权限的目的,防⽌好⼈做错事,⽽不是阻⽌坏人做坏事。HDFS相信你告诉我你是谁,你就是谁!!
解决⽅案

  • 指定⽤户信息获取FileSystem对象
  • 关闭HDFS集群权限校验

    vim hdfs-site.xml
    #添加如下属性
    <property>
    <name>dfs.permissions.enabled</name>
    <value>false</value>
    </property>
    

    修改完成之后要分发到其它节点,同时要重启HDFS集群

  • 基于HDFS权限本身⽐较鸡肋的特点,我们可以彻底放弃HDFS的权限校验,如果⽣产环境中我们可以考虑借助kerberos以及sentry等安全框架来管理理⼤数据集群安全。所以我们直接修改HDFS的根⽬录权限为777

    hadoop fs -chmod -R 777 /
    

    第 5 节 HDFS读写解析

    5.1 HDFS读数据流程

    image.png
    1. 客户端通过Distributed FileSystem向NameNode请求下载文件,NameNode通过查询元数据,找到文件块所在的DataNode地址。
    2. 挑选⼀台DataNode(就近原则,然后随机)服务器,请求读取数据。
    3. DataNode开始传输数据给客户端(从磁盘里面读取数据输入流,以Packet为单位来做校验)。
    4. 客户端以Packet为单位接收,先在本地缓存,然后写⼊目标文件。

    5.2 HDFS写数据流程

    image.png

  1. 客户端通过Distributed FileSystem模块向NameNode请求上传文件,NameNode检查⽬标⽂件是否已存在,⽗目录是否存在。
  2. NameNode返回是否可以上传。
  3. 客户端请求第⼀个 Block上传到哪几个DataNode服务器上。
  4. NameNode返回3个DataNode节点,分别为dn1、dn2、dn3。
  5. 客户端通过FSDataOutputStream模块请求dn1上传数据,dn1收到请求会继续调用dn2,然后dn2调⽤dn3,将这个通信管道建立完成。
  6. dn1、dn2、dn3逐级应答客户端。
  7. 客户端开始往dn1上传第一个Block(先从磁盘读取数据放到一个本地内存缓存),以Packet为单位,dn1收到一个Packet就会传给dn2,dn2传给dn3;dn1每传一个packet会放⼊一个确认队列等待确认。
  8. 当⼀个Block传输完成之后,客户端再次请求NameNode上传第⼆个Block的服务器。(重复执行3-7步)。

验证Packet代码

@Test
public void testUploadPacket() throws IOException {
    //1 准备读取本地文件的输入流
    final FileInputStream in = new FileInputStream(new File("e:/la.txt"));
    //2 准备好写出数据到hdfs的输出流
    final FSDataOutputStream out = fs.create(new Path("/la.txt"), new Progressable() {
        public void progress() {
            //这个progress⽅法就是每传输64KB(packet)就会执行一次
            System.out.println("&");
        }
    });
    //3 实现流拷贝
    IOUtils.copyBytes(in, out, configuration);
    //默认关闭流选项是true,所以会⾃动关闭
    //4 关流 可以再次关闭也可以不关了
}

第 6 节 NN与2NN

6.1 HDFS元数据管理机制

问题1:NameNode如何管理和存储元数据?
计算机中存储数据两种:内存或者是磁盘
元数据存储磁盘:存储磁盘⽆法⾯对客户端对元数据信息的任意的快速低延迟的响应,但是安全性⾼
元数据存储内存:元数据存放内存,可以高效的查询以及快速响应客户端的查询请求,数据保存在内存,如果断点,内存中的数据全部丢失。
解决⽅案:内存+磁盘;NameNode内存+FsImage的⽂件(磁盘)
新问题:磁盘和内存中元数据如何划分?
两个数据一模⼀样,还是两个数据合并到一起才是⼀份完整的数据呢?
⼀模⼀样:client如果对元数据进行增删改操作,需要保证两个数据的一致性。FsImage文件操作起来效率也不高。
两个合并=完整数据:NameNode引⼊了一个edits⽂件(⽇志⽂件:只能追加写入)edits文件记录的是client的增删改操作,
不再选择让NameNode把数据dump出来形成FsImage文件(这种操作是⽐较消耗资源)。
元数据管理流程图
image.png

  • 第一阶段: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。

      6.2 Fsimage与Edits⽂件解析

      NameNode在执行格式化之后,会在/op/servers/hadoop-2.9.2/data/tmp/dfs/name/current⽬录下产⽣如下文件
      image.png
  • Fsimage⽂件:是namenode中关于元数据的镜像,一般称为检查点,这里包含了HDFS⽂件系统所有⽬录以及⽂件相关信息(Block数量,副本数量,权限等信息)

  • Edits文件 :存储了客户端对HDFS文件系统所有的更新操作记录,Client对HDFS⽂件系统所有的更新操作都会被记录到Edits⽂件中(不包括查询操作)
  • seen_txid:该⽂件是保存了一个数字,数字对应着最后一个Edits⽂件名的数字
  • VERSION:该⽂件记录namenode的一些版本号信息,比如:CusterId,namespaceID等

NameNode启动时会将Fsimage⽂件加载到内存中,同时也把之前未合并元数据的Edits⽂件加载,集合两个文件中的元数据这样保证了NameNode中的元数据是最新最全的。通俗点说就是NameNode启动时把Fsimage和Edits⽂件进⾏了合并。

6.2.1 Fsimage⽂件内容

官⽅地址 https://hadoop.apache.org/docs/r2.9.2/hadoop-project-dist/hadoop-hdfs/HdfsImageViewer.html
1. 查看oiv和oev命令
[root@hadoop01 current]$ hdfs
oiv Offline Image Viewer View a Hadoop fsimage INPUTFILE using the specified PROCESSOR,saving the results in OUTPUTFILE.
oev Offline edits viewer Parse a Hadoop edits log file INPUT_FILE and save results in OUTPUT_FILE
2. 基本语法
hdfs oiv -p ⽂件类型 -i 镜像⽂件 -o 转换后文件输出路径
3. 案例实操

[root@hadoop01 current]$ cd /opt/servers/hadoop-2.9.2/data/tmp/dfs/name/current
[root@hadoop01 current]$ hdfs oiv -p XML -i fsimage_0000000000000000265 -o /opt/servers/fsimage.xml
[root@hadoop01 current]$ cat /opt/servers/fsimage.xml

问题:Fsimage中为什么没有记录块所对应DataNode? 在集群启动后,NameNode要求DataNode上报数据块信息,并间隔一段时间后再次上报。

6.2.2 Edits⽂件内容

  1. 基本语法
    hdfs oev -p ⽂件类型 -i编辑日志 -o 转换后文件输出路路径
    2. 案例实操
    [root@hadoop01 current]$ hdfs oev -p XML -i edits_0000000000000000266-0000000000000000267 -o /opt/servers/hadoop-2.9.2/edits.xml
    [root@hadoop01 current]$ cat /opt/servers/hadoop-2.9.2/edits.xml
    
    备注:Edits中只记录了更新相关的操作,查询或者下载文件并不会记录在内!!
    问题:NameNode启动时如何确定加载哪些Edits⽂件呢?

需要借助fsimage⽂件最后数字编码,来确定哪些edits之前是没有合并到fsimage中,启动时只需要加载那些未合并的edits⽂件即可。

6.3 checkpoint周期

[hdfs-default.xml]

<!-- 定时一⼩小时 -->
<property>
   <name>dfs.namenode.checkpoint.period</name>
   <value>3600</value>
</property>
<!-- 一分钟检查一次操作次数,3当操作次数达到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>

第 7 节 NN故障处理

NameNode故障后,HDFS集群就无法正常工作,因为HDFS文件系统的元数据需要由NameNode来管理维护并与Client交互,如果元数据出现损坏和丢失同样会导致NameNode⽆法正常⼯作进⽽HDFS⽂件系统⽆法正常对外提供服务。
如果元数据出现丢失损坏如何恢复呢?
1. 将2NN的元数据拷贝到NN的节点下
此种方式会存在元数据的丢失。
2. 搭建HDFS的HA(⾼可用)集群,解决NN的单点故障问题!!(借助Zookeeper实现HA,一个 Active的NameNode,一个是Standby的NameNode)

第 8 节 Hadoop的限额与归档以及集群安全模式

⾼级命令

  • HDFS⽂件限额配置

HDFS文件的限额配置允许我们以⽂件⼤小或者文件个数来限制我们在某个目录下上传的文件数量或者文件内容总量,以便达到我们类似百度网盘等限制每个⽤户允许上传的最大的⽂件的量
1. 数量限额

#创建hdfs⽂件夹
hdfs dfs -mkdir -p /user/root/
# 给该⽂件夹下⾯设置最多上传两个文件,上传⽂件,发现只能上传一个文件
hdfs dfsadmin -setQuota 2 /user/root/
# 清除文件数量限制
hdfs dfsadmin -clrQuota /user/root/
  1. 空间⼤小限额
    # 限制空间⼤小4KB
    hdfs dfsadmin -setSpaceQuota 4k /user/root/
    #上传超过4Kb的⽂件⼤小上去提示文件超过限额
    hdfs dfs -put /export/softwares/xxx.tar.gz /user/root/
    #清除空间限额
    hdfs dfsadmin -clrSpaceQuota /user/root/ 
    #查看hdfs⽂件限额数量
    hdfs dfs -count -q -h /user/root/
    
  • HDFS的安全模式

安全模式是HDFS所处的一种特殊状态,在这种状态下,文件系统只接受读数据请求,而不接受删除、修改等变更请求。在NameNode主节点启动时,HDFS⾸先进入安全模式,DataNode在启动的时候会向NameNode汇报可用的block等状态,当整个系统达到安全标准时,HDFS自动离开安全模式。如果HDFS出于安全模式下,则文件block不能进行任何的副本复制操作,因此达到最⼩的副本数量要求是基于DataNode启动时的状态来判定的,启动时不会再做任何复制(从⽽达到最⼩副本数量要求),HDFS集群刚启动的时候,默认30S钟的时间是出于安全期的,只有过了30S之后,集群脱离了了安全期,然后才可以对集群进行操作。

  • Hadoop归档技术

主要解决HDFS集群存在⼤量⼩文件的问题!!
由于⼤量⼩文件会占⽤NameNode的内存,因此对于HDFS来说存储⼤量⼩文件造成NameNode内存资源的浪费!
Hadoop存档⽂件HAR文件,是⼀个更高效的文件存档工具,HAR⽂件是由⼀组文件通过archive⼯具创建⽽来,在减少了NameNode的内存使⽤的同时,可以对文件进行透明的访问,通俗来说就是HAR⽂件对NameNode来说是⼀个⽂件减少了内存的浪费,对于实际操作处理文件依然是一个⼀个独立的文件。
image.png
案例
1. 启动YARN集群

[root@hadoop01 hadoop-2.9.2]$ start-yarn.sh
  1. 归档文件
    把/user/input⽬录⾥面的所有⽂件归档成⼀个叫input.har的归档⽂件,并把归档后文件存储到/user/output路径下。
    [root@hadoop01 hadoop-2.9.2]$ bin/hadoop archive -archiveName input.har –p /user/root/input  /user/root/output
    
  2. 查看归档
    [root@hadoop01 hadoop-2.9.2]$ hadoop fs -lsr /user/root/output/input.har
    [root@hadoop01 hadoop-2.9.2]$ hadoop fs -lsr har:///user/root/output/input.har
    
  3. 解归档⽂件
    [root@hadoop01 hadoop-2.9.2]$ hadoop fs -cp har:///user/root/output/input.har/*  \ 
    /user/root