04-HDFS(2)

发布时间 2023-06-29 00:21:29作者: tree6x7

1. HDFS Shell CLI

https://hadoop.apache.org/docs/r3.1.3/hadoop-project-dist/hadoop-common/FileSystemShell.html

1.1 基本概念

命令行界面(英语:command-line interface,缩写:CLI)是指用户通过键盘输入指令,计算机接收到指令后,予以执行一种人机交互方式。

Hadoop 提供了文件系统的 Shell 命令行客户端,使用方法如下:

Usage: hdfs [SHELL_OPTIONS] COMMAND [GENERIC_OPTIONS] [COMMAND_OPTIONS]
# ·······································································
SUBCOMMAND: Admin Commands、Client Commands、Daemon Commands
# ·······································································
和文件系统读写使用相关的命令: hadoop fs [generic options]
- HDFS 文件系统的操作命令很多和 Linux 类似,因此学习成本相对较低。
- 可以通过 hadoop fs -help 来查看每个命令的详细用法。

HDFS Shell CLI 支持操作多种文件系统,URI 格式为 scheme://authority/path

对于 HDFS,该 scheme 包括本地文件系统(file:///)、分布式文件系统(hdfs://nn:8020)等。操作的是什么文件系统取决于 URL 中的前缀协议。如果没有指定前缀,则将会读取环境变量中的 fs.defaultFS 属性,以该属性值作为默认文件系统。

# 操作本地文件系统
hdfs dfs -ls file:///  
# 操作 HDFS 分布式文件系统
hdfs dfs -ls hdfs://node1:8020/
# 直接根目录,没有指定协议,将加载读取配置 fs.defaultFS 的值
hdfs dfs -ls /

关于命令 hadoop dfs、hdfs dfs、 hadoop fs 三者区别:

  • hadoop dfs 只能操作 HDFS 文件系统(包括与 Local FS 间的操作),已过时;
  • hdfs dfs 只能操作 HDFS 文件系统相关(包括与 Local FS 间的操作),常用;
  • hadoop fs 可操作任意文件系统,不仅仅是 HDFS 文件系统,使用范围更广!

1.2 常用命令

(1)创建目录

hadoop fs -mkdir [-p] <path> ...
# path 为待创建的目录
# -p 选项的行为与 Unix mkdir -p 非常相似,它会沿着路径创建父目录。

(2)查看指定目录下内容

hadoop fs -ls [-h] [-R] [<path> ...]
# path 指定目录路径
# -h 人性化显示文件大小(单位)
# -R 递归查看指定目录及其子目录

(3)查看全部文件内容

hadoop fs -cat <src> ...
# 读取指定文件全部内容,显示在标准输出控制台。
# 对于大文件内容读取,慎重。

(4)查看文件开头内容

hadoop fs -head <file>
# 查看文件前 1KB 的内容

(5)查看文件结尾内容

hadoop fs -tail [-f] <file>
# 查看文件最后 1KB 的内容
# -f 选择可以动态显示文件中追加的内容

(6)追加数据到 HDFS 文件中

hadoop fs -appendToFile <localsrc> ... <dst>
# 将 localsrc 的内容追加到 dst 文件
# dst 如果不存在则将创建该文件
# 如果 <localSrc> 为 - 则输入为从标准输入中读取

(7)上传文件到指定目录下_1

hadoop fs -put [-f] [-p] <localsrc> ... <dst>
# -f Overwrites the destination if it already exists.
# -p 保留访问和修改时间、所有权和权限
# localsrc 本地文件系统(客户端所在机器)
# dst 目标文件系统(HDFS)

(8)上传文件到指定目录下_2

hadoop fs -moveFromLocal <localsrc> ... <dst>
# 和 -put 功能意义,只不过上传结束后源数据会被删除

(9)数据移动操作

hadoop fs -mv <src> ... <dst>
# 移动文件到指定文件夹下(该命令不能跨文件系统)
# 可以使用该命令移动数据、重命名文件的名称

(10)下载 HDFS 文件

hadoop fs -get [-f] [-p] <src> ... <localdst>
# 下载文件到本地文件系统指定目录,localdst 必须是目录
# -f Overwrites the destination if it already exists.
# -p 保留访问和修改时间、所有权和权限

(11)合并下载 HDFS 文件

hadoop fs -getmerge [-nl] [-skip-empty-file] <src> <localdst>
# 下载多个文件合并到本地文件系统的一个文件中
# -nl 选项表示在每个文件末尾添加换行符

(12)拷贝 HDFS 文件

hadoop fs -cp [-f] <src> ... <dst>
# -f Overwrites the destination if it already exists.

(13)查看 HDFS 磁盘空间

hadoop fs -df [-h] [<path> ...]
# 显示文件系统的容量,可用空间和已用空间

(14)查看 HDFS 文件使用的空间量

hadoop fs -du [-s] [-h] <path> ...
# -s 表示显示指定路径文件长度的汇总摘要,而不是单个文件的摘要。
# -h 表示将以“人类可读”的方式展示文件大小(如果是目录则详细列出每个文件的大小)

(15)修改 HDFS 文件副本个数

hadoop fs -setrep [-R] [-w] <rep> <path> ...
# 修改指定文件的副本个数
# -R 表示递归修改文件夹下及其所有
# -w 客户端是否等待副本修改完毕
# 副本数只是记录在 NameNode 的元数据中,是否真的会有这么多副本,还得看 DataNode 的数量。
# 因为目前只有3台设备,最多也就3个副本,只有节点数的增加到 10 台时,副本数才能达到 10。

若 HDFS 页面操作文件出现如下错误提示,可使用 hadoop fs -chmod -R 777 / 来解决。

2. HDFS Java API

HDFS 在生产应用中主要是 Java 客户端的开发,其核心步骤是从 HDFS 提供的 API 中构造一个 HDFS 的访问客户端对象,然后通过该客户端对象操作(增删改查)HDFS 上的文件。

<dependency>
  <groupId>org.apache.hadoop</groupId>
  <artifactId>hadoop-client</artifactId>
  <version>3.1.3</version>
</dependency>

测试用例:

public class HdfsClient {

    FileSystem fileSystem;

    @Before
    public void init() throws URISyntaxException, IOException, InterruptedException {
        // 连接的集群地址&配置
        URI uri = new URI("hdfs://hadoop102:8020");
        // Configuration 配置对象类,用于加载或设置参数属性
        Configuration config = new Configuration();
        // [ConfigPriority] code > hdfs-site.xml > hdfs-default.xml
        config.set("dfs.replication", "2");
        // 操作用户
        String user = "liujiaqi";
        // 文件系统对象基类。针对不同文件系统有不同具体实现。该类封装了文件系统的相关操作方法。
        fileSystem = FileSystem.get(uri, config, user);
    }
    
    @After
    public void close() throws IOException {
        // 首先判断文件系统实例是否为null 如果不为null 进行关闭
        if (fileSystem != null) {
            try {
                fileSystem.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Test
    public void mkdirTest() throws IOException {
        // 首先判断文件夹是否存在,如果不存在再创建
        if (!fileSystem.exists(new Path("/tree/6x7"))) {
            // 创建文件夹
            fileSystem.mkdirs(new Path("/tree/6x7"));
        }
    }

    @Test
    public void uploadTest() throws IOException {
        // 1. 是否删除本地源文件
        // 2. 是否允许覆盖
        // 3. 源文件路径
        // 4. 目标路径
        fileSystem.copyFromLocalFile(false, true, 
                                     new Path("/Users/tree6x7/Downloads/ljq.jpg"), new Path("/tree"));
    }

    @Test
    public void downloadTest() throws IOException {
        fileSystem.copyToLocalFile(false, new Path("/tree/ljq.png"), new Path("/Users/tree6x7/Desktop"));
    }

    @Test
    public void deleteTest() throws IOException {
        // 删除文件 / 空目录
        fileSystem.delete(new Path("/tree/6x7"), false);
        // (递归)删除非空目录
        fileSystem.delete(new Path("/tree"), true);
    }

    @Test
    public void renameTest() throws IOException {
        // 修改文件名称 | 移动文件 | 目录更名
        fileSystem.rename(new Path(""), new Path(""));
    }

    @Test
    public void fileDetail() throws IOException {
        RemoteIterator<LocatedFileStatus> listFiles = fileSystem.listFiles(new Path("/"), true);
        while (listFiles.hasNext()) {
            LocatedFileStatus fileStatus = listFiles.next();
            System.out.println("=====" + fileStatus.getPath() + "=====");
            System.out.println(fileStatus.getPermission());
            System.out.println(fileStatus.getOwner());
            System.out.println(fileStatus.getGroup());
            System.out.println(fileStatus.getLen());
            System.out.println(fileStatus.getModificationTime());
            System.out.println(fileStatus.getReplication());
            System.out.println(fileStatus.getBlockSize());
            System.out.println(fileStatus.getPath().getName());
            // 获取块信息
            BlockLocation[] blockLocations = fileStatus.getBlockLocations();
            System.out.println(Arrays.toString(blockLocations));
        }
    }

    @Test
    public void judgeIsFile() throws IOException {
        FileStatus[] listStatus = fileSystem.listStatus(new Path("/"));
        for (FileStatus fileStatus : listStatus) {
            if (fileStatus.isFile()) {
                System.out.println("File: " + fileStatus.getPath().getName());
            } else {
                System.out.println("Directory: " + fileStatus.getPath().getName());
            }
        }
    }

}

3. LibHDFS C API

Libhdfs 是用于 Hadoop 的分布式文件系统(HDFS)的基于 JNI 的 C API。它为 HDFSAPI 的一个子集提供了 C API,以操作 HDFS 文件和文件系统。

Libhdfs 是 Hadoop 发行版的一部分,已预编译在 ${HADOOP_HOME}/libhdfs/libhdfs.so 中。

Libhdfs API 是 Hadoop 文件系统API。Libhdfs 的头文件详细描述了每个 API,并在 $Hadoop_hdfs_home/include/hdfs.h。

4. HDFS HTTP API

HDFS 常见的客户端:

(1)Shell Command

HDFS Shell Command 的操作俗称命令行操作。命令类似于 Linux 的 Shell 对文件的操作,如 ls、mkdir、rm 等。

(2)Java API

HDFS Java API 提供了使用 Java 代码操作访问 HDFS 的能力,也是大数据开发中常用的 HDFS 操作方式。核心类:FileSystem、Configuration。

(3)C API LibHDFS

libhdfs 是用于 HDFS 的基于 JNI 的 C API,支持 C 语言客户端以操纵 HDFS 文件和文件系统。libhdfs 是 Hadoop 发行版的一部分,已预编译在 ${HADOOP_HOME}/libhdfs/libhdfs.so 中。


以上不管是什么类型都是操作访问 HDFS 的一种方式,那如果 Hadoop 集群外的一台主机作为客户端访问 HDFS,在没有 Hadoop 和 Java 环境的情况下,如何操作访问呢?

接下来,我们将学习几种基于 HTTP 协议的客户端,HTTP 是跨平台的,它不要求客户端上必须安装 Hadoop,就可以直接操作 HDFS。

4.1 WebHDFS

https://hadoop.apache.org/docs/r3.1.3/hadoop-project-dist/hadoop-hdfs/WebHDFS.html

WebHDFS 提供了访问 HDFS 的 RESTful 接口,并且它是独立于 Hadoop 的版本的,支持 HDFS 的完整 FileSystem/FileContext 接口。它可以让客户端发送 HTTP 请求的方式来操作 HDFS,而无需安装 Hadoop。

WebHDFS 是 HortonWorks 开发的,后捐给了 Apache。在我们经常使用的 HDFS Web UI,它就是基于 WebHDFS 来操作 HDFS 的。

当客户端请求某文件时,WebHDFS 会将其重定向到该资源所在的 DataNode。

WebHDFS 的文件系统 schema 是 webhdfs://。WebHDFS 文件系统 URI 具有以下格式。

webhdfs://<HOST>:<HTTP_PORT>/<PATH>

上面的 WebHDFS URI 对应于下面的 HDFS URI。

hdfs://<HOST>:<RPC_PORT>/<PATH>

在 RESTful API 中,在路径中插入前缀 /webhdfs/v1,并在末尾追加一个查询。因此,对应的 HTTP URL 具有以下格式。

http://<HOST>:<HTTP_PORT>/webhdfs/v1/<PATH>?op=...

HTTP GET

  • OPEN (等同于FileSystem.open)
  • GETFILESTATUS (等同于FileSystem.getFileStatus)
  • LISTSTATUS (等同于FileSystem.listStatus)
  • LISTSTATUS_BATCH (等同于FileSystem.listStatusIterator)
  • GETCONTENTSUMMARY (等同于FileSystem.getContentSummary)
  • GETQUOTAUSAGE (等同于FileSystem.getQuotaUsage)
  • GETFILECHECKSUM (等同于FileSystem.getFileChecksum)
  • GETHOMEDIRECTORY (等同于FileSystem.getHomeDirectory)
  • GETDELEGATIONTOKEN (等同于FileSystem.getDelegationToken)
  • GETTRASHROOT (等同于FileSystem.getTrashRoot)
  • GETXATTRS (等同于FileSystem.getXAttr)
  • GETXATTRS (等同于FileSystem.getXAttrs)
  • GETXATTRS (等同于FileSystem.getXAttrs)
  • LISTXATTRS (等同于FileSystem.listXAttrs)
  • CHECKACCESS (等同于FileSystem.access)
  • GETALLSTORAGEPOLICY (等同于FileSystem.getAllStoragePolicies)
  • GETSTORAGEPOLICY (等同于FileSystem.getStoragePolicy)
  • GETSNAPSHOTDIFF
  • GETSNAPSHOTTABLEDIRECTORYLIST
  • GETECPOLICY (等同于HDFSErasureCoding.getErasureCodingPolicy)
  • GETFILEBLOCKLOCATIONS (等同于FileSystem.getFileBlockLocations)

HTTP PUT

  • CREATE (等同于FileSystem.create)
  • MKDIRS (等同于FileSystem.mkdirs)
  • CREATESYMLINK (等同于FileContext.createSymlink)
  • RENAME (等同于FileSystem.rename)
  • SETREPLICATION (等同于FileSystem.setReplication)
  • SETOWNER (等同于FileSystem.setOwner)
  • SETPERMISSION (等同于FileSystem.setPermission)
  • SETTIMES (等同于FileSystem.setTimes)
  • RENEWDELEGATIONTOKEN (等同于DelegationTokenAuthenticator.renewDelegationToken)
  • CANCELDELEGATIONTOKEN (等同于DelegationTokenAuthenticator.cancelDelegationToken)
  • CREATESNAPSHOT (等同于FileSystem.createSnapshot)
  • RENAMESNAPSHOT (等同于FileSystem.renameSnapshot)
  • SETXATTR (等同于FileSystem.setXAttr)
  • REMOVEXATTR (等同于FileSystem.removeXAttr)
  • SETSTORAGEPOLICY (等同于FileSystem.setStoragePolicy)
  • ENABLEECPOLICY (等同于HDFSErasureCoding.enablePolicy)
  • DISABLEECPOLICY (等同于HDFSErasureCoding.disablePolicy)
  • SETECPOLICY (等同于HDFSErasureCoding.setErasureCodingPolicy)

HTTP POST

  • APPEND (等同于FileSystem.append)
  • CONCAT (等同于FileSystem.concat)
  • TRUNCATE (等同于FileSystem.truncate)
  • UNSETSTORAGEPOLICY (等同于FileSystem.unsetStoragePolicy)
  • UNSETECPOLICY (等同于HDFSErasureCoding.unsetErasureCodingPolicy)

HTTP DELETE

  • DELETE (等同于FileSystem.delete)
  • DELETESNAPSHOT (等同于FileSystem.deleteSnapshot)

4.2 HttpFS

对于文件 CRUD 的操作全部提交给 HttpFS 服务进行中转,然后由 HttpFS 去跟 HDFS 集群交互。HttpFS 本身是 JavaWeb 应用程序,使用内置的 Jetty 服务器对外提供服务。

HttpFS API 的底层通过是映射到 HDFS 的 HTTP RESTful API 调用实现的。

HttpFS 是一个独立于 HDFS 的服务,本质上是一个代理服务,若使用需要手动安装。因为是可以独立部署的,所以可以对 HttpHDFS 设置防火墙,而避免 NameNode 暴露在墙外,对一些安全性要求比较高的系统,HttpHDFS 会更好些。

HttpFS 内置了支持 Hadoop 伪身份验证和 HTTP、SPNEGO Kerberos 和其他可插拔身份验证机制的安全性。它还提供 Hadoop 代理用户支持。HttpFS 可用于在防火墙后面的集群上访问 HDFS 中的数据(HttpFS 服务器充当网关,是允许跨越防火墙进入集群的唯一系统)。

HttpFS 可以使用 HTTP 实用程序(例如 curl 和 wget)和来自 Java 以外的其他语言的 HTTP 库 Perl 来访问 HDFS 中的数据。

HttpFS 可用于在运行不同版本 Hadoop(克服 RPC 版本控制问题)的集群之间传输数据,例如使用 HadoopDiscreCP。

WebHDFS 和 HttpFS 之间区别:

5. NN 元数据管理

5.1 元数据

元数据(Metadata),又称中介数据,为描述数据的数据(data about data),主要是描述数据属性的信息,用来支持如指示存储位置、历史数据、资源查找、文件记录等功能。

NameNode 中的元数据是存储在哪里的?

在 HDFS 中,元数据主要指的是“文件相关的元数据”,由 NameNode 管理维护。从广义的角度来说,因为 NameNode 还需要管理众多 DataNode 节点,因此 DataNode 的位置和健康状态信息也属于元数据。

按存储形式分为「内存元数据」和「元数据文件」两种,分别存在内存和磁盘上。

在 HDFS 中,文件相关元数据具有两种类型:

  1. 【文件自身属性信息】文件名称、权限,修改时间,文件大小,复制因子,数据块大小。
  2. 【文件块位置映射信息】记录文件块和 DataNode 之间的映射信息,即哪个块位于哪个节点上。

a. 内存元数据

为了保证用户操作元数据交互高效、延迟低,NameNode 把所有的元数据都存储在内存中,我们叫做内存元数据。内存中的元数据是最完整的,包括文件自身属性信息、文件块位置映射信息。

但内存的致命问题是:断点数据丢失,数据不会持久化。因此 NameNode 又辅佐了元数据文件来保证元数据的安全完整。

b. 元数据文件

元数据文件有两种:fsimage 内存镜像文件、edits log 编辑日志

(1)fsimage 内存镜像文件

是内存元数据的一个持久化的检查点,但 fsimage 中仅包含 Hadoop 文件系统中文件自身属性相关的元数据信息,但不包含文件块位置的信息。

文件块位置信息只存储在内存中,是由 DataNode 启动加入集群的时候,向 NameNode 进行数据块的汇报得到的,并且后续间断指定时间进行数据块报告。

持久化的动作是一种数据从内存到磁盘的 IO 过程,会对 NameNode 正常服务造成一定的影响,不能频繁的进行持久化。

(2)edits log 编辑日志

为了避免两次持久化之间数据丢失的问题,又设计了 edits log 编辑日志文件。

文件中记录的是 HDFS 所有更改操作(文件创建、删除或修改)的日志,文件系统客户端执行的更改操作首先会被记录到 edits log 文件中。

c. 元数据加载顺序

fsimage 和 edits 文件都是经过序列化的,在 NameNode 启动的时候,它会将 fsimage 文件中的内容加载到内存中,之后再执行 edits 文件中的各项操作(重放),使得内存中的元数据和实际的同步,存在内存中的元数据支持客户端的读操作,也是最完整的元数据。

当客户端对 HDFS 中的文件进行新增或者修改操作,操作记录首先被记入 edits 日志文件中,当客户端操作成功后,相应的元数据会更新到内存元数据中。因为 fsimage 文件一般都很大(GB 级别的很常见),如果所有的更新操作都往 fsimage 文件中添加,这样会导致系统运行的十分缓慢。

HDFS 这种设计实现着手于:一是内存中数据更新、查询快,极大缩短了操作响应时间;二是内存中元数据丢失风险颇高(断电等),因此辅佐元数据镜像文件(fsimage)+ 编辑日志文件(edits)的备份机制进行确保元数据的安全。

NameNode 维护整个文件系统元数据。因此,元数据的准确管理,影响着 HDFS 提供文件存储服务的能力。

5.2 相关文件

a. 存储目录

在 HDFS 首次部署好配置文件之后,并不能马上启动使用,而是先要对文件系统进行格式化操作:hdfs namenode -format

在这里要注意两个概念,一个是 format 之前,HDFS 在物理上还不存在;二就是此处的 format 并不是指传统意义上的本地磁盘格式化,而是一些清除与准备工作,其中就会创建元数据本地存储目录和一些初始化的元数据相关文件。

NameNode 元数据存储目录由参数 dfs.namenode.name.dir 指定,其中的 dfs.namenode.name.dir 是在 hdfs-site.xml 文件中配置的:

<!-- 默认值 -->
<property>
  <name>hadoop.namenode.name.dir</name>
  <value>file://${hadoop.tmp.dir}/dfs/name</value>
</property>

<!-- 手动配置的 -->
<property>
  <name>hadoop.tmp.dir</name>
  <value>/opt/module/hadoop-3.1.3/data</value>
</property>

dfs.namenode.name.dir 属性可以配置多个目录,各个目录存储的文件结构和内容都完全一样,相当于备份,这样做的好处是当其中一个目录损坏了,也不会影响到 Hadoop 的元数据,特别是当其中一个目录是 NFS(网络文件系统 Network File System)之上,即使你这台机器损坏了,元数据也得到保存。

格式化完成之后,将会在 ${dfs.namenode.name.dir}/current 目录下创建如下的文件:

edits_*
edits_inprogress_*
fsimage_*
fsimage_*.md5
seen_txid
VERSION

b. VERSION

$ cat VERSION 
#Sat Apr 15 11:54:44 CST 2023
namespaceID=1651895677
clusterID=CID-241010b9-00c3-4fb9-b2b1-71992fcbdc32
cTime=1680338886133
storageType=NAME_NODE
blockpoolID=BP-13582961-192.168.6.102-1680338886133
layoutVersion=-64

(1)namespaceID/clusterID/blockpoolID

这些都是 HDFS 集群的唯一标识符。标识符被用来防止 DataNodes 意外注册到另一个集群中的 NameNode 上。这些标识在「联邦部署」中特别重要。

联邦模式下,会有多个 NameNode 独立工作。每个 NameNode 提供唯一的命名空间(namespaceID),并管理一组唯一的文件块池(blockpoolID)。clusterID 将整个集群结合在一起作为单个逻辑单元,在集群中的所有节点上都是一样的。

(2)storageType

说明这个文件存储的是什么进程的数据结构信息。如果是 DataNode,storageType=DATA_NODE;如果是 NameNode,storageType=NAME_NODE。

(3)cTime

NameNode 存储系统创建时间,首次格式化文件系统这个属性是 0,当文件系统升级之后,该值会更新到升级之后的时间戳。

(4)layoutVersion

HDFS 元数据格式的版本。添加需要更改元数据格式的新功能时,请更改此数字。当前的 HDFS 软件使用比当前版本更新的布局版本时,需要进行 HDFS 升级。

c. seen_txid

seen_txid 是 NameNode 在启动时记录的一个状态,里面包含上一次 checkpoint 时的最后一个事务 ID。NameNode 启动时会读取 fsimage 文件,并从 edits log 中恢复上次关闭时未处理的事务,将文件系统状态恢复到最后一个已经处理的事务的状态。然后,NameNode 会从该事务的下一个事务开始处理 edits log 中的事务,直到处理完所有的 edits log。

seen_txid 的作用是为了在下次启动时,可以从上一次已经处理过的位置继续处理 edits log,避免重复处理已经处理过的事务,从而提高启动速度。这个状态是在每次完成 edits log 的合并操作之后更新的。因此,seen_txid 和上述过程密切相关,它记录了 NameNode 处理 edits log 的位置,是启动时恢复文件系统状态和处理 edits log 的关键状态之一。

该文件的目的是尝试识别 edits 启动期间是否丢失的文件。如果 edits 目录被意外删除,然后自上一个 checkpoint 以来的所有事务都将消失,NameNode 仅从最近的一次 fsimage 加载启动。为了防止这种情况,NameNode 启动时还会检查 seen_txid 以确认它至少可以加载该数目的事务。如果无法验证装入事务,它将中止启动。

d. fsimage·oiv

元数据镜像文件。每个 fsimage 文件还有一个对应的 .md5 文件,其中包含 MD5 校验和,HDFS 使用该文件来防止磁盘损坏文件异常。

fsimage 文件是 Hadoop 文件系统元数据的一个永久性的检查点,包含 Hadoop 文件系统中的所有目录和文件 idnode 的序列化信息;对于文件来说,包含的信息有修改时间、访问时间、块大小和组成一个文件块信息等;而对于目录来说,包含的信息主要有修改时间、访问控制权限等信息。

oiv 是“offline image viewer”的缩写,用于将 fsimage 文件的内容转储到指定文件中以便于阅读,该工具还提供了只读的 WebHDFS API 以允许离线分析和检查 Hadoop 集群的命名空间。

oiv 在处理非常大的 fsimage 文件时是相当快的,如果该工具不能够处理 fsimage,它会直接退出。该工具不具备向后兼容性,比如使用 Hadoop 2.4 版本的 oiv 不能处理 Hadoop 2.3 版本的 fsimage,只能使用 Hadoop 2.3 版本的 oiv,就像它的名称所提示的(offline),oiv 不需要 Hadoop 集群处于运行状态。

# hdfs oiv -p 文件类型 -i镜像文件 -o 转换后文件输出路径
hdfs oiv -p xml -i fsimage_0000000000000000073 -o /opt/software/fsimage73.xml

文件内容:

<?xml version="1.0"?>
<fsimage>
    <version>
        <layoutVersion>-64</layoutVersion>
        <onDiskVersion>1</onDiskVersion>
        <oivRevision>ba631c436b806728f8ec2f54ab1e289526c90579</oivRevision>
    </version>
    <NameSection>
        <namespaceId>1651895677</namespaceId>
        <genstampV1>1000</genstampV1>
        <genstampV2>1002</genstampV2>
        <genstampV1Limit>0</genstampV1Limit>
        <lastAllocatedBlockId>1073741826</lastAllocatedBlockId>
        <txid>73</txid>
    </NameSection>
    <ErasureCodingSection>
        <erasureCodingPolicy>
            <policyId>1</policyId>
            <policyName>RS-6-3-1024k</policyName>
            <cellSize>1048576</cellSize>
            <policyState>DISABLED</policyState>
            <ecSchema>
                <codecName>rs</codecName>
                <dataUnits>6</dataUnits>
                <parityUnits>3</parityUnits>
            </ecSchema>
        </erasureCodingPolicy>
        <erasureCodingPolicy>
            <policyId>2</policyId>
            <policyName>RS-3-2-1024k</policyName>
            <cellSize>1048576</cellSize>
            <policyState>DISABLED</policyState>
            <ecSchema>
                <codecName>rs</codecName>
                <dataUnits>3</dataUnits>
                <parityUnits>2</parityUnits>
            </ecSchema>
        </erasureCodingPolicy>
        <erasureCodingPolicy>
            <policyId>3</policyId>
            <policyName>RS-LEGACY-6-3-1024k</policyName>
            <cellSize>1048576</cellSize>
            <policyState>DISABLED</policyState>
            <ecSchema>
                <codecName>rs-legacy</codecName>
                <dataUnits>6</dataUnits>
                <parityUnits>3</parityUnits>
            </ecSchema>
        </erasureCodingPolicy>
        <erasureCodingPolicy>
            <policyId>4</policyId>
            <policyName>XOR-2-1-1024k</policyName>
            <cellSize>1048576</cellSize>
            <policyState>DISABLED</policyState>
            <ecSchema>
                <codecName>xor</codecName>
                <dataUnits>2</dataUnits>
                <parityUnits>1</parityUnits>
            </ecSchema>
        </erasureCodingPolicy>
        <erasureCodingPolicy>
            <policyId>5</policyId>
            <policyName>RS-10-4-1024k</policyName>
            <cellSize>1048576</cellSize>
            <policyState>DISABLED</policyState>
            <ecSchema>
                <codecName>rs</codecName>
                <dataUnits>10</dataUnits>
                <parityUnits>4</parityUnits>
            </ecSchema>
        </erasureCodingPolicy>
    </ErasureCodingSection>
    <INodeSection>
        <lastInodeId>16396</lastInodeId>
        <numInodes>11</numInodes>
        <inode>
            <id>16385</id>
            <type>DIRECTORY</type>
            <name></name>
            <mtime>1680476095912</mtime>
            <permission>liujiaqi:supergroup:0755</permission>
            <nsquota>9223372036854775807</nsquota>
            <dsquota>-1</dsquota>
        </inode>
        <inode>
            <id>16386</id>
            <type>DIRECTORY</type>
            <name>tmp</name>
            <mtime>1680344181451</mtime>
            <permission>liujiaqi:supergroup:0770</permission>
            <nsquota>-1</nsquota>
            <dsquota>-1</dsquota>
        </inode>
        <inode>
            <id>16387</id>
            <type>DIRECTORY</type>
            <name>hadoop-yarn</name>
            <mtime>1680344181451</mtime>
            <permission>liujiaqi:supergroup:0770</permission>
            <nsquota>-1</nsquota>
            <dsquota>-1</dsquota>
        </inode>
        <inode>
            <id>16388</id>
            <type>DIRECTORY</type>
            <name>staging</name>
            <mtime>1680344181451</mtime>
            <permission>liujiaqi:supergroup:0770</permission>
            <nsquota>-1</nsquota>
            <dsquota>-1</dsquota>
        </inode>
        <inode>
            <id>16389</id>
            <type>DIRECTORY</type>
            <name>history</name>
            <mtime>1680344181479</mtime>
            <permission>liujiaqi:supergroup:0770</permission>
            <nsquota>-1</nsquota>
            <dsquota>-1</dsquota>
        </inode>
        <inode>
            <id>16390</id>
            <type>DIRECTORY</type>
            <name>done</name>
            <mtime>1680344181451</mtime>
            <permission>liujiaqi:supergroup:0770</permission>
            <nsquota>-1</nsquota>
            <dsquota>-1</dsquota>
        </inode>
        <inode>
            <id>16391</id>
            <type>DIRECTORY</type>
            <name>done_intermediate</name>
            <mtime>1680344181479</mtime>
            <permission>liujiaqi:supergroup:1777</permission>
            <nsquota>-1</nsquota>
            <dsquota>-1</dsquota>
        </inode>
        <inode>
            <id>16392</id>
            <type>DIRECTORY</type>
            <name>sanguo</name>
            <mtime>1680472113347</mtime>
            <permission>liujiaqi:supergroup:0755</permission>
            <nsquota>-1</nsquota>
            <dsquota>-1</dsquota>
        </inode>
        <inode>
            <id>16393</id>
            <type>DIRECTORY</type>
            <name>wenzhou</name>
            <mtime>1680509268685</mtime>
            <permission>liujiaqi:supergroup:0755</permission>
            <nsquota>-1</nsquota>
            <dsquota>-1</dsquota>
        </inode>
        <inode>
            <id>16395</id>
            <type>FILE</type>
            <name>ljq.png</name>
            <replication>3</replication>
            <mtime>1680507698316</mtime>
            <atime>1680507697926</atime>
            <preferredBlockSize>134217728</preferredBlockSize>
            <permission>liujiaqi:supergroup:0644</permission>
            <blocks>
                <block>
                    <id>1073741825</id>
                    <genstamp>1001</genstamp>
                    <numBytes>268936</numBytes>
                </block>
            </blocks>
            <storagePolicyId>0</storagePolicyId>
        </inode>
        <inode>
            <id>16396</id>
            <type>FILE</type>
            <name>ljq.jpg</name>
            <replication>1</replication>
            <mtime>1680507915498</mtime>
            <atime>1680507915369</atime>
            <preferredBlockSize>134217728</preferredBlockSize>
            <permission>liujiaqi:supergroup:0644</permission>
            <blocks>
                <block>
                    <id>1073741826</id>
                    <genstamp>1002</genstamp>
                    <numBytes>43332</numBytes>
                </block>
            </blocks>
            <storagePolicyId>0</storagePolicyId>
        </inode>
    </INodeSection>
    <INodeReferenceSection></INodeReferenceSection>
    <SnapshotSection>
        <snapshotCounter>0</snapshotCounter>
        <numSnapshots>0</numSnapshots>
    </SnapshotSection>
    <INodeDirectorySection>
        <directory>
            <parent>16385</parent>
            <child>16392</child>
            <child>16386</child>
            <child>16393</child>
        </directory>
        <directory>
            <parent>16386</parent>
            <child>16387</child>
        </directory>
        <directory>
            <parent>16387</parent>
            <child>16388</child>
        </directory>
        <directory>
            <parent>16388</parent>
            <child>16389</child>
        </directory>
        <directory>
            <parent>16389</parent>
            <child>16390</child>
            <child>16391</child>
        </directory>
        <directory>
            <parent>16393</parent>
            <child>16396</child>
            <child>16395</child>
        </directory>
    </INodeDirectorySection>
    <FileUnderConstructionSection></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>

可以看出,fsimage 中没有记录 block 所对应 DataNode,为什么?

在集群启动后,要求 DataNode 上报 block 信息,并间隔一段时间后再次上报。

e. editslog·oev

已完成且不可修改的编辑日志。这些文件中的每个文件都包含文件名定义的范围内的所有编辑日志事务。

edits log 文件存放的是 Hadoop 文件系统的所有更新操作记录日志,文件系统客户端执行的所有写操作首先会被记录到 edits 文件中。

在 HA 高可用性部署中,主备 NameNode 之间可以通过 edits log 进行数据同步。

NameNode 起来之后,HDFS 中的更新操作会重新写到 edits 文件中,因为 fsimage 文件一般都很大(GB 级别的很常见),如果所有的更新操作都往 fsimage 文件中添加,这样会导致系统运行的十分缓慢,但是如果往 edits 文件里面写就不会这样,每次执行写操作之后,且在向客户端发送成功代码之前,edits 文件都需要同步更新。如果一个文件比较大,使得写操作需要向多台机器进行操作,只有当所有的写操作都执行完成之后,写操作才会返回成功,这样的好处是任何的操作都不会因为机器的故障而导致元数据的不同步。

oev 是“offline edits viewer,离线 edits 查看器“的缩写,该工具不需要 Hadoop 集群处于运行状态。

# hdfs oev -p 文件类型 -i编辑日志 -o 转换后文件输出路径
hdfs oev -p xml -i edits_0000000000000000005-0000000000000000012 -o /opt/software/edits.xml

文件内容:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<EDITS>
  <EDITS_VERSION>-64</EDITS_VERSION>
  <RECORD>
    <OPCODE>OP_START_LOG_SEGMENT</OPCODE>
    <DATA>
      <TXID>5</TXID>
    </DATA>
  </RECORD>
  <RECORD>
    <OPCODE>OP_MKDIR</OPCODE>
    <DATA>
      <TXID>6</TXID>
      <LENGTH>0</LENGTH>
      <INODEID>16386</INODEID>
      <PATH>/tmp</PATH>
      <TIMESTAMP>1680344181450</TIMESTAMP>
      <PERMISSION_STATUS>
        <USERNAME>liujiaqi</USERNAME>
        <GROUPNAME>supergroup</GROUPNAME>
        <MODE>504</MODE>
      </PERMISSION_STATUS>
    </DATA>
  </RECORD>
  <RECORD>
    <OPCODE>OP_MKDIR</OPCODE>
    <DATA>
      <TXID>7</TXID>
      <LENGTH>0</LENGTH>
      <INODEID>16387</INODEID>
      <PATH>/tmp/hadoop-yarn</PATH>
      <TIMESTAMP>1680344181451</TIMESTAMP>
      <PERMISSION_STATUS>
        <USERNAME>liujiaqi</USERNAME>
        <GROUPNAME>supergroup</GROUPNAME>
        <MODE>504</MODE>
      </PERMISSION_STATUS>
    </DATA>
  </RECORD>
  <RECORD>
    <OPCODE>OP_MKDIR</OPCODE>
    <DATA>
      <TXID>8</TXID>
      <LENGTH>0</LENGTH>
      <INODEID>16388</INODEID>
      <PATH>/tmp/hadoop-yarn/staging</PATH>
      <TIMESTAMP>1680344181451</TIMESTAMP>
      <PERMISSION_STATUS>
        <USERNAME>liujiaqi</USERNAME>
        <GROUPNAME>supergroup</GROUPNAME>
        <MODE>504</MODE>
      </PERMISSION_STATUS>
    </DATA>
  </RECORD>
  <RECORD>
    <OPCODE>OP_MKDIR</OPCODE>
    <DATA>
      <TXID>9</TXID>
      <LENGTH>0</LENGTH>
      <INODEID>16389</INODEID>
      <PATH>/tmp/hadoop-yarn/staging/history</PATH>
      <TIMESTAMP>1680344181451</TIMESTAMP>
      <PERMISSION_STATUS>
        <USERNAME>liujiaqi</USERNAME>
        <GROUPNAME>supergroup</GROUPNAME>
        <MODE>504</MODE>
      </PERMISSION_STATUS>
    </DATA>
  </RECORD>
  <RECORD>
    <OPCODE>OP_MKDIR</OPCODE>
    <DATA>
      <TXID>10</TXID>
      <LENGTH>0</LENGTH>
      <INODEID>16390</INODEID>
      <PATH>/tmp/hadoop-yarn/staging/history/done</PATH>
      <TIMESTAMP>1680344181451</TIMESTAMP>
      <PERMISSION_STATUS>
        <USERNAME>liujiaqi</USERNAME>
        <GROUPNAME>supergroup</GROUPNAME>
        <MODE>504</MODE>
      </PERMISSION_STATUS>
    </DATA>
  </RECORD>
  <RECORD>
    <OPCODE>OP_MKDIR</OPCODE>
    <DATA>
      <TXID>11</TXID>
      <LENGTH>0</LENGTH>
      <INODEID>16391</INODEID>
      <PATH>/tmp/hadoop-yarn/staging/history/done_intermediate</PATH>
      <TIMESTAMP>1680344181479</TIMESTAMP>
      <PERMISSION_STATUS>
        <USERNAME>liujiaqi</USERNAME>
        <GROUPNAME>supergroup</GROUPNAME>
        <MODE>493</MODE>
      </PERMISSION_STATUS>
    </DATA>
  </RECORD>
  <RECORD>
    <OPCODE>OP_SET_PERMISSIONS</OPCODE>
    <DATA>
      <TXID>12</TXID>
      <SRC>/tmp/hadoop-yarn/staging/history/done_intermediate</SRC>
      <MODE>1023</MODE>
    </DATA>
  </RECORD>
</EDITS>

5.3 2NN

a. 职责概述

首先,我们做个假设,如果存储在 NameNode 节点的磁盘中,因为经常需要进行随机访问,还有响应客户请求,必然是效率过低。因此,元数据需要存放在内存中。但如果只存在内存中,一旦断电,元数据丢失,整个集群就无法工作了。因此产生在磁盘中备份元数据的 fsImage。

这样又会带来新的问题,当在内存中的元数据更新时,如果同时更新 FsImage,就会导致效率过低,但如果不更新,就会发生一致性问题,一旦 NameNode 节点断电,就会产生数据丢失。因此,引入 edits log(只进行追加操作,效率很高)。每当元数据有更新或者添加元数据时,修改内存中的元数据并追加到 edits log 中。这样,一旦 NameNode 节点断电,可以通过 fsImage 和 edits log 的合并,合成元数据。

但是,如果长时间添加数据到 edits log 中,会导致该文件数据过大,效率降低,而且一旦断电,恢复元数据需要的时间过长。因此,需要定期进行 fsImage 和 edits log 的合并,如果这个操作由 NameNode 节点完成,又会效率过低。因此,引入一个新的节点 SecondaryNamenode,专门用于合并 NameNode 的 fsImage 和 edits Log

b. checkpoint 流程

checkpoint 核心是把 fsimage 与 edits log 合并以生成新的 fsimage 的过程。此过程有两个好处:fsimage 版本不断更新不会太旧;edits log 不会太大。

  1. 当触发 checkpoint 操作条件时,2NN 发生请求给 NN 滚动 edits log,然后 NN 会生成一个新的编辑日志文件:edits_new,便于记录后续操作记录;
  2. 同时 2NN 会使用 HTTP GET 方式将 edits log 和 fsimage 复制到本地;
  3. 2NN 首先将 fsimage 载入到内存,然后一条一条地执行 edits log 中的操作,使得内存中的 fsimage 不断更新,这个过程就是 edits log 和 fsimage 文件合并。合并结束,2NN 将内存中的数据 dump 生成一个新的 fsimage 文件;
  4. 2NN 将新生成的 fsimage 文件复制到 NN 节点;
  5. 至此刚好是一个轮回,等待下一次 checkpoint 触发 SecondaryNameNode 进行工作,一直这样循环操作。

c. ckpt 触发机制

checkpoint 触发条件受两个参数控制,可以通过 core-site.xml 进行配置。

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

# 两次连续的checkpoint之间的时间间隔。默认1h
dfs.namenode.checkpoint.period=3600
# 最大没有执行checkpoint事务的数量,满足将强制执行紧急checkpoint,即使尚未达到检查点周期。默认100w
dfs.namenode.checkpoint.txns=1000000

5.4 NN 元数据恢复

a. NN 存储多目录

NameNode 元数据存储目录由参数 fs.namenode.name.dir 指定。

dfs.namenode.name.dir 属性可以配置多个目录,各个目录存储的文件结构和内容都完全一样,相当于备份,这样做的好处是当其中一个目录损坏了,也不会影响到 Hadoop 的元数据,特别是当其中一个目录是 NFS(网络文件系统 Network File System)之上,即使你这台机器损坏了,元数据也得到保存。

b. 从 2NN 恢复

SecondaryNameNode 在 checkpoint 的时候会将 fsimage 和 edits log 下载到自己的本机上本地存储目录下,并且在 checkpoint 之后也不会进行删除。

如果 NameNode 中的 fsimage 真的出问题了,还是可以用 SecondaryNameNode 中的 fsimage 替换一下 NameNode 上的 fsimage,虽然已经不是最新的 fsimage,但是我们可以将损失减小到最少。

此外在部署规划集群的时候,也尽力不要把 secondaryNameNode 和 NameNode 部署在一台机器上。

6. DataNode

6.1 工作机制

  1. 一个数据块在 DataNode 上以文件形式存储在磁盘上,包括 2 个文件:一个是数据本身,一个是元数据(包括数据块的长度、块数据的校验和以及时间戳);

  2. DataNode 启动后向 NameNode 注册,通过后,周期性(6h)的向 NameNode 上报自己所持有的所有块信息;

    <!-- DN 向 NN 汇报当前解读信息的时间间隔,默认6h-->
    <property>
     <name>dfs.blockreport.intervalMsec</name>
     <value>21600000</value>
     <description>Determines block reporting interval in milliseconds.</description>
    </property>
    <!-- DN 扫描自己节点块信息列表的时间,默认6h -->
    <!--  单位:秒 -->
    <property>
     <name>dfs.datanode.directoryscan.interval</name>
     <value>21600</value>
     <description>
         Interval in seconds for Datanode to scan data directories and reconcile 
          the difference between blocks in memory and on the disk.
         Support multiple time unit suffix(case insensitive), as described
         in dfs.heartbeat.interval.
     </description>
    </property>
    

6.2 数据完整性

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

如下是 DataNode 节点保证数据完整性的方法。

  1. 当 DataNode 读取 block 的时候,它会计算 CheckSum;
  2. 如果计算后的 CheckSum,与 block 创建时值不一样,说明 block 已经损坏;
  3. Client 读取其他 DataNode 上的 block;
  4. 常见的校验算法 CRC(32)、MD5(128)、SHA1(160);
  5. DataNode 在其文件创建后周期验证 CheckSum。

6.3 掉线时限参数

需要注意的是 hdfs-site.xml 配置文件中的 heartbeat.recheck.interval 的单位为 ms,dfs.heartbeat.interval 的单位为 s。

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

<property>
    <name>dfs.heartbeat.interval</name>
    <value>3</value>
</property>

http://hadoop102:9870/dfshealth.html#tab-datanode