Hadoop入门|HDFS分布式文件系统

一、HDFS概述

1. HDFS产出背景及定义

  1. HDFS产生背景

随着数据量越来越大,在一个操作系统存不下所有的数据,那么就分配到更多的操作系统管理的磁盘中,但是不方便管理和维护,迫切需要一种系统来管理多台机器上的文件,这就是分布式文件管理系统。HDFS只是分布式文件管理系统中的一种。

  1. HDFS定义

    HDFS(Hadoop Distributed File System),它是一个文件系统,用于存储文件,通过目录树来定位文件;其次,它是分布式的,由很多服务器联合起来实现其功能,集群中的服务器有各自的角色,HDFS是分布式存储服务。

    HDFS的使用场景:适合一次写入,多次读出的场景,且不支持文件的修改。适合用来做数据分析,并不适合用来做网盘应用。

2. HDFS优缺点

  1. 优点:

    • 高容错性

      • 数据自动保存多个副本。它通过增加副本的形式,提高容错性。

      • 某一个副本丢失以后,它可以自动恢复。

    • 适合处理大数据

      • 数据规模:能够处理数据规模达到GB、TB、甚至PB级别的数据;
      • 文件规模:能够处理百万规模以上的文件数量,数量相当之大。
    • 可构建在廉价机器上,通过多副本机制,提高可靠性。

  2. 缺点:

    • 不适合低延时数据访问,比如毫秒级的存储数据,是做不到的。

    • 无法高效的对大量小文件进行存储。

      • 存储大量小文件的话,它会占用NameNode大量的内存来存储文件目录和块信息。这样是不可取的,因为NameNode的内存总是有限的;
      • 小文件存储的寻址时间会超过读取时间,它违反了HDFS的设计目标。
    • 不支持并发写入、文件随机修改。

      • 一个文件只能有一个写,不允许多个线程同时写;

      • 仅支持数据append(追加),不支持文件的随机修改。

3. HDFS的重要概念

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

  1. 典型的 Master/Slave 架构

    • HDFS 的架构是典型的 Master/Slave 结构
    • HDFS 集群往往是一个 NameNode+多个DataNode 组成( HA架构会有两个NameNode,联邦机制
    • NameNode 是集群的 主节点,DataNode 是集群的 从节点
  2. 分块存储(block机制)

    • HDFS 中的文件在物理上是 分块存储(block)的,块的大小可以通过配置参数来规定
    • Hadoop2.x 版本中默认的block大小是 128M,老版本默认是64M
    • 切分规则:如果有两个文件,分别是 200M 和 20M,那么第一个文件先分成两个切片(128M + 72M),第二个文件分成一个切片(20M),共三个切片
  3. 命名空间(NameSpace)

    • HDFS

      支持传统的层次型文件组织结构,用户或者应用程序可以创建目录,然后将文件保存在这些目录里。文件系统名字空间的层次结构和大多数现有的文件系统类似:用户可以创建、删除、移动或重命名文件

    • Namenode

      负责维护文件系统的名字空间,任何对文件系统名字空间或属性的修改都将被 Namenode 记录下来

    • 抽象目录树

      HDFS 提供给客户单一的一个抽象目录树,用户不知道这些文件是存储在哪个dataNode上

      访问形式:hdfs://namenode的hostname:port/test/input

      例如:hdfs://linux121:9000/test/input

  4. NameNode 元数据管理

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

    • 文件的各个 block 的具体存储管理由 DataNode 节点承担
    • 一个 block 会有多个 DataNode 来存储,DataNode 会定时向 NameNode 来汇报自己持有的 block 信息( 这就是高容错的体现,很重要
  6. 副本机制

    • 为了容错,文件的所有 block 都会有副本,默认是 3 个(包括本来的block,一共 3 个)。每个文件的 block大小副本系数 都是可配置

    • 应用程序可以指定某个文件的副本数目,副本系数可以在文件创建的时候指定,也可以在之后改变

    • 如果副本数量设置为 10,但设备数只有 3台,那么不会真正创建 10 个副本,而是创建相当于设备数量的副本,即 3个副本。等设备数增加到 10 台,创建的副本数才达到 10 台(很重要

  7. 一次写入,多次读出

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

4. HDFS架构

  1. NameNode: HDFS集群的管理者,Master
  • 维护管理Hdfs的名称空间(NameSpace)
  • 维护副本策略
  • 记录⽂件块(Block)的映射信息
  • 负责处理客户端读写请求
  1. DataNode: NameNode下达命令,DataNode执行实际操作,Slave节点。

    • 保存实际的数据块

    • 负责数据块的读写

  2. Client: 客户端

    • 上传⽂文件到HDFS的时候,Client负责将⽂件切分成Block,然后进行上传
    • 请求NameNode交互,获取文件的位置信息
    • 读取或写⼊文件,与DataNode交互
    • Client可以使⽤一些命令来管理HDFS或者访问HDFS

二、 HDFS 客户端操作

1. Shell命令行操作HDFS

  1. 基本语法

    • bin/hadoop fs 具体命令
    • bin/hdfs dfs 具体命令( 推荐
  2. 命令⼤全

    [root@linux121 hadoop-2.9.2]# bin/hdfs dfs
    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] [-d] <localsrc> ... <dst>]
            [-copyToLocal [-f] [-p] [-ignoreCrc] [-crc] <src> ... <localdst>]
            [-count [-q] [-h] [-v] [-t [<storage type>]] [-u] [-x] <path> ...]
            [-cp [-f] [-p | -p[topax]] [-d] <src> ... <dst>]
            [-createSnapshot <snapshotDir> [<snapshotName>]]
            [-deleteSnapshot <snapshotDir> <snapshotName>]
            [-df [-h] [<path> ...]]
            [-du [-s] [-h] [-x] <path> ...]
            [-expunge]
            [-find <path> ... <expression> ...]
            [-get [-f] [-p] [-ignoreCrc] [-crc] <src> ... <localdst>]
            [-getfacl [-R] <path>]
            [-getfattr [-R] {-n name | -d} [-e en] <path>]
            [-getmerge [-nl] [-skip-empty-file] <src> <localdst>]
            [-help [cmd ...]]
            [-ls [-C] [-d] [-h] [-q] [-R] [-t] [-S] [-r] [-u] [<path> ...]]
            [-mkdir [-p] <path> ...]
            [-moveFromLocal <localsrc> ... <dst>]
            [-moveToLocal <src> <localdst>]
            [-mv <src> ... <dst>]
            [-put [-f] [-p] [-l] [-d] <localsrc> ... <dst>]
            [-renameSnapshot <snapshotDir> <oldName> <newName>]
            [-rm [-f] [-r|-R] [-skipTrash] [-safely] <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>               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 <local|resourcemanager:port>  specify a ResourceManager
    -files <file1,...>                specify a comma-separated list of files to be copied to the map reduce cluster
    -libjars <jar1,...>               specify a comma-separated list of jar files to be included in the classpath
    -archives <archive1,...>          specify a comma-separated list of archives to be unarchived on the compute machines
  3. HDFS命令演示

    • 首先启动Hadoop集群(HDFS和YARN要做各自的节点上启动

      [root@linux121 hadoop-2.9.2]$ sbin/start-dfs.sh
      [root@linux122 hadoop-2.9.2]$ sbin/start-yarn.sh
    • **-help:**输出这个命令参数

      [root@linux121 hadoop-2.9.2]$ hdfs dfs -help rm
    • -ls: 显示⽬录信息

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

      [root@linux121 hadoop-2.9.2]$ hdfs dfs -mkdir -p /lagou/bigdata
    • -moveFromLocal: 从本地剪切粘贴到HDFS

      [root@linux121 hadoop-2.9.2]$ touch hadoop.txt
      [root@linux121 hadoop-2.9.2]$ hdfs dfs  -moveFromLocal  ./hadoop.txt /lagou/bigdata
    • -appendToFile: 追加⼀个文件到已经存在的⽂件末尾

      [root@linux121 hadoop-2.9.2]$ touch hdfs.txt
      [root@linux121 hadoop-2.9.2]$ vi hdfs.txt

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

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

       [root@linux121 hadoop-2.9.2]$ hdfs dfs  -chmod  666 /lagou/bigdata/hadoop.txt
      [root@linux121 hadoop-2.9.2]$ hdfs dfs  -chown  root:root /lagou/bigdata/hadoop.txt
    • -copyFromLocal: 从本地⽂件系统中拷⻉文件到HDFS路径去

      [root@linux121 hadoop-2.9.2]$ hdfs dfs -copyFromLocal README.txt /
    • -copyToLocal: 从HDFS拷贝到本地

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

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

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

      [root@linux121 hadoop-2.9.2]$ hdfs dfs -get /lagou/bigdata/hadoop.txt ./
    • -put: 等同于copyFromLocal

      [root@linux121 hadoop-2.9.2]$ hdfs dfs -mkdir -p /user/root/test/ 

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

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

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

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

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

      [root@linux121 hadoop-2.9.2]$ hdfs dfs -setrep 10 /lagou/bigdata/hadoop.txt

2. JAVA客户端

2.1 客户端环境准备

  1. 将Hadoop-2.9.2安装包解压到非中⽂路径(例如:E:\hadoop-2.9.2)。

  2. 配置HADOOP_HOME环境变量

  3. 配置Path环境变量。

  4. 创建⼀个Maven工程ClientDemo

  5. 导入相应的依赖坐标+日志配置文件

    • 需要导入三个模块:hadoop-common、hadoopclient、hadoop-hdfs

      <dependencies>
        
        <!-- 导入三个模块:hadoop-common、hadoopclient、hadoop-hdfs -->
        
        <!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-common -->
       <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-common</artifactId>
        <version>2.9.2</version>
       </dependency>
        
       <!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoopclient -->
       <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>
        
        <!-- 单元测试 -->
        <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>

      </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
  6. 创建包名:com.lagou.hdfs

  7. 创建HdfsClient类

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

会遇到的问题:

  1. 如果不指定操作 HDFS 集群的用户信息,默认是 获取当前操作系统的用户信息,出现权限被拒绝的问题,报错如下:

    出现问题的原因:

    由于 HDFS 的权限管理机制是:用户告诉 hdfs 自己是什么用户,hdfs 都会相信,认为用户不会做坏事。HDFS⽂件权限的目的,防⽌好⼈做错事,⽽不是阻⽌坏人做坏事。HDFS相信你告诉我你是谁,你就是谁。因此这种权限管理比较鸡肋

    解决办法:

    1. 把 根目录 的权限设置为 777 ,关于文件权限管理 交给其他软件去做

      hdfs dfs -R 777 /
    2. 在配置文件 hdfs-site.xml 中添加以下属性,以关闭 HDFS 集群权限校验,修改完成之后要分发到其它节点,同时要重启HDFS集群

      vim hdfs-site.xml

      #
      添加如下属性
      <property>
           <name>dfs.permissions.enabled</name>
           <value>false</value>
      </property>
    3. 指定用户信息获取 FileSystem 对象

      FileSystem fs = FileSystem.get(new URI("hdfs://linux121:9000"), configuration, "root")
  2. windows 解压安装 Hadoop后,在调用相关API操作 HDFS 集群时可能会报错,如下图:

    出现问题的原因:

    这是由于 Hadoop 安装缺少 windows 操作系统相关文件所致

    解决办法:

    将 winutils.exe 拷贝到 windows 系统 Hadoop 安装目录的 bin 目录下即可

  3. IDEA 启动 Hadoop 的任务时,出现如下报错:

解决办法:

将 Hadoop.dll 文件 添加到 C:\Windows\System32 中

参数的优先级:

当在配置文件,java 代码中,包括hdfs服务器的默认配置,都设置副本数量时,参数的优先级是:

代码中设置的值 -->用户自定义配置文件 -->服务器的默认配置

2.2 HDFS的API操作

  1. 上传文件

    • 可通过 configuration.set("dfs.replication", "2") 设置副本数量

      @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://linux121:9000"), configuration, "root");

          // 2上传⽂文件
       fs.copyFromLocalFile(new Path("e:/lagou.txt"), new Path("/lagou.txt"));

          // 3 关闭资源
       fs.close();
      }
    • 也可通过 配置文件 hdfs-site.xml 来指定,文件放在 resources 目录下

      注:配置文件名 不能写错!!

      <?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>
  2. 下载文件

    @Test
    public void testCopyToLocalFile() throws IOException, InterruptedException, URISyntaxException{

        // 1 获取⽂文件系统
     Configuration configuration = new Configuration();
     FileSystem fs = FileSystem.get(new URI("hdfs://linux121:9000"), configuration, "root");

        // 2 执⾏行行下载操作
     // boolean delSrc 指是否将原⽂文件删除
     // Path src 指要下载的⽂文件路路径
     // Path dst 指将⽂文件下载到的路路径
     // boolean useRawLocalFileSystem 是否开启⽂文件校验
     fs.copyToLocalFile(falsenew Path("/lagou.txt"), newPath("e:/lagou_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://linux121:9000"), configuration, "root");

        // 2 执⾏行行删除
     fs.delete(new Path("/api_test/"), true);

        // 3 关闭资源
     fs.close();
    }
  4. HDFS文件名更改

    @Test
    public void testRename() throws IOException, InterruptedException, URISyntaxException{

     // 1 获取文件系统
     Configuration configuration = new Configuration();
     FileSystem fs = FileSystem.get(new URI("hdfs://linux121:9000"), configuration, "root");
      
     // 2 修改文件名称
     fs.rename(new Path("/banzhang.txt"), new Path("/banhua.txt"));
      
     // 3 关闭资源
     fs.close();
    }

  5. 查看 文件名称、权限、长度、块信息

    @Test
    public void testListFiles() throws IOException, InterruptedException, URISyntaxException{

        // 1获取⽂文件系统
     Configuration configuration = new Configuration();
     FileSystem fs = FileSystem.get(new URI("hdfs://linux121: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();

  6. 区分文件和文件夹

    @Test
    public void testListStatus() throws IOException, InterruptedException, URISyntaxException{

        // 1 获取⽂文件配置信息
     Configuration configuration = new Configuration();
     FileSystem fs = FileSystem.get(new URI("hdfs://linux121: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();
    }
  7. 文件上传 create

    将本地文件上传到 hdfs 上

    @Test
    public void putFileToHDFS() throws IOException, InterruptedException, URISyntaxException {

        // 1 获取文件系统
     Configuration configuration = new Configuration();
     FileSystem fs = FileSystem.get(new URI("hdfs://linux121:9000"), configuration, "root");

        // 2 创建输⼊流
     FileInputStream fis = new FileInputStream(new File("e:/lagou.txt"));

        // 3 获取输出流
     FSDataOutputStream fos = fs.create(new Path("/lagou_io.txt"));

        // 4 流的拷贝
     IOUtils.copyBytes(fis, fos, configuration);

        // 5 关闭资源(这里其实不需要关闭)
     IOUtils.closeStream(fos);
     IOUtils.closeStream(fis);
     fs.close();
    }
  8. 文件下载 open

    将 hdfs 文件下载到本地

    @Test
    public void getFileFromHDFS() throws IOException, InterruptedException, URISyntaxException{

        // 1 获取⽂文件系统
        Configuration configuration = new Configuration();
     FileSystem fs = FileSystem.get(new URI("hdfs://linux121:9000"), configuration, "root");

        // 2 获取输⼊入流
     FSDataInputStream fis = fs.open(new Path("/lagou_io.txt"));

        // 3 获取输出流
     FileOutputStream fos = new FileOutputStream(new File("e:/lagou_io_copy.txt"));

        // 4 流的对拷
     IOUtils.copyBytes(fis, fos, configuration);

        // 5 关闭资源
     IOUtils.closeStream(fos);
     IOUtils.closeStream(fis);
     fs.close();
    }
  9. 定位读取 seek

    将 HDFS上 的 lagou.txt 的内容在控制台输出两次

    @Test
    public void readFileSeek2() throws IOException, InterruptedException, URISyntaxException{

        // 1 获取⽂文件系统
     Configuration configuration = new Configuration();
     FileSystem fs = FileSystem.get(new URI("hdfs://linux121:9000"), configuration, "root");
     
        // 2 打开输⼊入流,读取数据输出到控制台
     FSDataInputStream in = null;
     try{
      in = fs.open(new Path("/lagou.txt"));
      IOUtils.copyBytes(in, System.out, 4096false);
      
            //从头再次读取,偏移量为0 表示从头读起
            in.seek(0); 
      IOUtils.copyBytes(in, System.out, 4096false);
     }finally {
         IOUtils.closeStream(in);
            fs.close();
     }
    }

三、 HDFS读写解析

1. HDFS读数据流程

HDFS读数据流程:

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

2.HDFS写数据流程

HDFS写数据流程:

  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(64KB)为单位,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:/lagou.txt"));
        //2 准备好写出数据到hdfs的输出流
        final FSDataOutputStream out = fs.create(new Path("/lagou.txt"), new Progressable() {
            public void progress() {
                //这个progress⽅法就是每传输64KB(packet)就会执行一次
                System.out.println("&");
            }
        });
        //3 实现流拷贝
        IOUtils.copyBytes(in, out, configuration);
        //默认关闭流选项是true,所以会⾃动关闭
        //4 关流 可以再次关闭也可以不关了
    }

四、 NameNode和SecondaryNameNode

1. NN和2NN工作机制

问题引出: NameNode如何管理和存储元数据?

计算机中存储数据的两种方式:磁盘、内存

  • 元数据存储磁盘:存储磁盘⽆法⾯对客户端对元数据信息的随机访问,还有响应客户请求,必然是效率过低。但是安全性⾼
  • 元数据存储内存:元数据存放内存,可以高效的查询以及快速响应客户端的查询请求,数据保存在内存,如果断点,内存中的数据全部丢失。安全性低

解决办法: 内存+磁盘;NameNode内存+FsImage的⽂件(磁盘)

新问题: 磁盘和内存中元数据如何划分?

两个数据一模⼀样,还是两个数据合并到一起才是⼀份完整的数据呢?

  • 如果两份数据一模一样的话,客户端 Client 如果对元数据进行增删改操作,则需要时刻保证两份数据的一致性,导致效率变低
  • 如果两份数据合并后 ==> 完整数据的情况。NameNode 引入了 edits 文件(日志文件,只能追加写入),记录了client 的增删改操作,而不再让 NameNode 把数据 dump 出来形成 fsimage文件(让 NameNode 专注于处理客户端的请求)
  • edits文件:文件生成快,恢复慢;fsimage文件:文件生成慢,恢复快

新问题: 谁来负责文件合并?

如果长时间添加数据到Edits中,会导致该文件数据过大,效率降低,而且一旦断电,恢复元数据需要的时间过长。因此,需要定期进行FsImage和Edits的合并,但是谁来合并?

  • NameNode:NameNode本身任务重,再负责合并,势必效率过低,甚至会影响本身的任务
  • 因此,引入一个新的节点SecondaryNamenode,专门用于FsImage和Edits的合并。

流程分析:

  1. 第一阶段:NameNode启动
    1. 第一次启动NameNode格式化后,创建Fsimage和Edits文件。如果不是第一次启动,直接加载编辑日志和镜像文件到内存。
    2. 客户端对元数据进行增删改的请求。
    3. NameNode记录操作日志,更新滚动日志(所谓滚动日志,即 把前一阶段的日志保存成一个日志文件,再新生成一个文件,新生成文件后缀带有 inprogress 字样)。
    4. NameNode在内存中对数据进行增删改。
  2. 第二阶段:Secondary NameNode工作
    1. Secondary NameNode询问NameNode是否需要CheckPoint。直接带回NameNode是否检查结果。
    2. Secondary NameNode请求执行CheckPoint。
    3. NameNode滚动正在写的Edits日志。
    4. 将滚动前的编辑日志和镜像文件拷贝到Secondary NameNode。
    5. Secondary NameNode加载编辑日志和镜像文件到内存,并合并。
    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中。

2. Fsimage和Edits解析

  • NameNode在执行格式化之后,会在/opt/lagou/servers/hadoop-2.9.2/data/tmp/dfs/name/current⽬录下产⽣如下文件

    • Fsimage⽂件: 是namenode中关于元数据的镜像,一般称为检查点,这里包含了HDFS⽂件系统所有⽬录以及⽂件相关信息(Block数量,副本数量,权限等信息)
    • Edits文件 : 存储了客户端对HDFS文件系统所有的更新操作记录,Client对HDFS⽂件系统所有的更新操作都会被记录到Edits⽂件中(不包括查询操作)
    • seen_txid: 该⽂件是保存了一个数字,数字对应着最后一个Edits⽂件名的数字
    • VERSION: 该⽂件记录namenode的一些版本号信息,比如:CusterId,namespaceID等
  • NameNode启动时会将Fsimage⽂件加载到内存中,同时也把之前未合并元数据的Edits⽂件加载,集合两个文件中的元数据这样保证了NameNode中的元数据是最新最全的。通俗点说就是NameNode启动时把Fsimage和Edits⽂件进⾏了合并。

2.1 Fsimage⽂件内容

  1. 官方地址:https://hadoop.apache.org/docs/r2.9.2/hadoop-project-dist/hadoop-hdfs/HdfsImageViewer.html

  2. 查看oiv和oev命令

    [[root@linux121 current]$ hdfs
    oiv            apply the offline fsimage viewer to an fsimage
    oev            apply the offline edits viewer to an edits file
  3. 基本语法

    hdfs oiv -p 文件类型 -i镜像文件 -o 转换后文件输出路径
  4. 案例实操

    [root@linux121 current]$ cd /opt/lagou/servers/hadoop2.9.2/data/tmp/dfs/name/current
    [root@linux121 current]$ hdfs oiv -p XML -i fsimage_0000000000000000265 -o /opt/lagou/servers/fsimage.xml
    [root@linux121 current]$ cat /opt/lagou/servers/fsimage.xml
  5. 查看文件

    <?xml version="1.0"?>
    <fsimage>
        <version>
            <layoutVersion>-63</layoutVersion>
            <onDiskVersion>1</onDiskVersion>
            <oivRevision>826afbeae31ca687bc2f8471dc841b66ed2c6704</oivRevision>
        </version>
        <NameSection>
            <namespaceId>722925838</namespaceId>
            <genstampV1>1000</genstampV1>
            <genstampV2>1049</genstampV2>
            <genstampV1Limit>0</genstampV1Limit>
            <lastAllocatedBlockId>1073741873</lastAllocatedBlockId>
            <txid>621</txid>
        </NameSection>
        <INodeSection>
            <lastInodeId>16487</lastInodeId>
            <numInodes>36</numInodes>
            <inode>
                <id>16385</id>
                <type>DIRECTORY</type>
                <name></name>
                <mtime>1604115316122</mtime>
                <permission>root:supergroup:0777</permission>
                <nsquota>9223372036854775807</nsquota>
                <dsquota>-1</dsquota>
            </inode>
            <inode>
                <id>16389</id>
                <type>DIRECTORY</type>
                <name>wcinput</name>
                <mtime>1604023034981</mtime>
                <permission>root:supergroup:0777</permission>
                <nsquota>-1</nsquota>
                <dsquota>-1</dsquota>
            </inode> 
          </INodeSection>
    </fsimage>

    问题: Fsimage 中为什么没有记录块所对应 DataNode ?

    答案: 在集群启动后,NameNode 要求 DataNode 上报数据块信息,并间隔一段时间后再次上报

2.2 Edits文件内容

  1. 基本语法

    hdfs oev -p 文件类型 -i编辑日志 -o 转换后文件输出路径
  2. 案例实操

    [root@linux121 current]$ hdfs oev -p XML -i edits_0000000000000000266-0000000000000000267 -o /opt/lagou/servers/hadoop-2.9.2/edits.xml
    [root@linux121 current]$ cat /opt/lagou/servers/hadoop-2.9.2/edits.xml
  3. 查看文件

    <?xml version="1.0" encoding="utf-8"?>

    <EDITS> 
      <EDITS_VERSION>-63</EDITS_VERSION>  
      <RECORD> 
        <OPCODE>OP_START_LOG_SEGMENT</OPCODE>  
        <DATA> 
          <TXID>113</TXID> 
        </DATA> 
      </RECORD>  
      <RECORD> 
        <OPCODE>OP_SET_PERMISSIONS</OPCODE>  
        <DATA> 
          <TXID>114</TXID>  
          <SRC>/wcoutput/_SUCCESS</SRC>  
          <MODE>493</MODE> 
        </DATA> 
      </RECORD>  
      <RECORD> 
        <OPCODE>OP_SET_PERMISSIONS</OPCODE>  
        <DATA> 
          <TXID>115</TXID>  
          <SRC>/wcoutput/part-r-00000</SRC>  
          <MODE>493</MODE> 
        </DATA> 
      </RECORD> 
    </EDITS>
    ...

    备注:Edits中只记录了更新相关的操作,查询或者下载文件并不会记录在内!!

    问题: NameNode启动时如何确定加载哪些Edits⽂件呢?

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

3. checkpoint周期

  1. 通常情况下,SecondaryNameNode每隔一小时执行一次。

    官方默认配置文件:hdfs-default.xml

    <property>
      <name>dfs.namenode.checkpoint.period</name>
      <value>3600</value>
    </property>
  2. 一分钟检查一次操作次数,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 >

4. NameNode故障处理

NameNode故障后,HDFS集群就无法正常工作,因为HDFS文件系统的元数据需要由NameNode来管理维护并与Client交互,如果元数据出现损坏和丢失同样会导致NameNode⽆法正常⼯作进⽽HDFS⽂件系统⽆法正常对外提供服务。

如果元数据出现丢失损坏如何恢复呢?

  1. 搭建HDFS的HA(⾼可用)集群,解决NN的单点故障问题!!(借助Zookeeper实现HA,一个 Active的NameNode,一个是Standby的NameNode)

  2. 将SecondaryNameNode中数据拷贝到NameNode存储数据的目录

    • kill -9 NameNode进程

    • 删除NameNode存储的数据(/opt/lagou/servers/hadoop-2.9.2/data/tmp/dfs/name)

      [root@linux121 hadoop-2.9.2]rm -rf /opt/lagou/servers/hadoop-2.9.2/data/tmp/dfs/name/*
    • 拷贝SecondaryNameNode中数据到原NameNode存储数据目录

      [root@linux121 hadoop-2.9.2]$ scp -r atguigu@hadoop104:/opt/lagou/servers/hadoop-2.9.2/data/tmp/dfs/namesecondary/* ./name/
    • 重新启动NameNode

    [root@linux121 hadoop-2.9.2]$ sbin/hadoop-daemon.sh start namenode
  3. 使用-importCheckpoint选项启动NameNode守护进程,从而将SecondaryNameNode中数据拷贝到NameNode目录中。

    • 修改hdfs-site.xml中的

      <property>
        <name>dfs.namenode.checkpoint.period</name>
        <value>120</value>
      </property>

      <property>
        <name>dfs.namenode.name.dir</name>
        <value>/opt/lagou/servers/hadoop-2.9.2/data/tmp/dfs/name</value>
      </property>

    • kill -9 NameNode进程

    • 删除NameNode存储的数据(/opt/lagou/servers/hadoop-2.9.2/data/tmp/dfs/name)

      [root@linux121 hadoop-2.9.2]rm -rf /opt/lagou/servers/hadoop-2.9.2/data/tmp/dfs/name/*
    • 如果SecondaryNameNode不和NameNode在一个主机节点上,需要将SecondaryNameNode存储数据的目录拷贝到NameNode存储数据的平级目录,并删除in_use.lock文件

      [root@linux123 dfs]$ scp -r root@linux121:/opt/lagou/servers/hadoop-2.9.2/data/tmp/dfs/namesecondary ./

      [root@linux123 namesecondary]$ rm -rf in_use.lock

      [root@linux123 dfs]$ pwd
      /opt/lagou/servers/hadoop-2.9.2//data/tmp/dfs

      [root@linux123 dfs]$ ls
      data  name  namesecondary

    • 导入检查点数据(等待一会ctrl+c结束掉)

      [root@linux121 hadoop-2.9.2]$ bin/hdfs namenode -importCheckpoint
    • 启动NameNode

      [root@linux121 hadoop-2.9.2]$ sbin/hadoop-daemon.sh start namenode

五、Hadoop的限额与归档以及集群安全模式

1. HDFS⽂件限额配置

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

  1. 数量限额

    #创建hdfs⽂件夹
    hdfs dfs -mkdir -p /user/root/lagou

    #
     给该⽂件夹下⾯设置最多上传两个文件,上传⽂件,发现只能上传一个文件
    hdfs dfsadmin -setQuota 2 /user/root/lagou 

    #
     清除文件数量限制
    hdfs dfsadmin -clrQuota /user/root/lagou
  2. 空间⼤小限额

    # 限制空间⼤小4KB
    hdfs dfsadmin -setSpaceQuota 4k /user/root/lagou

    #
    上传超过4Kb的⽂件⼤小上去提示文件超过限额
    hdfs dfs -put /export/softwares/xxx.tar.gz /user/root/lagou

    #
    清除空间限额
    hdfs dfsadmin -clrSpaceQuota /user/root/lagou

    #
    查看hdfs⽂件限额数量
    hdfs dfs -count -q -h /user/root/lagou

2. HDFS的安全模式

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

3. Hadoop归档技术

  1. 主要解决HDFS集群存在⼤量⼩文件的问题!!

    由于⼤量⼩文件会占⽤NameNode的内存,因此对于HDFS来说存储⼤量⼩文件造成NameNode内存资源的浪费!

  2. Hadoop存档⽂件HAR文件,是⼀个更高效的文件存档工具,HAR⽂件是由⼀组文件通过archive⼯具创建⽽来,在减少了NameNode的内存使⽤的同时,可以对文件进行透明的访问,通俗来说就是HAR⽂件对NameNode来说是⼀个⽂件减少了内存的浪费,对于实际操作处理文件依然是一个⼀个独立的文件。

  3. 案例

    1. 启动YARN集群

      [root@linux121 hadoop-2.9.2]$ start-yarn.sh
    2. 归档文件

      把/user/lagou/input⽬录⾥面的所有⽂件归档成⼀个叫input.har的归档⽂件,并把归档后文件存储到/user/lagou/output路径下。

       [root@linux121 hadoop-2.9.2]$ bin/hadoop archive -archiveName input.har –p /user/root/input  /user/root/output
    3. 查看归档

      [root@linux121 hadoop-2.9.2]$ hadoop fs -lsr /user/root/output/input.har
      [root@linux121 hadoop-2.9.2]$ hadoop fs -lsr har:///user/root/output/input.har
    4. 解归档⽂件

      [root@linux121 hadoop-2.9.2]$ hadoop fs -cp har:///user/root/output/input.har/*  /user/root

六、DataNode

1. DataNode工作机制

DataNode工作机制:

  1. 一个数据块在DataNode上以文件形式存储在磁盘上,包括两个文件,一个是数据本身,一个是元数据包括数据块的长度,块数据的校验和,以及时间戳。
  2. DataNode启动后向NameNode注册,通过后,周期性(1小时)的向NameNode上报所有的块信息。
  3. 心跳是每3秒一次,心跳返回结果带有NameNode给该DataNode的命令如复制块数据到另一台机器,或删除某个数据块。如果超过10分钟没有收到某个DataNode的心跳,则认为该节点不可用。
  4. 集群运行中可以安全加入和退出一些机器。

2. 数据完整性

  1. 思考:

    如果电脑磁盘里面存储的数据是控制高铁信号灯的红灯信号(1)和绿灯信号(0),但是存储该数据的磁盘坏了,一直显示是绿灯,是否很危险?同理DataNode节点上的数据损坏了,却没有发现,是否也很危险,那么如何解决呢?

  2. DataNode节点保证数据完整性的方法:

    1. 当DataNode读取Block的时候,它会计算CheckSum。
    2. 如果计算后的CheckSum,与Block创建时值不一样,说明Block已经损坏。
    3. Client读取其他DataNode上的Block。
    4. DataNode在其文件创建后周期验证CheckSum,如下图所示。 校验和

3. 掉线时限参数设置

  1. DataNode进程死亡或者网络故障造成DataNode无法与NameNode通信

  2. NameNode不会立即把该节点判定为死亡,要经过一段时间,这段时间暂称作超时时长。

  3. HDFS默认的超时时长为10分钟+30秒。

  4. 如果定义超时时间为TimeOut,则超时时长的计算公式为:

    TimeOut  = 2 * dfs.namenode.heartbeat.recheck-interval + 10 * dfs.heartbeat.interval。

    而默认的dfs.namenode.heartbeat.recheck-interval 大小为5分钟,dfs.heartbeat.interval默认为3秒。

注意:

  • hdfs-site.xml 配置文件中的heartbeat.recheck.interval的单位为 毫秒dfs.heartbeat.interval的单位为

  • <property>
        <name>dfs.namenode.heartbeat.recheck-interval</name>
        <value>300000</value>
    </property>
    <property>
        <name>dfs.heartbeat.interval</name>
        <value>3</value>
    </property>

对大数据感兴趣的小伙伴可以关注我的公众号:大数据学习宝典,在上面会定期更新学习笔记和一些心得!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值