avatar

目录
Hadoop学习笔记(二)HDFS

Hadoop学习笔记(二)HDFS

一、HDFS相关概念

1.1 数据块

在HDFS中文件的储存是以块为单位存储,默认大小为128M。在DHFS中,文件的上传后将文件切块分别存储在不同的DataNode上。

但是,如果存储一个1MB的文件在一个128MB的块中,实际占用的空间是1MB。

1.2 元数据

数据格式:FileName(文件路径,只是逻辑地址,实际不存在),replicas(副本数量),clock-ids(文件块的ID以及分布在哪些DataNode上),id2host(对应DataNode的地址)…

1.3 名称节点(NameNode)

NameNode负责管理分布式文件系统的命名空间,不参与数据的存储和读取,只记录元数据信息。是唯一存储元数据到数据块映射的地方。

在NameNode中有两个核心的数据结构:FsImage(镜像文件,用来维护文件系统树以及元数据信息)和Editlog(编辑日志文件,用来记录文件的一些操作)。

工作原理:当DHFS启动的时候,会先将FsImage的内容加载到内存中然后将EditLog的内容合并后内存中的元数据就保持最新的状态。(映射信息是放在内存中的)

注意:不适合存放大量小文件。由于元数据存储在内存中,因此该文件系统所能存储的文件总数受限于namenode的内存容量。每个数据块、文件、目录的存储信息都会占用字节。

1.4 数据节点(DataNode)

DataNode是分布式文件系统HDFS的工作节点,负责数据的存储和读取。

1.5 第二名称节点(SecondaryNameNode)

在NameNode运行期间,HDFS会在不断的发生更新操作。这些操作会直接写入到EditLog中,因此该文件会逐渐变大,在重启名称节点的时候,合并操作就会变慢,影响启动速度。因此每隔一段时间(默认是3600秒)或者EditLog达到一定大小的时候(默认是64M),会通知NameNode进行合并操作。同时,该机制也可以当作故障恢复的”检查点“。

工作原理:首先FsImage会暂时将日志写入一个新建的EditLog.new中,然后第二名称节点将名称节点中的FsImage和EditLog文件拉到本地后载入内存中进行合并操作,然后将合并后最新的FsImage发送给名称节点,名称节点用新的FsImage替换旧的FsImage,最后将EditLog.new重命名为EditLog

单独配置第二名称节点的位置:进入hdfs-site.xml修改字段值dfs.namenode.secondary.http-address为相应主机:端口。

二、数据流

2.1 文件写入过程

2.1.1 数据的冗余

为了保证系统的容错性和可用性,HDFS采用了多副本方式来存储。

2.1.2 数据存储机制

avatar

首先namenode接受到创建文件的请求的时候,会先去检查数据是否存在,用户权限等,通过后去安排要存放数据的datanode(返回一个datanode列表按网络结构远近排序),如何选取,如下:

数据的存放(机架感知)

一个HDFS集群通常有多个机架,机架间的通信通过交换机和路由器,而机架内部间的通信不需要。HDFS默认冗余复制因子是3。文件写入操作的时候,如果在集群内部,则把第一个副本放在发起写操作请求的数据节点上(如果是在集群外部,则从集群内部随机选一个数据节点来存放)。第二个副本放在与第一个副本不同的机架上。第三个副本放在和第一个副本相同的机架上,且随机选另一个节点。如果还有更多的副本,随机选择数据节点进行存储。

一旦选定复本的存放位置,就会根据网络拓扑创建一个管线。紧接着:

数据的复制

HDFS的数据采用了流水线复制的策略。用户向HDFS中写入一个文件的时候,会先把文件写入到本地,并被分割成若干个块,每个块依次进行复制(第一个块写入的同时将数据发送给第二个节点,第二个数据节点写入的同时发给第三个节点,当第三个数据节点写入完成后,返回成功信息给第二个数据节点,第二个数据节点返回成功信息给第一个数据节点,第一个数据节点返回成功信息给NameNode,至此复制完成。!如果中途某数据节点写入失败,会另找一个数据节点进行写入),全部块完成后,文件写入完成。

2.2 文件读取过程

avatar

客户端发送请求,分布式文件系统通过RPC请求调用namenode,以确定文件起始块的位置。对于每一个块,返回该存有该块副本的datanode地址(返回目标文件的元数据)。然后去对应的datanode上读取数据。

2.3 数据错误与恢复

  1. 名称节点出错

    名称节点存储的是元数据,而其中的FsImage或者EditLog文件损坏,那么整个HDFS将不可用。Hadoop采用两种有两种机制来确保名称节点安全:第一种,同步名称节点上的信息到其他文件系统;第二种,运行第二名称节点,进行补救。但是可能会有数据丢失。

  2. 数据节点出错

    在运行过程中,如果某数据节点宕机了,那么该文件就不完整了。因此,每个数据节点会向名称节点发送”心跳包“来报告自己的状态。如果发生故障,名称节点会获悉并判断数据块副本数量是否小于冗余因子,来复制新的副本。

  3. 数据出错

    在网络传输中,有可能发生数据错误。或者,人为误操作把某文件给删除了,这样文件就不完整了。在客户端在读取到数据后,会采用md5和shal对数据块进行校验,以确保获取到正确的信息。

三、HDFS常用命令

命令说明:hadoop fs 等价于 hdfs dfs。

  • hadoop fs :查看指令列表
  • hadoop fs -ls 显示当前目录结构, [-R] 递归显示目录结构
  • hadoop fs -mkdir 创建目录, [-p] 创建父目录
  • hadoop fs -rm 删除文件,[-R] 递归删除目录和文件
  • hadoop fs -put [localsrc] [dst] 从本地加载文件到HDFS
  • hadoop fs -get [dst] [localsrc] 从HDFS导出文件到本地
  • hadoop fs -copyFromLocal [localsrc] [dst] 从本地加载文件到HDFS,与put一致
  • hadoop fs -copyToLocal [dst] [localsrc] 从HDFS导出文件到本地,与get一致
  • hadoop fs -test -e 检测目录和文件是否存在,存在返回值$?为0,不存在返回1
  • hadoop fs -text 查看文件内容
  • hadoop fs -du 统计目录下各文件大小,单位字节。-du -s 汇总目录下文件大小,-du -h 显示单位
  • hadoop fs -tail 显示文件末尾
  • hadoop fs -cp [src] [dst] 从源目录复制文件到目标目录
  • hadoop fs -mv [src] [dst] 从源目录移动文件到目标目录
  • hadoop fs -appendToFile [src] [dst] 将源文件追加到目标文件末尾(不支持中间插入)

四、JAVA客户端API编写

详细代码见GitHub

  • pom.xml

    xml
    <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <!--设置hadoop版本-->
    <hadoop.3.version>3.1.2</hadoop.3.version>
    </properties>
    <dependencies>
    <dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-common</artifactId>
    <version>${hadoop.3.version}</version>
    </dependency>
    <dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-hdfs</artifactId>
    <version>${hadoop.3.version}</version>
    </dependency>
    <dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-client</artifactId>
    <version>${hadoop.3.version}</version>
    </dependency>
    <dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-mapreduce-client-core</artifactId>
    <version>${hadoop.3.version}</version>
    </dependency>
    <dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-mapreduce-client-common</artifactId>
    <version>${hadoop.3.version}</version>
    </dependency>
    <dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-mapreduce-client-hs</artifactId>
    <version>${hadoop.3.version}</version>
    </dependency>
    <dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-mapreduce-client-jobclient</artifactId>
    <version>${hadoop.3.version}</version>
    </dependency>
    <dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-mapreduce-client-shuffle</artifactId>
    <version>${hadoop.3.version}</version>
    </dependency>
    <dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-auth</artifactId>
    <version>${hadoop.3.version}</version>
    </dependency>
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>3.8.1</version>
    <scope>test</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-common -->
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    </dependency>
    </dependencies>
  • HdfsUtils.java

    java
    /**
    * HdfsUtils class
    * HDFS常用操作工具类
    *
    * @author BoWenWang
    * @date 2020/2/14 22:23
    */
    public class HdfsUtils {

    private static final String HDFS_PATH = "hdfs://192.168.174.201:8020";
    private static final String HDFS_USER = "wbw";
    private static FileSystem fileSystem;

    static {
    Configuration conf = new Configuration();
    try {
    // 第三个参数是用户权限
    fileSystem = FileSystem.get(URI.create(HDFS_PATH), conf, HDFS_USER);
    } catch (IOException | InterruptedException e) {
    e.printStackTrace();
    }
    }

    /**
    * 创建目录
    * @param path 目录路径
    * @return 是否创建成功
    * @throws IOException
    */
    public static boolean mkdir(String path) throws IOException {
    return fileSystem.mkdirs(new Path(path));
    }

    /**
    * 上传文件
    * @param src 源路径
    * @param dst 目标路径
    * @throws IOException
    */
    public static void copyFromLocalFile(String src, String dst) throws IOException {
    fileSystem.copyFromLocalFile(new Path(src), new Path(dst));
    }

    /**
    * 文件下载
    * @param src
    * @param dst
    * @throws IOException
    */
    public static void copyToLocalFile(String src, String dst) throws IOException {
    fileSystem.copyToLocalFile(new Path(src), new Path(dst));
    }

    /**
    * 删除文件
    * @param path 文件路径
    * @param b 是否递归删除
    * @return 是否删除文件成功
    * @throws IOException
    */
    public static boolean delete(String path, boolean b) throws IOException {
    return fileSystem.delete(new Path(path), b);
    }

    /**
    * 判断文件或目录是否存在
    * @param path 文件或目录路径
    * @return 是否存在
    * @throws IOException
    */
    public static boolean exists(String path) throws IOException {
    return fileSystem.exists(new Path(path));
    }

    /**
    * 查看文件
    * @param path 文件路径
    * @return 文件内容
    * @throws IOException
    */
    public static String open(String path) throws IOException {
    FSDataInputStream fis = fileSystem.open(new Path(path));
    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    IOUtils.copy(fis, bout, 1024);
    fis.close();
    return new String(bout.toByteArray());
    }

    /**
    * 文件重命名(移动)
    * @param oldPath 旧路径
    * @param newPath 新路径
    * @return 是否操作成功
    * @throws IOException
    */
    public static boolean rename(String oldPath, String newPath) throws IOException {
    return fileSystem.rename(new Path(oldPath), new Path(newPath));
    }

    /**
    * 查看目录下的所有文件
    * @param path 文件目录
    * @param b 是否递归查看
    * @throws IOException
    */
    public static void listFiles(String path, Boolean b) throws IOException {
    RemoteIterator<LocatedFileStatus> iterator = fileSystem.listFiles(new Path(path), b);
    while (iterator.hasNext()) {
    System.out.println(iterator.next());
    }
    }
    public static void listFiles(String path) throws IOException {
    listFiles(path, false);
    }

    }

五、日志文件

日志文件里面记录了HDFS的各种信息,现在我们来对日志文件进行解析一下。打开Hadoop临时文件目录查看文件。其中edits就是编辑日志,fsimage就是镜像文件,而edits_inprogress则是HDFS正在运行使用的日志。

avatar

5.1 镜像文件

首先我们来看一下当前HDFS文件结构:

avatar

查看镜像文件命令:

Code
$hdfs oiv -i fsimage_0000000000000000036 -o a.txt -p XML

-i: 镜像文件路径
-o: 将内容输出到指定路径
-p: 以指定解析,这里选XML格式

查看 a.txt 内容(这里推荐cat内容后,复制到IDEA下,方便阅览。这里仅列出部分节点内容,同时可以打开WEB-UI来进行信息对照):

  • 目录信息节点

    xml
    <inode>
    <id>16385</id>
    <!-- 类型 -->
    <type>DIRECTORY</type>
    <!-- 目录名(这里为根目录所以为空) -->
    <name></name>
    <!-- 时间戳 -->
    <mtime>1581692427585</mtime>
    <!-- 权限 -->
    <permission>wbw:supergroup:0755</permission>
    <nsquota>9223372036854775807</nsquota>
    <dsquota>-1</dsquota>
    </inode>
    <inode>
    <id>16389</id>
    <type>DIRECTORY</type>
    <name>user</name>
    <mtime>1581694636967</mtime>
    <permission>wbw:supergroup:0755</permission>
    <nsquota>-1</nsquota>
    <dsquota>-1</dsquota>
    </inode>
  • 文件信息节点

    xml
    <inode>
    <id>16391</id>
    <!-- 文件类型 -->
    <type>FILE</type>
    <!-- 文件名 -->
    <name>b.txt</name>
    <!-- 副本数(文件夹没有副本数) -->
    <replication>3</replication>
    <!-- 时间戳 -->
    <mtime>1581692458378</mtime>
    <atime>1581692458090</atime>
    <preferredBlockSize>134217728</preferredBlockSize>
    <permission>wbw:supergroup:0644</permission>
    <blocks>
    <block>
    <!-- 块ID -->
    <id>1073741826</id>
    <genstamp>1002</genstamp>
    <!-- 字节数 -->
    <numBytes>11</numBytes>
    </block>
    </blocks>
    <storagePolicyId>0</storagePolicyId>
    </inode>
  • 目录树(维护HDFS目录结构)

    xml
    <INodeDirectorySection>
    <directory>
    <!-- 根目录ID -->
    <parent>16385</parent>
    <!-- 子目录/文件(这里可以对照上面的目录节点ID,发现是user子目录) -->
    <child>16389</child>
    </directory>
    <directory>
    <parent>16389</parent>
    <child>16393</child>
    <child>16392</child>
    <child>16390</child>
    </directory>
    <directory>
    <parent>16392</parent>
    <child>16391</child>
    </directory>
    </INodeDirectorySection>

5.2 编辑日志

查看编辑日志命令:

Code
$hdfs oev -i edits_0000000000000000042-0000000000000000043 -o b.txt -p XML

-i: 镜像文件路径
-o: 将内容输出到指定路径
-p: 以指定解析,这里选XML格式

操作信息节点

xml
<RECORD>
<!-- 操作 -->
<OPCODE>OP_MKDIR</OPCODE>
<DATA>
<!-- 事务ID -->
<TXID>7</TXID>
<LENGTH>0</LENGTH>
<INODEID>16386</INODEID>
<PATH>/user</PATH>
<TIMESTAMP>1581691491684</TIMESTAMP>
<PERMISSION_STATUS>
<USERNAME>wbw</USERNAME>
<GROUPNAME>supergroup</GROUPNAME>
<MODE>493</MODE>
</PERMISSION_STATUS>
</DATA>
</RECORD>

5.3 日志滚动

打开WEB-UI的“Startup Progress”菜单,从图上可以看出HDFS启动后,显示加载了fsimage然后再加载edits,整个操作在安全模式下进行:

avatar

avatar

安全模式(read-only)

Code
$>hdfs dfsadmin -safemode  enter		//进入(进入后无法进行删除等操作)
$>hdfs dfsadmin -safemode get //查看
$>hdfs dfsadmin -safemode leave //退出
$>hdfs dfsadmin -safemode wait //等待

保存名字空间(手动滚动)

HDFS在启动的时候,会自动进行滚动。但是我们也可以手动对其进行滚动操作。

手动滚动前,编号是46:

avatar

Code
进入安全模式(滚动操作只能在安全模式下进行):
$>hdfs dfsadmin -safemode enter
手动滚动:
$>hdfs dfsamdin -saveNamespace
推出安全模式
$>hdfs dfsadmin -safemode leave

手动滚动后,编号是48:

avatar

对比上面2图,可以发现:

  1. edits文件多了一个46-47,原本是44-45
  2. fsimage多了一个47,少了43(fsimage一般保留2个,用于滚动)
  3. 同时运行时日志变成48

六、配额管理

HDFS可以对目录数量和可用空间大小进行限制。

6.1 目录配额

命令介绍

Code
-setQuota <quota> <dirname>...<dirname>: Set the quota <quota> for each directory <dirName>.
The directory quota is a long integer that puts a hard limit
on the number of names in the directory tree
For each directory, attempt to set the quota. An error will be reported if
1. quota is not a positive integer, or
2. User is not an administrator, or
3. The directory does not exist or is a file.
Note: A quota of 1 would force the directory to remain empty.

可以看到该命令会限制被限制目录下的文件数量(包括文件和文件夹嵌套),如果设置1,表示空目录,即自身占1个数量。

执行命名

Code
$hdfs dfsadmin -setQuota 3 /user/wbw
$hdfs dfs -put quota1.txt /user/wbw/
$hdfs dfs -put quota2.txt /user/wbw/
$hdfs dfs -put quota3.txt /user/wbw/
put: The NameSpace quota (directories and files) of directory /user/wbw is exceeded: quota=3 file count=4

清除配额

Code
$hdfs dfsadmin -clrQuota <dirname>...<dirname>

6.2 空间配额

命令介绍

Code
$>hdfs dfsadmin -setSpaceQuota <quota> <dirname>

注意:副本为3的1GB文件将消耗3GB的配额。同时还可设置 quota 的单位(e.g. 50t is 50TB, 5m is 5MB, 3p is 3PB).,默认是字节。

清除配额

Code
$>hdfs dfsadmin -clrSpaceQuota <dirname>...<dirname>

七、快照管理

7.1 描述

迅速对文件(夹)进行备份。不产生新数据文件(即源文件和快照对应文件的存储块ID是相同的)。

默认是禁用快照,要想进行快照,要先启用。

7.2 命令

Code
$>hdfs dfsadmin -allowSnapshot dir1								//在dir1启用快照
$>hdfs dfsadmin -disallowSnapshot dir1 //在dir1禁用快照
$>hdfs dfs -createSnapshot <snapshotDir> [<snapshotName>] //创建快照
$>hdfs dfs -renameSnapshot <snapshotDir> <oldName> <newName> //重命名
$>hdfs dfs -deleteSnapshot <snapshotDir> <snapshotName> //删除快照

7.3 效果

执行如下命令

Code
[wbw@s201 /home/wbw]$hdfs dfsadmin -allowSnapshot /user/wbw
Allowing snapshot on /user/wbw succeeded
[wbw@s201 /home/wbw]$hdfs dfs -createSnapshot /user/wbw ss1
Created snapshot /user/wbw/.snapshot/ss1
[wbw@s201 /home/wbw]$echo hello > tmp.txt
[wbw@s201 /home/wbw]$ls
downloads hadoop tmp.txt
[wbw@s201 /home/wbw]$hdfs dfs -appendToFile tmp.txt /user/wbw/quota1.txt

可以看到快照会在该目录下新建文件,如果此时修改源文件,那么快照的内容是不会变的(如图):

avatar

avatar

文章作者: IT小王
文章链接: https://wangbowen.cn/2020/02/13/Hadoop%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%BA%8C%EF%BC%89HDFS/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 IT小王

评论