Hadoop-5-HDFS

Hadoop-5-HDFS

1、概述

 Hadoop Distributed File System (HDFS™),是一种分布式文件管理系统,用于管理多个机器上的文件

 它主要用于存储文件,通过目录树结构来定位文件的位置。适合一次写入,多次读出的场景,不支持文件的修改,所以用它做数据的分析,而不是当作网盘

2、优缺点

【1】优点

  1. 高容错性。容错性体现在数据保存到多个副本上(DataNode),并且可以通过增加副本的数量(replication),来提高这种特性。并且当一个副本的数据丢失后,它还可以自动恢复
  2. 大数据量。一是处理数据的量级,可以达到TB、PB的级别。二是文件的数量,能够处理百万以上的规模
  3. 流式数据。一次写入,多次写出。只能追加,不能修改。保证数据的一致性
  4. 高可用性。可以将数据节点部署到多个廉价机器上

【2】缺点

  1. 不能实现低延迟的数据访问。无法做到毫秒级的海量数据存储
  2. 无法高效地存储多个小文件。处理大量的小文件的存储、目录结构以及块信息,会占用NameNode的大量内存资源。此外小文件存储时的寻址时间会超过其读取时间,这违反了HDFS设计的初衷
  3. 不能并发写入和随机修改。对于一个文件,在同一时间内只能有一个写入,不允许多个线程同时写入。仅支持文件的追加,不支持文件的修改

3、架构设计

 主要由NameNode,DataNode和SecondaryNameNode组成

 需要注意的是SecondaryNameNode不是NameNode的热备,所以当NameNode节点挂掉时,其并不能立即替换NameNode并提供服务。SecondaryNameNode是一个用于监控HDFS状态的后台辅助程序,它每隔一段时间就会获取NameNode(HDFS元数据)的快照

4、块

 HDFS中的文件在物理上是分块存储(block),块的大小可以通过etc/hadoop/hdfs-site.xml中的dfs.blocksize来设置。apache hadoop 2.9.2默认大小为134217728(128MB)

 HDFS的块的大小比物理磁盘的块的大小要大,这是为了减少寻址的开销。如果块设置得足够大,那么数据传输的时间要明显地久于块寻址的时间

 寻址时间为传输时间的1%时,是最佳状态。计算公式:文件传输时间 = 磁盘寻址时间 / 0.01块的大小 = 文件传输时间 * 磁盘传输速率

 根据公式:假如寻址时间是10s,传输速率是100MB/s,那么块的大小 = 10s / 0.01 * 100MB/s = 100MB

5、命令操作【常用命令】

 所有命令都是通过bin/hadoop fs 命令的方式执行的。注意命令前面都有一个横杠

【1】创建目录

-mkdir -p,选项p表示递归,和Linux命令一样,不带参数仅创建一级目录

【2】查看目录

-ls -R,选项R表示递归,它会一直到目录的最里面

【3】删除目录

-rmdir

【4】上传文件

-copyFromLocal /本地文件 /HDFS目录,复制本地文件到HDFS

-put /本地文件 /HDFS目录,和copyFromLocal命令一样

-moveFromLocal /本地文件 /HDFS目录,移动本地文件到HDFS

【5】下载文件

-copyToLocal /HDFS文件 /本地目录,复制HDFS文件到本地

-get /HDFS文件 /本地目录,和copyToLocal命令一样

-moveToLocal /HDFS文件 /本地目录,移动HDFS文件到本地。

Hadoop 2.9.2,提示:moveToLocal: Option ‘-moveToLocal’ is not implemented yet.

【6】追加文件

-appendToFile /本地文件 /HDFS文件,将本地文件的内容追加到HDFS文件的末端

【7】查看文件

-cat /HDFS文件或目录,查看文件,可以使用*通配符,和Linux命令一样

-tail -f /HDFS文件,查看文件末尾,后41行,可以追加f选项,和Linux命令一样

【8】删除文件

-rm -r /HDFS目录或文件,删除文件或目录,和Linux命令一样

6、Java客户端操作

【1】准备环境

  1. Java
  2. Maven
  3. 和服务端一致的Hadoop,并解压缩到无中文目录
  4. 下载winutils,并将hadoop-2.8.3/bin目录里的winutils.exe复制到解压缩后的hadoop的bin目录当中
  5. 配置环境变量:HADOOP_HOME到解压缩目录
  6. 打开IDE。一定要先配置好环境变量再打开IDE

【2】MAVEN依赖

<properties>
    <!-- 和服务端版本一致 -->
    <hadoop.version>2.9.2</hadoop.version>
</properties>

<dependency>
    <!-- hadoop-client间接依赖hadoop-common -->
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-client</artifactId>
    <version>${hadoop.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-hdfs</artifactId>
    <version>${hadoop.version}</version>
</dependency>

【3】获取文件系统

通过org.apache.hadoop.fs.FileSystem对象的静态方法来获取

  1. FileSystem get(Configuration conf) throws IOException,需要传递一个org.apache.hadoop.conf.Configuration对象,该配置对象的属性,可以从etc/hadoop/core-site.xml中获取

    Configuration conf = new Configuration();
    // NameNode服务器的IP,9000是默认端口
    conf.set("fs.defaultFS", "hdfs://???.???.???.???:9000");
    FileSystem fileSystem = null;
    try {
          
          
        fileSystem = FileSystem.get(conf);
        System.out.println(fileSystem);
    } catch (IOException e) {
          
          
        e.printStackTrace();
    } finally {
          
          
        if (null != fileSystem) {
          
          
            try {
          
          
                fileSystem.close();
            } catch (IOException e) {
          
          
                e.printStackTrace();
            }
        }
    }
    
  2. FileSystem get(final URI uri, final Configuration conf, String user) throws IOException, InterruptedException

    URI uri = null;
    try {
          
          
        // NameNode服务器的IP,9000是默认端口
        uri = new URI("hdfs://???.???.???.???:9000");
    } catch (URISyntaxException e) {
          
          
        e.printStackTrace();
        return;
    }
    
    Configuration conf = new Configuration();
    FileSystem fileSystem = null;
    try {
          
          
        // 操作文件系统的用户名
        fileSystem = FileSystem.get(uri, conf, "???");
        System.out.println(fileSystem);
    } catch (IOException | InterruptedException e) {
          
          
        e.printStackTrace();
    } finally {
          
          
        if (null != fileSystem) {
          
          
            try {
          
          
                fileSystem.close();
            } catch (IOException e) {
          
          
                e.printStackTrace();
            }
        }
    }
    
  3. 【推荐】FileSystem get(URI uri, Configuration conf) throws IOException

    URI uri = null;
    try {
          
          
        // NameNode服务器的IP,9000是默认端口
        uri = new URI("hdfs://???.???.???.???:9000");
    } catch (URISyntaxException e) {
          
          
        e.printStackTrace();
        return;
    }
    
    // 操作文件系统的用户名。这句话也可以配置到vm参数当中-DHADOOP_USER_NAME=???
    System.setProperty("HADOOP_USER_NAME", "???");
    
    Configuration conf = new Configuration();
    FileSystem fileSystem = null;
    try {
          
          
        fileSystem = FileSystem.get(uri, conf);
        System.out.println(fileSystem);
    } catch (IOException e) {
          
          
        e.printStackTrace();
    } finally {
          
          
        if (null != fileSystem) {
          
          
            try {
          
          
                fileSystem.close();
            } catch (IOException e) {
          
          
                e.printStackTrace();
            }
        }
    }
    

【4】参数配置的优先级

 通过org.apache.hadoop.conf.Configuration对象配置参数的优先级是最高的。接着是classpath路径下的配置文件,如core-site.xml等。最后才是默认配置的参数

【5】创建目录

try {
    
    
    boolean mkdirs = fileSystem.mkdirs(new Path("/?/?/?"));
    System.out.println(mkdirs ? "成功" : "失败");
} catch (IOException e) {
    
    
    e.printStackTrace();
}

【6】查看目录

try {
    
    
    RemoteIterator<LocatedFileStatus> remoteIterator = fileSystem.listFiles(new Path("/"), true); // 第二个参数表示是否递归

    System.out.println("Permission\tOwner\tGroup\t\tSize\tLast Modified\tPath\t\tReplication");
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM-dd HH:mm");

    while (remoteIterator.hasNext()) {
    
    
        LocatedFileStatus locatedFileStatus = remoteIterator.next();

        String permission = locatedFileStatus.getPermission().toString();
        StringBuilder line = new StringBuilder(permission).append("\t");

        String owner = locatedFileStatus.getOwner();
        line.append(owner).append("\t");

        String group = locatedFileStatus.getGroup();
        line.append(group).append("\t");

        long size = locatedFileStatus.getLen();
        line.append(size).append("\t");

        long lastModified = locatedFileStatus.getModificationTime();
        String time = simpleDateFormat.format(new Date(lastModified));
        line.append(time).append("\t\t");

        Path path = locatedFileStatus.getPath();
        String pathStr = path.toString();
        line.append(pathStr).append("\t\t");

        short replication = locatedFileStatus.getReplication();
        line.append(replication);

        BlockLocation[] blockLocations = locatedFileStatus.getBlockLocations();
        line.append(Arrays.asList(blockLocations)).append("\t");

        System.out.println(line);
    }
} catch (IOException e) {
    
    
    e.printStackTrace();
}

【7】删除目录或文件

try {
    
    
    boolean delete = fileSystem.delete(new Path("/?"), true); // 第二个参数表示是否递归
    System.out.println(delete ? "成功" : "失败");
} catch (IOException e) {
    
    
    e.printStackTrace();
}

【8】上传文件

// 复制文件
try {
    
    
    fileSystem.copyFromLocalFile(new Path("?:\\???"), new Path("/???/???"));
} catch (IOException e) {
    
    
    e.printStackTrace();
}

// 剪切文件
try {
    
    
    fileSystem.moveFromLocalFile(new Path("?:\\???"), new Path("/???/???"));
} catch (IOException e) {
    
    
    e.printStackTrace();
}

// 复制文件,流式方式
File file = new File("?:\\???");
try {
    
    
    FileInputStream fis = new FileInputStream(file);
    FSDataOutputStream fsos = fileSystem.create(new Path("/???/???"));
    /*
     * 第三个参数,缓冲容器大小
     * 第四个参数,是否关闭输入流和输入流
     */
    IOUtils.copyBytes(fis, fsos, 4096, true);
} catch (IOException e) {
    
    
    e.printStackTrace();
}

【9】下载文件

try {
    
    
    /*
     * 第一个参数,是否删除源文件。true: 剪切,false: 复制
     * 最后一个参数,是否将RawLocalFileSystem作为本地文件系统。推荐使用
     */
    fileSystem.copyToLocalFile(false, new Path("?:\\???"), new Path("/???/???"), true);
} catch (IOException e) {
    
    
    e.printStackTrace();
}

// 下载文件,流式方式
try {
    
    
    FSDataInputStream fsis = fileSystem.open(new Path("/???/???");
    File file = new File("?:\\???");
    FileOutputStream fos = new FileOutputStream(file);
    /*
     * 第三个参数,缓冲容器大小
     * 第四个参数,是否关闭输入流和输入流
     */
    IOUtils.copyBytes(fsis, fos, 4096, true);
} catch (IOException e) {
    
    
    e.printStackTrace();
}

// 下载2块文件
try {
    
    
    FSDataInputStream fsis = fileSystem.open(new Path("/???/jdk-8u212-linux-x64.tar.gz"));
    File part1 = new File("?:\\part1");
    FileOutputStream fos1 = new FileOutputStream(part1);

    int bufferSize = 4096; // 缓冲大小
    byte[] buffer = new byte[bufferSize];
    long blockSize = 128 * 1024 * 1024; // 128MB -- 块大小
    for (int offset = 0; offset < blockSize; offset += bufferSize) {
    
    
        int read = fsis.read(buffer, 0, bufferSize);
        fos1.write(buffer, 0, read);
    }
    IOUtils.closeStream(fos1); // 关闭输出流

    File part2 = new File("?:\\part2");
    FileOutputStream fos2 = new FileOutputStream(part2);
    fsis.seek(blockSize);
    IOUtils.copyBytes(fsis, fos2, bufferSize, true); // 复制块1,并关闭输入流和输出流
} catch (IOException e) {
    
    
    e.printStackTrace();
}

【10】重命名目录或文件(移动操作)

try {
    
    
    boolean rename = fileSystem.rename(new Path("/?/?"), new Path("/?/?"));
    System.out.println(rename ? "成功" : "失败");
} catch (IOException e) {
    
    
    e.printStackTrace();
}

【11】判断是否是文件还是目录

try {
    
    
    Path path = new Path("/?/?");
    boolean file = fileSystem.isFile(path);
    boolean directory = fileSystem.isDirectory(path);
    System.out.println(file ? "是文件" : "不是文件!");
    System.out.println(directory ? "是目录" : "不是目录!");
} catch (IOException e) {
    
    
    e.printStackTrace();
}

注意:如果目录或文件不存在时,这2种判断都会返回false

7、机架感知(Rack Awareness)

 Hadoop具有机架感知的功能。 将一个块的分片复制品放在不同的机架上,通过这种块放置的方式,外加使用机架感知来实现容错。 这可以在群集发生网络切换故障或分区时,提供数据的可用性

 就默认分片数为3而言:如果本机DataNode需要存储数据,则Hadoop会将DataNode的副本放到本机上,否则就随机放到其他DataNode上。同时会在另外一机架上的节点放置副本。最后一副本放到同一机架上的不同节点

 这样做可以减少机架间的写入流量,从而提高写入效率。因为机架故障的可能性远小于节点故障的可能性,所以这种策略不会影响数据的可靠性和可用性。此外也减少了读取数据时使用的聚合网络带宽,因为块放到了2个唯一的机架上,而不是3个。三分之一的副本存放到一个节点上,三分之二的副本存放到一个机架上。这种策略可以提高写入性能,而不会影响数据的可靠性或可读性

8、文件上传和文件下载的工作流程

【1】文件上传

  1. 客户端请求NameNode,是否可以进行文件的上传
  2. 当客户端收到可以上传的响应后,依次顺序上传block0(0 ~ 128MB),block1(128MB ~ 256MB)到DataNode
  3. 获取所有需要上传的DataNode的节点信息,依次请求建立传输通道
  4. 收到DataNode响应,传输通道建立完毕,开始传输数据

【2】文件下载

  1. 客户端请求NameNode,获取block信息
  2. 收到NameNode返回的元数据信息,请求对应的DataNode,获取所需的block数据
  3. 合并所有的block数据为一个完整的数据,最终得到完整数据

9、NameNode和SecondaryNameNode工作机制

【1】NameNode启动时的工作机制

  1. NameNode将镜像文件(FsImage)加载到内存中
  2. NameNode将编辑日志(EditLog)加载到内存中
  3. 保存检查点位置(checkpoint)
  4. 进入安全模式(Safe mode)

这一过程,可以通过NameNode的管理页面来查看,在Startup Progress选项卡页面当中


Elapsed Time: ??? sec, Percent Complete: 100%

Phase Completion Elapsed Time
Loading fsimage /???/dfs/name/current/fsimage_??? ??? KB 100% ??? sec
 inodes (???/???) 100%
 delegation tokens (???/???) 100%
 cache pools (???/???) 100%
Loading edits 100% ??? sec
 /???/dfs/name/current/edits_???-??? ??? MB (???/???) 100%
Saving checkpoint 100% ??? sec
 inodes /???/dfs/name/current/fsimage.ckpt_??? (???/???) 100%
 delegation tokens /???/dfs/name/current/fsimage.ckpt_??? (???/???) 100%
 cache pools /???/dfs/name/current/fsimage.ckpt_??? (???/???) 100%
Safe mode 100% ??? sec
 awaiting reported blocks (???/???) 100%

每一个block占元数据的大小为150字节,由于NameNode在启动时,会将这些都加载到内存当中,而内存是有上限的。所以这就是Hadoop不宜存储过多小文件的原因,它会占用大量的内存空间

【2】SecondaryNameNode运行时的工作机制

  1. 请求NameNode是否需要检查点(Checkpoint)
  2. 如果需要Checkpoint,则执行
  3. 复制NameNode的编辑日志(edits),包括已经执行完毕的(例如:edits_0000000000000000001-0000000000000000002)以及正在执行的(例如:edits_inprogress_0000000000000010453)
  4. 加载到内存中,合并生成新的镜像文件(fsimage.checkpoint)
  5. 复制镜像文件(fsimage.checkpoint)到NameNode

 将编辑日志(EditLog)中的所有事务保存到镜像文件(FsImage)里,并加载到内存中,然后结转旧的编辑日志(EditLog),这个过程称为检查点(checkpoint)

 触发SecondaryNameNode请求NameNode查看是否需要执行检查点(checkpoint),是由2个参数配置来决定的,在etc/hadoop/hdfs-site.xml

<!-- 执行检查点的周期,单位:秒。默认为1小时 -->
<property>
    <name>dfs.namenode.checkpoint.period</name>
    <value>3600</value>
</property>
<!-- 执行编辑日志的事务次数。默认为一百万次 -->
<property>
    <name>dfs.namenode.checkpoint.txns</name>
    <value>1000000</value>
</property>

 可以通过前端管理页面来查看SecondaryNameNode的概览,链接是:http://SecondaryNameNode服务主机的IP:50090

 包括了:Hadoop的版本(Version),NameNode的服务地址(NameNode Address),启动时间(Started),上次检查点时间(Last Checkpoint)以及检查点周期(Checkpoint Period)和HDFS事务执行次数上限(Checkpoint Transactions)

 注意:hadoop-2.9.2版本的SecondaryNameNode管理页面JS有一个名为moment的function未定义,所以在加载页面时不会展示数据。解决办法是打开浏览器的开发者工具里的Sources选项卡,修改moment函数

'date_tostring' : function (v) {
    
    
    // return moment(Number(v)).format('ddd MMM DD HH:mm:ss ZZ YYYY');
    return new Date(v); // 直接新建时间对象并返回即可
},

 在dfs-dust.js:61处打断点,修改并保存JS,放开断点即可

【3】NameNode运行时的工作机制

 接收SecondaryNameNode发来的镜像文件(fsimage.checkpoint),并重命名为fsimage

10、编辑日志和镜像文件

【1】概念

 EditLog文件位于??/dfs/name/current/目录里,以edits_开头。它保存了HDFS的所有更新操作的记录的序列化信息

 seen_txid文件保存了正在执行过程中的EditLog文件(edits_inprogress_???)的事务ID

 FsImage文件位于??/dfs/name/current/目录里,以fsimage_开头。它保存了HDFS的所有目录和文件的idnode的序列化信息,是HDFS元数据的检查点(checkpoint)的持久化形态

 VERSION文件,保存了NameNode的唯一标识(namespaceID),全局唯一的集群ID(clusterID),以及存储形态(storageType=NAME_NODE)

 SecondaryNameNode和NameNode相比,其???/dfs/namesecondary/current/目录里,仅仅少了正在执行过程中的EditLog文件(edits_inprogress_???)和seen_txid文件。这样设计是为了方便当NameNode节点故障时,并且没有及时备份数据,可以通过SecondaryNameNode来恢复NameNode

【2】反序列化EditLog和FsImage

 可以反序列化编辑日志,使用命令:bin/hdfs oev -p 反序列化的文件类型 -i 编辑日志路径 -o 反序列化后的文件路径

 可选的反序列化的文件类型有:binary (native binary format that Hadoop uses), xml (default, XML format), stats (prints statistics about edits file)。可以看到默认是XML类型

 反序列化镜像文件,使用命令:bin/hdfs oiv -p 反序列化的文件类型 -i 镜像文件路径 -o 反序列化后的文件路径

 可选的反序列化的文件类型有:(XML|FileDistribution|ReverseXML|Web|Delimited) The default is Web。可以看到默认是Web类型。推荐使用XML

【3】反序列化后的EditLog

<?xml version="1.0" encoding="UTF-8"?>
<EDITS>
  <!-- EditLog版本号 -->
  <EDITS_VERSION>-63</EDITS_VERSION>
  <RECORD>
    <!-- 操作类型:开始记录EditLog -->
    <OPCODE>OP_START_LOG_SEGMENT</OPCODE>
    <DATA>
      <!-- 事务ID -->
      <TXID>1</TXID>
    </DATA>
  </RECORD>
  <RECORD>
    <!-- 操作类型:结束记录EditLog -->
    <OPCODE>OP_END_LOG_SEGMENT</OPCODE>
    <DATA>
      <TXID>2</TXID>
    </DATA>
  </RECORD>
<!-- 另外一个EditLog -->
  <RECORD>
    <!-- 操作类型:创建目录 -->
    <OPCODE>OP_MKDIR</OPCODE>
    <DATA>
      <TXID>4</TXID>
      <!-- 文件大小:0,目录 -->
      <LENGTH>0</LENGTH>
      <!-- inodeID -->
      <INODEID>16386</INODEID>
      <!-- 路径 -->
      <PATH>/tmp</PATH>
      <!-- 创建时间戳 -->
      <TIMESTAMP>1565635741266</TIMESTAMP>
      <!-- 权限 -->
      <PERMISSION_STATUS>
        <!-- 所有者 -->
        <USERNAME>hadoop</USERNAME>
        <!-- 所属组 -->
        <GROUPNAME>supergroup</GROUPNAME>
        <MODE>504</MODE>
      </PERMISSION_STATUS>
    </DATA>
  </RECORD>
  <RECORD>
    <!-- 操作类型:新建文件 -->
    <OPCODE>OP_ADD</OPCODE>
    <DATA>
      <TXID>10315</TXID>
      <LENGTH>0</LENGTH>
      <INODEID>18322</INODEID>
      <PATH>/???/_temporary/???/_temporary/attempt_1565637203418_0001_r_000000_0/part-r-00000</PATH>
      <!-- 分片数 -->
      <REPLICATION>4</REPLICATION>
      <MTIME>1565637361625</MTIME>
      <ATIME>1565637361625</ATIME>
      <!-- 块大小:128MB -->
      <BLOCKSIZE>134217728</BLOCKSIZE>    
      <!-- 客户端名称 -->
       <CLIENT_NAME>DFSClient_attempt_1565637203418_0001_r_000000_0_950599497_1</CLIENT_NAME>
      <!-- 客户端IP -->
      <CLIENT_MACHINE>???.???.???.???</CLIENT_MACHINE>
      <OVERWRITE>false</OVERWRITE>
      <PERMISSION_STATUS>
        <USERNAME>hadoop</USERNAME>
        <GROUPNAME>supergroup</GROUPNAME>
        <MODE>420</MODE>
      </PERMISSION_STATUS>
      <RPC_CLIENTID>fb6e1e9c-f8a4-4428-a6f0-12304607e6c1</RPC_CLIENTID>
      <RPC_CALLID>4</RPC_CALLID>
    </DATA>
  </RECORD>
  <RECORD>
    <!-- 操作类型:分配块ID -->
    <OPCODE>OP_ALLOCATE_BLOCK_ID</OPCODE>
    <DATA>
      <TXID>10316</TXID>
      <BLOCK_ID>1073743480</BLOCK_ID>
    </DATA>
  </RECORD>
  <RECORD>
    <!-- 操作类型:添加块 -->
    <OPCODE>OP_ADD_BLOCK</OPCODE>
    <DATA>
      <TXID>10318</TXID>
      <PATH>/???/_temporary/???/_temporary/attempt_1565637203418_0001_r_000000_0/part-r-00000</PATH>
      <BLOCK>
        <BLOCK_ID>1073743480</BLOCK_ID>
        <NUM_BYTES>0</NUM_BYTES>
        <GENSTAMP>2656</GENSTAMP>
      </BLOCK>
      <RPC_CLIENTID></RPC_CLIENTID>
      <RPC_CALLID>-2</RPC_CALLID>
    </DATA>
  </RECORD>
  <RECORD>
    <!-- 操作类型:删除临时目录 -->
    <OPCODE>OP_DELETE</OPCODE>
    <DATA>
      <TXID>10324</TXID>
      <LENGTH>0</LENGTH>
      <PATH>/???/_temporary</PATH>
      <TIMESTAMP>1565637361871</TIMESTAMP>
      <RPC_CLIENTID>4e4b34de-f566-4b67-a350-e014ebc27e39</RPC_CLIENTID>
      <RPC_CALLID>39</RPC_CALLID>
    </DATA>
  </RECORD>
  </RECORD>
  <RECORD>
    <!-- 操作类型:重命名(执行MapReduce任务) -->
    <OPCODE>OP_RENAME</OPCODE>
    <DATA>
      <TXID>10354</TXID>
      <LENGTH>0</LENGTH>
      <SRC>/tmp/hadoop-yarn/staging/history/done_intermediate/hadoop/job_1565637203418_0001_conf.xml</SRC>
      <DST>/tmp/hadoop-yarn/staging/history/done/2019/08/13/000000/job_1565637203418_0001_conf.xml</DST>
      <TIMESTAMP>1565637363513</TIMESTAMP>
      <OPTIONS>TO_TRASH</OPTIONS>
      <RPC_CLIENTID>696288b7-d3ff-4bf2-a17b-5dc108925fc9</RPC_CLIENTID>
      <RPC_CALLID>20</RPC_CALLID>
    </DATA>
  </RECORD>
<!-- 另外一个EditLog -->
  <RECORD>
    <!-- 操作类型:重命名(上传文件) -->
    <OPCODE>OP_RENAME_OLD</OPCODE>
    <DATA>
      <TXID>25</TXID>
      <LENGTH>0</LENGTH>
      <SRC>/???/???._COPYING_</SRC>
      <DST>/???/???</DST>
      <TIMESTAMP>1566211667239</TIMESTAMP>
      <RPC_CLIENTID>a3bffd43-b49b-4c0b-b4d1-394feb9fd9a2</RPC_CLIENTID>
      <RPC_CALLID>8</RPC_CALLID>
    </DATA>
  </RECORD>
</EDITS>

【4】反序列化后的FsImage

<?xml version="1.0"?>
<fsimage>
    <!-- 版本信息 -->
    <version>
        <layoutVersion>-63</layoutVersion>
        <onDiskVersion>1</onDiskVersion>
        <oivRevision>826afbeae31ca687bc2f8471dc841b66ed2c6704</oivRevision>
    </version>
    <!-- NameNode信息 -->
    <NameSection>
        <!-- 对应VERSION文件里的namespaceID -->
        <namespaceId>598335362</namespaceId>
        <genstampV1>1000</genstampV1>
        <genstampV2>2670</genstampV2>
        <genstampV1Limit>0</genstampV1Limit>
        <lastAllocatedBlockId>1073743492</lastAllocatedBlockId>
        <txid>10455</txid>
    </NameSection>
    <!-- 目录和文件信息 -->
    <INodeSection>
        <!-- 最后一个InodeId -->
        <lastInodeId>18346</lastInodeId>
        <numInodes>42</numInodes>
        <inode>
            <id>16385</id>
            <!-- 这是个目录 -->
            <type>DIRECTORY</type>
            <!-- /:根路径 -->
            <name/>
            <mtime>1566131716332</mtime>
            <!-- 权限,所有者:所属组:0权限信息 -->
            <permission>hadoop:supergroup:0755</permission>
            <nsquota>9223372036854775807</nsquota>
            <dsquota>-1</dsquota>
        </inode>
        <inode>
            <id>16386</id>
            <type>DIRECTORY</type>
            <!-- 目录名称 -->
            <name>tmp</name>
            <mtime>1565636758639</mtime>
            <permission>hadoop:supergroup:0770</permission>
            <nsquota>-1</nsquota>
            <dsquota>-1</dsquota>
            </inode>
        <inode>
            <id>18346</id>
            <!-- 这是个文件 -->
            <type>FILE</type>
            <!-- 文件名称 -->
            <name>jdk-8u212-linux-x64.tar.gz</name>
            <!-- 分片数量 -->
            <replication>4</replication>
            <mtime>1566132732553</mtime>
            <atime>1566132726115</atime>
            <preferredBlockSize>134217728</preferredBlockSize>
            <permission>hadoop:supergroup:0644</permission>
            <blocks>
                <block>
                    <id>1073743491</id>
                    <genstamp>2669</genstamp>
                    <numBytes>134217728</numBytes>
                </block>
                <block>
                    <id>1073743492</id>
                    <genstamp>2670</genstamp>
                    <numBytes>60795424</numBytes>
                </block>
            </blocks>
            <storagePolicyId>0</storagePolicyId>
        </inode>
    </INodeSection>
    <INodeReferenceSection/>
    <!-- 快照信息 -->
    <SnapshotSection>
        <snapshotCounter>0</snapshotCounter>
        <numSnapshots>0</numSnapshots>
    </SnapshotSection>
    <!-- 目录的父子关系 -->
    <INodeDirectorySection>
        <directory>
            <parent>16385</parent>
            <child>18340</child>
            <child>16392</child>
            <child>16386</child>
        </directory>
        <directory>
            <parent>16386</parent>
            <child>16387</child>
            <child>18243</child>
        </directory>
    </INodeDirectorySection>
    <FileUnderConstructionSection/>
    <SecretManagerSection>
        <currentId>0</currentId>
        <tokenSequenceNumber>0</tokenSequenceNumber>
        <numDelegationKeys>0</numDelegationKeys>
        <numTokens>0</numTokens>
    </SecretManagerSection>
    <CacheManagerSection>
        <nextDirectiveId>1</nextDirectiveId>
        <numDirectives>0</numDirectives>
        <numPools>0</numPools>
    </CacheManagerSection>
</fsimage>

11、滚动编辑日志

 使用命令bin/hdfs dfsadmin -rollEdits,这样会结转当前正在执行中的EditLog(edits_???-???),并生成一个新的正在执行过程中的EditLog(edits_inprogress_???

Successfully rolled edit logs.
New segment starts at txid ???

12、通过SecondaryNameNode来恢复NameNode

【1】手动方式

  1. 模拟NameNode故障,jps查看NameNode的进程号,通过命令kill -9 NameNode的进程ID强制停止NameNode
  2. 首先删除???/dfs/name目录里的所有文件
  3. 使用scp复制SecondaryNameNode的???/dfs/namesecondary目录里的所有文件到NameNode的???/dfs/name目录里,并删除复制后的name目录里的in_use.lock文件
  4. 启动NameNode,sbin/hadoop-daemon.sh start namenode
  5. 查看是否可以访问NameNode的前端管理页面,并测试HDFS的文件上传和文件下载

【2】命令方式

  1. 模拟NameNode故障,jps查看NameNode的进程号,通过命令kill -9 NameNode的进程ID强制停止NameNode

  2. 修改NameNode节点的etc/hadoop/hdfs-site.xml文件

    <!-- 修改执行检查点的周期为2分钟,这是为了保证能够及时同步SecondaryNameNode的数据。默认为1小时 -->
    <property>
        <name>dfs.namenode.checkpoint.period</name>
        <value>120</value>
    </property>
    <!-- 指定NameNode的name目录的路径。默认为file://${hadoop.tmp.dir}/dfs/name -->
    <property>
        <name>dfs.namenode.name.dir</name>
        <value>/???/dfs/name</value>
    </property>
    
  3. 使用scp复制SecondaryNameNode的???/dfs/namesecondary目录到NameNode的???/dfs目录下,和???/dfs/name平级即可。并删除复制后的namesecondary目录里的in_use.lock文件

  4. 删除???/dfs/name目录里的所有文件

  5. 执行导入检查点数据命令,bin/hdfs namenode -importCheckpoint

    执行命令后,会发现NameNode被启动起来。命令执行过程较长,如果此时访问NameNode的前端管理页面,会发现提示信息:Upgrade in progress. Not yet finalized.

    当看到process的信息后,就可以Ctrl + C停止命令了

  6. 启动NameNode,sbin/hadoop-daemon.sh start namenode

  7. 查看是否可以访问NameNode的前端管理页面,并测试HDFS的文件上传和文件下载

13、安全模式(Safemode)

【1】概念

 当NameNode在刚启动时,会进入安全模式,此时HDFS对于客户端来说是只读的

 在系统正常运行期间,NameNode会在内存中保存所有的块位置的映射信息。而在安全模式下,每个DataNode会向NameNode发送最新的块列表信息。NameNode了解的块位置信息越多,它可越高效地运行文件系统

一旦满足最小副本条件,NameNode会在30秒后退出安全模式。最小副本条件是指,在整个文件系统当中,99.9%的块满足最小副本级别,最小副本级别可以通过etc/hadoop/hdfs-site.xmldfs.replication.min参数来配置,默认值为1

 启动一个刚格式化的HDFS集群时,不会进入安全模式,是因为此时还没有任何块信息

【2】相关命令

  • 查看当前是否在安全模式当中

    bin/hdfs dfsadmin -safemode get

  • 进入安全模式

    bin/hdfs dfsadmin -safemode enter

  • 退出安全模式

    bin/hdfs dfsadmin -safemode leave

  • 等待安全模式退出

    bin/hdfs dfsadmin -safemode wait

#!/bin/bash
bin/hdfs dfsadmin -safemode wait
echo "退出安全模式!"
# 执行HDFS文件操作
# bin/hadoop fs -???

14、NameNode多目录配置

 在etc/hadoop/hdfs-site.xml中配置

<!-- 指定NameNode的name目录的路径,多个目录之间用逗号隔开。默认为file://${hadoop.tmp.dir}/dfs/name -->
<property>
    <name>dfs.namenode.name.dir</name>
    <value>file://${hadoop.tmp.dir}/dfs/name1,file://${hadoop.tmp.dir}/dfs/name2</value>
</property>

注意:配置此项后,再次启动集群时需要格式化NameNode

15、DataNode工作机制

  1. DataNode启动后,向NameNode发起注册
  2. 注册成功后,每隔1个小时上传一次所有块的位置信息
  3. NameNode每隔3秒钟(可配置),执行一次心跳检测,DataNode接收到心跳检测并返回给NameNode发来的命令信息
  4. 如果超过10分钟(可配置)[外加30秒],NameNode没有收到DataNode传来的心跳检测信息,则认为该节点不可用

 DataNode不同于NameNode,其无需进行格式化,在初始化过程中,创建好数据目录

 DataNode保存了数据,数据长度(文件大小),校验和(CRC),以及时间戳等信息。这些位于???/dfs/data/current目录当中

 DataNode校验和使用CRC算法(循环冗余校验)。对原始数据进行CRC的校验计算,然后和传输过来的CRC校验位进行对比,查看结果是否一致

 心跳检测周期和心跳超时时间,可以在etc/hadoop/hdfs-site.xml中来配置

<!-- 心跳检测DataNode的超时时间,单位:毫秒。默认为300秒(5分钟) -->
<property>
    <name>dfs.namenode.heartbeat.recheck-interval</name>
    <value>300000</value>
</property>
<!-- DataNode的心跳检测间隔,单位:秒。默认为3秒 -->
<property>
    <name>dfs.heartbeat.interval</name>
    <value>3</value>
</property>

心跳超时时间 = 2 * dfs.namenode.heartbeat.recheck-interval + 10 * dfs.heartbeat.interval

 所以默认心跳超时时间是10分钟

 一旦NameNode将最近没有发送heartbeat消息的DataNode标记为已死亡,就不会再将新的IO请求转发给它,并且对于HDFS来说,它是不可用的。这就有可能降低某些block的分片数量(replication factor)

16、DataNode多目录配置

 在etc/hadoop/hdfs-site.xml中配置

<!-- 指定NameNode的name目录的路径,多个目录之间用逗号隔开。默认为file://${hadoop.tmp.dir}/dfs/data -->
<property>
    <name>dfs.datanode.data.dir</name>
    <value>file://${hadoop.tmp.dir}/dfs/data1,file://${hadoop.tmp.dir}/dfs/data2</value>
</property>

注意:配置此项后,再次启动集群时需要格式化NameNode

17、集群节点的动态增加和动态移除

【1】动态新增节点

  1. 准备好新的机器,配置好静态IP,host和防火墙,以及Java运行环境和环境变量,并新建执行Hadoop的用户

  2. 使用scp命令复制NameNode服务节点的Hadoop目录到新的机器上

  3. 配置好其他节点的host

  4. 使用命令ssh-copy-id 用户名@新机器host,为NameNode节点和ResourceManager节点,创建新的机器的免密登录

  5. ① 在所有节点的etc/hadoop目录下,创建名为dfs.hosts的文件,并添加所有节点的IP到文件中。每个IP占一行,不允许有空格

    ② 在所有节点的etc/hadoop/hdfs-site.xml中,修改配置

    <!-- 修改 -->
    <property>
        <!-- 配置副本数 -->
        <name>dfs.replication</name>
        <value>增加机器后的DataNode的数量</value>
    </property>
    <!-- 新增 -->
    <property>
        <!-- 指定可以连接到NameNode的主机列表。如果该值为空,则允许所有主机。默认为空 -->
        <name>dfs.hosts</name>
        <value>/???/可以连接到NameNode的主机列表文件</value>
    </property>
    

    ③ 在所有节点的etc/hadoop/slaves中,将新机器的IP添加到里面

  6. 在NameNode节点机器上,执行命令bin/hdfs dfsadmin -refreshNodes,刷新DataNode

  7. 在ResourceManager节点机器上,执行命令bin/yarn rmadmin -refreshNodes,刷新NodeManager

  8. 在NameNode节点机器上,执行命令sbin/start-balancer.sh,实现集群数据的平衡(Blocks)

  9. 此时查看NameNode的前端管理页面,在Datanodes选项页面里,应该新增了一个DataNode节点,其Admin State为Dead

  10. 到新机器上,分别执行命令sbin/hadoop-daemon.sh start datanode启动DataNode,和命令sbin/yarn-daemon.sh start nodemanager启动NodeManager

  11. 再回到NameNode的前端管理页面,刷新可以看到,新节点的Admin State为In Service

  12. 上传文件和下载文件,进行测试

【2】动态移除节点

  1. ① 在所有节点的etc/hadoop目录下,创建名为dfs.hosts.exclude的文件,并添加要移除的节点的IP到文件中。每个IP占一行,不允许有空格

    ② 在所有节点的etc/hadoop/hdfs-site.xml中,修改配置

    <!-- 修改 -->
    <property>
        <!-- 配置副本数 -->
        <name>dfs.replication</name>
        <value>移除机器后的DataNode的数量</value>
    </property>
    <!-- 新增 -->
    <property>
        <!-- 指定不允许连接到NameNode的主机列表。如果该值为空,则不排除任何主机。默认为空 -->
        <name>dfs.hosts.exclude</name>
        <value>/???/不允许连接到NameNode的主机列表文件</value>
    </property>
    

    ③ 在所有节点的etc/hadoop/slaves中,将准备移除的机器的IP删除

  2. 在NameNode节点机器上,执行命令bin/hdfs dfsadmin -refreshNodes,刷新DataNode

  3. 在ResourceManager节点机器上,执行命令bin/yarn rmadmin -refreshNodes,刷新NodeManager

  4. 在NameNode节点机器上,执行命令sbin/start-balancer.sh,实现集群数据的平衡(Blocks)

  5. 此时查看NameNode的前端管理页面,在Datanodes选项页面里,那些准备移除的节点机器的Admin State应该为Decommission In Progress。等待数据传输给其他的DataNode完毕,状态变为Decommission

  6. 在准备退役的机器上,分别执行命令sbin/yarn-daemon.sh stop nodemanager停止NodeManager,和命令sbin/hadoop-daemon.sh stop datanode停止DataNode

  7. 如果在etc/hadoop/hdfs-site.xml中配置了dfs.hosts,则还应删掉对应的退役机器的IP,并执行命令重新刷新DataNode和NodeManager

18、集群间的数据拷贝

 使用命令,bin/hadoop distcp hdfs://源服务机器IP:9000/?/? hdfs://目标服务机器IP:9000/?/?

 注意:数据拷贝的时候,会执行一个MapReduce任务,所以必须确保2个集群的相关资源(YARN等)都在正常运行中

19、数据的归档

 由于NameNode启动时会加载所有的元数据到内存当中,所以过多的小文件,会占用大量的内存资源。我们可以把这些小文件归档(压缩)起来,这样就能节省很多的内存空间

 数据归档本质上是一个MapReduce程序(确保YARN相关资源已启动)

 相关命令:

  1. 创建归档文件,bin/hadoop archive -archiveName 归档文件名.har -p 要归档的目录 归档文件输出目录

  2. 查看归档文件,bin/hadoop fs -ls -R har:///???/???.har

  3. 提取归档文件,其实就是复制归档文件里的内容,bin/hadoop fs -cp har:///???/归档文件名.har/归档文件 /目标路径

    注意:只能复制归档文件的内容,不能移动(mv),否则会报错:mv: `har:///???/???.har/???': Does not match target filesystem

20、快照

 快照相当于是对目录进行一个备份,它并不会立即复制所有的文件,而是先创建一个索引文件指向目标文件。当有新的文件写入时,它才会产生新的文件

 相关命令:

  1. 开启指定目录的快照功能,bin/hdfs dfsadmin -allowSnapshot /指定目录

  2. 创建指定目录的快照,bin/hdfs dfs -createSnapshot /指定目录 快照文件名称

    快照文件名称可以省略,默认的名称为/指定目录/.snapshot/s日期-时间.毫秒

  3. 重命名快照,bin/hdfs dfs -renameSnapshot /快照目录 旧名称 新名称

    注意:系统默认创建的快照名称是无法重命名的,会报错:renameSnapshot: Modification on a read-only snapshot is disallowed

  4. 展示当前用户下的所有的已开启快照功能的目录,bin/hdfs lsSnapshottableDir

  5. 比较两个快照目录的不同,bin/hdfs snapshotDiff /开启快照功能的目录 快照名称1 快照名称2

    快照名称可以使用.来代替,表示当前开启了快照功能的目录

    注意:比较结果是用右边的快照和左边的快照相比较

  6. 删除快照,bin/hdfs dfs -deleteSnapshot /开启快照功能的快照目录 快照名称

    注意快照目录和快照名称之间的空格

  7. 禁用指定目录的快照功能,bin/hdfs dfsadmin -disallowSnapshot /指定目录

    注意:禁用快照功能前,需要先删除当前目录的所有快照,否则报错:disallowSnapshot: The directory /???/??? has snapshot(s). Please redo the operation after removing all the snapshots.

 使用快照进行恢复,则使用bin/hadoop fs -cp /快照目录里的文件 /指定目录。注意不能使用移动(mv)命令,因为快照文件是只读的

21、回收站

 回收站功能默认是关闭的,需要在etc/hadoop/core-site.xml配置文件中,设置2个参数来开启它

<!-- 回收站内的文件的有效期,单位:分钟。默认为0,表示禁用回收站功能 -->
<property>
    <name>fs.trash.interval</name>
    <value>0</value>
</property>
<!--
检查回收站的周期,单位:分钟。该值要小于等于fs.trash.interval。如果为0,则设置为fs.trash.interval的值。
每当回收站运行的时候,它都会创建一个新的检查点,用于删除超过fs.trash.interval时间(文件有效期)之后的文件
-->
<property>
    <name>fs.trash.checkpoint.interval</name>
    <value>0</value>
</property>

 同时还要注意:默认拥有查看回收站的权限的用户名是dr.who,需要修改它为操作Hadoop集群的用户。否则访问NameNode的前端管理页面时会报错:Permission denied: user=dr.who, access=READ_EXECUTE, inode="/user":???:supergroup:drwx------

 在etc/hadoop/core-site.xml

<!-- 拥有查看HDFS Web页面(NameNode前端管理页面)的权限的用户名 -->
<property>
    <name>hadoop.http.staticuser.user</name>
    <value>dr.who</value>
</property>

 删除的文件目录,位于回收站的目录/user/执行Hadoop的用户名/.Trash/Current/???

 恢复回收站里的文件的方法是,将回收站里的文件移动出来,使用命令bin/hadoop fs -mv /回收站目录 /恢复目录。注意,如果移动文件夹,可能因为权限问题而无法查看

 删除回收站目录,可以使用命令bin/hadoop fs -rm -r -skipTrash /user/???。加上-skipTrash选项

猜你喜欢

转载自blog.csdn.net/adsl624153/article/details/99369931