大数据之Hadoop——HDFS

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 优点

  1. 高容错性:数据自动保存多个副本,如果某个副本丢失可以其它节点自动恢复
  2. 适合处理大数据:能够处理TB、PB级别的数据,能处理百万规模以上的文件数量。
  3. 低成本:可构建在廉价机器上,通过多副本机制提高可靠性

1.2.2 缺点

  1. 不合适低延时数据访问,比如毫秒级的存储数据。
  2. 无法高效的对大量小文件进行存储。
    (1)存储大量小文件的话,它会占用Namenode大量内存来存储文件目录和块信息,而Namenode的内存是有限的。
    (2)小文件存储的寻址时间会超过读取时间,它违反了HDFS的设计目标。
  3. 不支持并发写入、文件随机修改。
    (1)一个文件只能有一个写,不允许多个线程同时写。
    (2)仅支持数据append(追加),不支持文件的随机修改,因为HDFS没有提供对文件的在线寻址(打开)功能,文件以块形式存储,修改了一个块中的内容,就会影响当前块之后所有的块,效率低。

1.3 HDFS组成架构

Namenode(nn): 就是Master,它是一个主管、管理者

  1. 管理HDFS的名称空间
  2. 配置副本策略
  3. 管理数据块(Block)映射信息
  4. 处理客户端读写请求

Datanode: 就是Slave,Namenode下达命令,Datanode执行实际的操作

  1. 存储实际的数据块
  2. 执行数据块的读/写操作

Client: 客户端

  1. 文件切分,文件上传HDFS的时候,Client将文件切分成一个个Block,然后进行上传
  2. 与Namenode交互,获取文件的位置信息
  3. 与Datanode交互,读取或写入数据
  4. Client提供一些命令来管理HDFS,比如Namenode格式化
  5. Client可以通过一些命令来访问HDFS,比如对HDFS增删改查操作

Secondary Namenode: 并非Namenode的热备,当Namenode挂掉的时候,它并不能马上替换Namenode并提供服务

  1. 辅助Namenode,分担其工作量,比如定期合并Fsimage和Edits,并推送给Namenode
  2. 紧急情况下,可辅助恢复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的大小不能设置太小或太大:

  1. HDFS的块设置太小,会增加寻址时间
  2. HDFS的块设置太大,从磁盘传输数据的时间会明显大于寻址时间

1.4.2 文件块属性

  • length: 块的实际大小
  • offset: 块的便宜量,这个块从文件的哪部分开始保存数据

1.5 副本

副本数的概念指的是最大副本数,具体存放几个副本需要参考DN节点的数量,每个DN节点最多只能存储一个副本。

2 HDFS的Shell操作

shell操作命令:

  • bin/hdfs dfs command params:只操作分布式的HDFS
  • bin/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写数据流程

在这里插入图片描述

  1. 客户端创建DistributedFileSystem(分布式文件系统)对象,通过该对象向NameNode请求上传文件。
  2. NameNode检查父目录和目标文件是否存在。
  3. NameNode返回是否可以上传文件。
  4. 客户端通过FSDataOutputStream上传数据,FSDataOutputStream根据客户端参数将文件分块(默认块大小128M)。
  5. 客户端请求第一个 Block上传到哪几个DataNode服务器上。
  6. NameNode根据客户端上传文件的副本数(默认为3),根据机架感知策略选取指定数量的DataNode节点返回,并将相关信息存到元数据中。
  7. 客户端接收到NameNode返回的DataNode服务器列表。
  8. 客户端根据返回的DN节点,请求建立传输通道,客户端向最近(网络距离最近)的DN节点发起通道建立请求,由这个DN节点依次向通道中的(距离当前DN距离最近)下一个节点发送建立通道请求,各个节点发送响应 ,通道建立成功。
  9. 客户端每读取64K的数据,封装为一个packet(数据包,传输的基本单位),将packet发送到通道的下一个节点,通道中的节点收到packet之后,落盘(检验)存储,将packet发送到通道的下一个节点,每个节点在收到packet后,向客户端发送ack确认消息。
  10. 当一个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读数据流程

在这里插入图片描述

  1. 客户端创建DistributedFileSystem(分布式文件系统)对象,通过该对象向NameNode请求读取文件。
  2. NameNode查询元数据,查找目标文件是否存在。
  3. NameNode返回目标文件Block所在的DataNode地址等。
  4. 挑选一台DataNode(就近原则,然后随机)服务器,请求读取数据。
  5. DataNode开始传输数据给客户端(从磁盘里面读取数据输入流,以packet为单位来做校验)。
  6. 客户端以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的合并。

在这里插入图片描述

  1. 第一阶段:NameNode启动
    (1)第一次启动NameNode格式化后,创建Fsimage和Edits文件。如果不是第一次启动,直接加载编辑日志(edits_inprogress)和镜像文件(fsimage)到内存合并得到最新的元数据,并将元数据持久化到磁盘生成新的fsimage文件。
    (2)客户端对元数据进行增删改的请求。
    (3)NameNode记录操作日志,更新滚动日志。
    (4)NameNode在内存中对元数据进行增删改。
  2. 第二阶段: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

  1. fsimage:HDFS文件系统元数据的一个永久性的检查点,其中包含HDFS文件系统的所有目录和文件inode的序列化信息。默认保留最近的二个fsimage。
  2. edits:存放HDFS文件系统的所有更新操作的路径,文件系统客户端执行的所有写操作首先会被记录到edits文件中。
  3. seen_txid:保存的是一个数字,即edits_inprogress文件最后的序号
  4. 每次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 总结

  1. NN的作用:

     ①NN保存HDFS上所有文件的元数据
     ②NN负责接受客户端的请求
     ③NN负责接受DN上报的信息,
     ④和DN保持心跳,给DN分配任务(维护副本数)
    
  2. 元数据的存储

     元数据存储在fsiamge文件+edits文件中!
     fsimage(元数据的快照文件)
     edits(记录所有写操作的文件)	
     
     NN负责集群中所有客户端的请求和所有DN的请求。在一个集群中,通常NN需要一个高配置,保证NN
     可以及时处理客户端或DN的请求,一旦NN无法及时处理请求,HDFS就已经瘫痪!
    
  3. 存储的元数据分为两种

     ①inodes :  记录文件的属性和文件由哪些块组成,记录到edits和fsimage文件中
     ②块的位置映射信息:  由NN启动后,接收DN的上报,动态生成!
    
  4. NN的启动过程

     ①先加载fsimage_000000xx文件
     ②将fsimage文件xx之后的edits文件加载
     ③合并生成最新的元数据,记录checkpoint,如果满足要求,执行saveNamespace操作,不满足等满足后执行saveNamespace操作必须在安全模式执行
     ④自动进入安全模式,等待DN上报块,DN上报的块的最小副本数总和 / 块的总数  > 0.999,自动在30s离开安全模式!安全模式只能有限读,不能写!
    

6 Datanode

6.1 工作机制

datanode

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

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	

注意:

  1. 通过浏览器查看节点退役状态为decommission in progress,代表数据节点正在复制块到其它节点,等待退役节点状态为decommissioned,再停止该节点及节点资源管理器。
  2. 如果服务节点数量小于等于副本数是不能退役成功的,需要修改副本数后才能退役。
  3. 不允许白名单和黑名单中同时出现同一个主机名称。

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的内存。
har

#需要启动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/….
注意:

  1. fs.trash.checkpoint.interval <= fs.trash.interval
  2. 通过程序删除的文件不会经过回收站,需要调用Trash对象的moveToTrash()方法才进入回收站。
  3. 过期删除的文件会加上时间戳重新命名。
#删除/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	#恢复快照
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值