Hadoop笔记02-Hadoop-HDFS

HDFS概述

HDFS产出背景及定义

随着数据量越来越大,在一个操作系统存不下所有的数据,那么就分配到更多的操作系统管理的磁盘中,但是不方便管理和维护,迫切需要一种系统来管理多台机器上的文件,这就是分布式文件管理系统。HDFS只是分布式文件管理系统中的一种。
HDFS(Hadoop Distributed File System),它是一个文件系统,用于存储文件,通过目录树来定位文件;它是分布式的,由很多服务器联合起来实现其功能,集群中的服务器有各自的角色。
HDFS的使用场景:适合一次写入,多次读出的场景。一个文件经过创建、写入和关闭之后就不需要改变。

HDFS优缺点

优点:

  • 高容错性:数据会保存多个副本,当某个副本损坏或丢失,可以自动恢复
  • 适合处理大数据:数据规模大,文件规模大
  • 可以构建在廉价机器上,通过多副本机制,提高可靠性

缺点:

  • 不适合低延迟数据访问:毫秒级的访问是做不到的
  • 无法高效对大量小文件进行存储:存储大量小文件会占用NameNode存储空间,大量小文件会增加寻址时间,违反了HDFS设计目标
  • 不支持并发写入、文件随机修改:一个文件不允许多线程同时写,仅支持数据追加,不支持文件随机修改

HDFS组成架构

在这里插入图片描述
NameNode(nn):作为HDFS的管理者,管理HDFS名称空间、配置副本策略、管理数据库映射信息、处理客户端的读写请求。
DataNode:当NameNode下达指令,DataNode就去执行实际操作,存储实际的数据块,执行数据块的读写操作。
Client:在大文件上传的时候,Client会对文件按照Block切分,再进行上传、与NameNode交互,获取文件位置信息、与DataNode交互,读写数据、Client提供一些命令来管理HDFS,比如NameNode的格式化、Client可以通过命令访问HDFS,实现对HDFS的增删查改。
SecondaryNameNode(2nn):只是起到一个辅助作用,不能作为NameNode的热备,可以帮助NameNode分担工作,比如定期合并Fsimage和Edits,并推送给NameNode、紧急情况下,辅助恢复NameNode。

HDFS文件块大小

在HDFS中,文件在物理上是分块存储的(Block),块的大小可以通过配置参数(dfs.blocksize)来确定,在Hadoop 2.x和Hadoop 3.x中,是128M。通常来说,寻址时间为传输时间的1%是最佳状态。块大小约为磁盘传输时间乘以磁盘传输速率,取接近的二次幂为结果,机械硬盘可以是128M,固态硬盘可以是256M。
如果块大小设置的过小,会增加寻址时间,如果块大小设置的过大,会增加传输时间和数据处理时间。
HDFS的块大小主要取决于磁盘传输速率。

HDFS的Shell操作

基本语法

hadoop fs 具体命令
hdfs dfs 具体命令
这两个命令是完全等效的。

命令大全

[root@hadoop102 hadoop-3.1.3]# 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] [-d] [-t <thread count>] <localsrc> ... <dst>]
        [-copyToLocal [-f] [-p] [-ignoreCrc] [-crc] <src> ... <localdst>]
        [-count [-q] [-h] [-v] [-t [<storage type>]] [-u] [-x] [-e] <path> ...]
        [-cp [-f] [-p | -p[topax]] [-d] <src> ... <dst>]
        [-createSnapshot <snapshotDir> [<snapshotName>]]
        [-deleteSnapshot <snapshotDir> <snapshotName>]
        [-df [-h] [<path> ...]]
        [-du [-s] [-h] [-v] [-x] <path> ...]
        [-expunge]
        [-find <path> ... <expression> ...]
        [-get [-f] [-p] [-ignoreCrc] [-crc] <src> ... <localdst>]
        [-getfacl [-R] <path>]
        [-getfattr [-R] {-n name | -d} [-e en] <path>]
        [-getmerge [-nl] [-skip-empty-file] <src> <localdst>]
        [-head <file>]
        [-help [cmd ...]]
        [-ls [-C] [-d] [-h] [-q] [-R] [-t] [-S] [-r] [-u] [-e] [<path> ...]]
        [-mkdir [-p] <path> ...]
        [-moveFromLocal <localsrc> ... <dst>]
        [-moveToLocal <src> <localdst>]
        [-mv <src> ... <dst>]
        [-put [-f] [-p] [-l] [-d] <localsrc> ... <dst>]
        [-renameSnapshot <snapshotDir> <oldName> <newName>]
        [-rm [-f] [-r|-R] [-skipTrash] [-safely] <src> ...]
        [-rmdir [--ignore-fail-on-non-empty] <dir> ...]
        [-setfacl [-R] [{-b|-k} {-m|-x <acl_spec>} <path>]|[--set <acl_spec> <path>]]
        [-setfattr {-n name [-v value] | -x name} <path>]
        [-setrep [-R] [-w] <rep> <path> ...]
        [-stat [format] <path> ...]
        [-tail [-f] [-s <sleep interval>] <file>]
        [-test -[defsz] <path>]
        [-text [-ignoreCrc] <src> ...]
        [-touch [-a] [-m] [-t TIMESTAMP ] [-c] <path> ...]
        [-touchz <path> ...]
        [-truncate [-w] <length> <path> ...]
        [-usage [cmd ...]]

Generic options supported are:
-conf <configuration file>        specify an application configuration file
-D <property=value>               define a value for a given property
-fs <file:///|hdfs://namenode:port> specify default filesystem URL to use, overrides 'fs.defaultFS' property from configurations.
-jt <local|resourcemanager:port>  specify a ResourceManager
-files <file1,...>                specify a comma-separated list of files to be copied to the map reduce cluster
-libjars <jar1,...>               specify a comma-separated list of jar files to be included in the classpath
-archives <archive1,...>          specify a comma-separated list of archives to be unarchived on the compute machines

The general command line syntax is:
command [genericOptions] [commandOptions]

这么来看,其实,好多命令和Linux下是差不多的,熟悉Linux下的常用命令,这里也很好上手,就是在前面加上一个“hadoop fs”。

常用命令实操

准备工作

启动Hadoop集群。

[root@hadoop102 hadoop-3.1.3]# sbin/start-dfs.sh
[root@hadoop102 hadoop-3.1.3]# sbin/start-yarn.sh

查看命令帮助。

[root@hadoop102 hadoop-3.1.3]# hadoop fs -help rm
-rm [-f] [-r|-R] [-skipTrash] [-safely] <src> ... :
  Delete all files that match the specified file pattern. Equivalent to the Unix
  command "rm <src>"

  -f          If the file does not exist, do not display a diagnostic message or
              modify the exit status to reflect an error.
  -[rR]       Recursively deletes directories.
  -skipTrash  option bypasses trash, if enabled, and immediately deletes <src>.
  -safely     option requires safety confirmation, if enabled, requires
              confirmation before deleting large directory with more than
              <hadoop.shell.delete.limit.num.files> files. Delay is expected when
              walking over large directory recursively to count the number of
              files to be deleted before the confirmation.

创建一个测试目录,用于后面的测试。

[root@hadoop102 hadoop-3.1.3]# hadoop fs -mkdir /test

在http://hadoop102:9870/的Utilities的Browse the file system里可以看到。

上传

# 将本地的demo.txt剪切到HDFS的/test目录下
[root@hadoop102 hadoop-3.1.3]# hadoop fs -moveFromLocal demo.txt /test
# 将本地的demo.sh复制粘贴到HDFS的/test目录下,put和copyFromLocal一样,生产环境,put用的更多
[root@hadoop102 hadoop-3.1.3]# hadoop fs -copyFromLocal demo.sh /test
# 把demo.sh的内容追加到HDFS目录下/test/demo.txt文件后
[root@hadoop102 hadoop-3.1.3]# hadoop fs -appendToFile demo.sh /test/demo.txt

下载

# 将HDFS上的/test/demo.txt下载到当前路径,get也是同样的效果,生产环境get用的多一些
[root@hadoop102 hadoop-3.1.3]# hadoop fs -copyToLocal /test/demo.txt

HDFS直接操作

# 显示目录信息
[root@hadoop102 hadoop-3.1.3]# hadoop fs -ls /test
# 显示文件内容
[root@hadoop102 hadoop-3.1.3]# hadoop fs -cat /test/demo.sh
# -chrgp、-chmod、chown:和Linux下效果一样
[root@hadoop102 hadoop-3.1.3]# hadoop fs -chmod 777 /test/demo.sh
# 创建目录
[root@hadoop102 hadoop-3.1.3]# hadoop fs -mkdir /hello
# 从HDFS的一个路径拷贝到HDFS的另一个路径
[root@hadoop102 hadoop-3.1.3]# hadoop fs -cp /hello /test
# 在HDFS目录中移动文件
[root@hadoop102 hadoop-3.1.3]# hadoop fs -mv /hello /test
# 显示一个文件末尾的1kb数据
[root@hadoop102 hadoop-3.1.3]# hadoop fs -tail /test/demo.txt
# 统计文件夹大小信息
[root@hadoop102 hadoop-3.1.3]# hadoop fs -du /test
# 设置HDFS中副本数量,当然也可以在http://hadoop102:9870/里直接修改
# 当设置的副本数超过集群里的机器数时,实际不会有这么多副本,副本最多等于机器数
# 如果之后集群增加结点了,那么副本会自动复制到新结点上
[root@hadoop102 hadoop-3.1.3]# hadoop fs -setrep 5 /test/demo.sh
# 删除文件或文件夹
[root@hadoop102 hadoop-3.1.3]# hadoop fs -rm /test/demo.sh
# 递归删除
[root@hadoop102 hadoop-3.1.3]# hadoop fs -rm -r /test

HDFS的API操作

首先下载对应版本的winutils,不同版本的去github找下就可以,我这里用的Hadoop-3.1.3,从这里下载的。
解压后放到非中文目录下,设置环境变量,并加入PATH中。
在这里插入图片描述
双击运行winutils.exe,如果一闪而过,说明正常,如果出现错误信息,根据错误信息解决问题。
新建一个Maven项目HDFSClientDemo。
pom.xml需要用到的jar包坐标。

<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-client</artifactId>
    <version>3.1.3</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.30</version>
</dependency>

log4j.properties

log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c]- %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c]- %m%n

创建HDFSClient类。

package com.demo.hdfs;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;

public class HDFSClient {
    private FileSystem fileSystem;

    @Before
    public void before() throws URISyntaxException, IOException, InterruptedException {
        URI uri = new URI("hdfs://hadoop102:8020");
        Configuration configuration = new Configuration();
        String user = "root";
        fileSystem = FileSystem.get(uri, configuration, user);
    }

    @After
    public void after() throws IOException {
        fileSystem.close();
    }

    @Test
    public void testMkdirs() throws IOException {
        fileSystem.mkdirs(new Path("/test/demo"));
    }

    @Test
    public void testCopyFromLocalFile() throws IOException {
        boolean delSrc = false;// 是否将本地文件删除
        boolean overwrite = false;// 同名文件上传后是否允许覆盖
        Path src = new Path("H:\\IDEAProject\\demo\\src\\main\\resources\\log4j.properties");// 本地路径
        Path dest = new Path("/test");// HDFS的路径
        fileSystem.copyFromLocalFile(delSrc, overwrite, src, dest);
    }

    @Test
    public void testCopyToLocalFile() throws IOException {
        boolean delSrc = false;// 是否删除HDFS的文件
        Path src = new Path("/test/log4j.properties");// HDFS的路径
        Path dest = new Path("H:\\");// 本地路径
        boolean userRawLocalFileSystem = true;// 是否进行CRC校验
        fileSystem.copyToLocalFile(delSrc, src, dest, false);
    }

    @Test
    public void testRename() throws IOException {
        Path path = new Path("/test/log4j.properties");
        Path path1 = new Path("/test/demo.properties");
        Path path2 = new Path("/test/demo/demo.properties");
        fileSystem.rename(path, path1);// 重命名
        fileSystem.rename(path1, path2);// 移动位置
    }

    @Test
    public void testDelete() throws IOException {
        Path path = new Path("/test/demo");// 要删除的路径
        boolean recursive = true;// 是否递归
        fileSystem.delete(path, recursive);
    }

    @Test
    public void testListFiles() throws IOException {
        Path path = new Path("/");// 要查看的HDFS目录
        boolean recursive = true;// 是否递归
        RemoteIterator<LocatedFileStatus> locatedFileStatusRemoteIterator = fileSystem.listFiles(path, recursive);
        while (locatedFileStatusRemoteIterator.hasNext()) {
            LocatedFileStatus locatedFileStatus = locatedFileStatusRemoteIterator.next();
            System.out.println("========" + locatedFileStatus.getPath() + "=========");
            System.out.println(locatedFileStatus.getPermission());
            System.out.println(locatedFileStatus.getOwner());
            System.out.println(locatedFileStatus.getGroup());
            System.out.println(locatedFileStatus.getLen());
            System.out.println(locatedFileStatus.getModificationTime());
            System.out.println(locatedFileStatus.getReplication());
            System.out.println(locatedFileStatus.getBlockSize());
            System.out.println(locatedFileStatus.getPath().getName());
            // 获取块信息
            BlockLocation[] blockLocations = locatedFileStatus.getBlockLocations();
            System.out.println(Arrays.toString(blockLocations));
        }
    }

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

代码里的Configuration对象,我们并没有给它set任何属性,如果有需要,可以通过configuration.set(String name, String value);方法设置。
这里就涉及到一个优先级问题,对于同一个属性,如果有多个地方设置了不同的值,优先级按照如下顺序:
代码里configuration的设置>classpath下用户自定义配置文件(xxx-site.xml)>服务器自定义配置文件(xxx-default.xml)。

HDFS的读写流程

HDFS写数据流程

剖析文件写入

在这里插入图片描述

  1. 客户端通过Distributed FileSystem向NameNode发送上传文件请求,NameNode检测目标文件,父目录等信息
  2. NameNode返回是否可以上传
  3. 客户端请求上传第一个Block
  4. NameNode告知客户端具体存储数据的3个DataNode结点,分别为dn1,dn2,dn3
  5. 客户端通过FSDataOutputStream,向dn1发送数据,dn1收到请求后,继续访问dn2,dn2继续访问dn3,建立通信管道
  6. dn1,dn2,dn3逐级应答客户端
  7. 客户端开始向dn1发送第一个Block,一个Block又分为多个Packet,dn1收到一个Packet就会传给dn2,dn2传给dn3,同时dn1每传递一个Packet就会放入一个应答队列等待应答
  8. 当一个Block传输完毕后,客户端再次请求NameNode上传第二个Block

网络拓扑-结点距离计算

在网络拓扑中,结点距离的定义是:两个结点到达最近公共祖先结点的距离之和。
在这里插入图片描述

机架感知

官方说明
在选取副本的时候,第一个副本位于Client所在结点,如果客户端不在集群里,那么随机选择一个,第二个副本在另一个机架上随机选择一个结点,第三个副本和第二个副本在同一台机架上,随机选择一个结点。
这样的设计可以兼顾可靠性和传输速度。

HDFS读数据流程

在这里插入图片描述

  1. 客户端通过DistributedFileSystem向NameNode发送一个下载请求,NameNode查询元数据,找到文件对应的DataNode
  2. 按照就近原则,选择一台DataNode,读取数据
  3. DataNode传输数据给客户端,以Packet为单位校验
  4. 客户端以Packet为单位接收,再写入目标文件

NameNode和SecondaryNameNode

NN和2NN工作机制

NameNode的元数据是分两部分的,一部分是内存,一部分是硬盘,内存和硬盘的优劣是互补的。所以,内存和磁盘中都是要存储的,磁盘中的这部分,我们称为FsImage。对于访问数据,通过内存获取,速度非常快,对于修改数据,修改内存中的数据,并把操作记录下来,保存到一个叫Edits的文件中,避免内存断电数据丢失,这个Edits文件只进行append操作,所以效率非常高。
不过Edits也是有限的,长期存在里面会导致文件变大,效率降低,因此需要定期对FsImage和Edits进行合并,这个操作由2NN来完成,它专门处理FsImage和Edits的合并工作。
在这里插入图片描述

  1. 如果第一次启动NameNode格式化后,会创建FsImage和Edits文件。否则直接加载FsImags和Edits到内存。
  2. 客户端对数据进行增删查改操作。
  3. NameNode记录操作日志,更新滚动日志。
  4. NameNode在内存中对数据进行增删查改。
  5. 经过一段时间后,Secondary NameNode会询问NameNode是否需要CheckPoint。
  6. 如果需要CheckPoint,Secondary NameNode执行CheckPoint操作。
  7. NameNode滚动正在写的Edits日志,就是产生一个新的日志文件。
  8. 将滚动前的Edits和FsImage交给Secondary NameNode。
  9. Secondary NameNode加载FsImage和Edits到内存,做合并操作。
  10. 将会生成的新的镜像文件FsImage.chkpoint。
  11. 拷贝FsImage.chkpoint到NameNode。
  12. NameNode将FsImage.chkpoint重命名为FsImage。

FsImage和Edits解析

NameNode格式化后,会在/opt/module/hadoop-3.1.3/data/tmp/dfs/name/current目录产生如下文件:
fsimage_0000000000000000000
fsimage_0000000000000000000.md5
seen_txid
VERSION
FsImage:HDFS元数据的一个永久检查点,包含HDFS所有目录和文件inode序列化信息。
Edits:存放HDFS所有更新操作的路径,客户端执行的所有写操作首先被记录到Edits中。
seen_txid:保存当前的进度,和Edits_后的数字保持一致。
每次NameNode启动的时候,都会将FsImage读取到内存,并根据Edits的操作日志,更新内存里的信息。可以认为,在NameNode启动的时候,会做一个FsImage和Edits的合并操作。

# 查看FsImage
# hdfs oiv -p 文件类型 -i 镜像文件 -o 转换后文件输出路径
[root@hadoop102 hadoop-3.1.3]# hdfs oiv -p XML -i fsimage_0000000000000000025 -o /opt/module/hadoop-3.1.3/fsimage.xml
# 查看Edits
# hdfs oev -p 文件类型 -i 编辑日志 -o 转换后文件输出路径
[root@hadoop102 hadoop-3.1.3]# hdfs oev -p XML -i edits_0000000000000000012-0000000000000000013 -o /opt/module/hadoop3.1.3/edits.xml

CheckPoint时间设置

通常情况下,Secondary NameNode每隔一小时执行一次,在hdfs-default.xml里由dfs.namenode.checkpoint.period参数控制。
每分钟查看一次操作次数,如果操作次数达到100W次时,Secondary NameNode也会执行一次,在hdfs-default.xml里由dfs.namenode.checkpoint.txns和dfs.namenode.checkpoint.check.period参数控制。

DataNode工作机制

工作机制

在这里插入图片描述

  1. 一个数据块在DataNode上以文件形式存储在磁盘,包括两部分,一个是数据本身,一个是数据属性和校验信息。
  2. DataNode启动后,向NameNode注册,周期性(间隔6小时)的向NameNode上报块信息,DataNode向NameNode汇报信息间隔是6小时,由dfs.blockreport.intervalMsec参数控制,DataNode扫描自己结点信息列表时间也是6小时,由dfs.datanode.directoryscan.interval参数控制。
  3. DataNode和NameNode之间用每隔3秒的心跳信号保活,心跳返回结果带有NameNode给该DataNode的命令如复制块数据到另一台机器,或删除某个数据块。如果超过10分钟没有收到某个DataNode的心跳,则认为该节点不可用。
  4. 集群运行中可以安全加入和退出一些机器。

数据完整性

  1. DataNode结点读取Block信息的时候,会计算CheckSum。
  2. 如果计算后的CheckSum与Block创建时不一样,说明Block已经损坏。
  3. Client读取其他DataNode上的Block。
  4. 校验算法:crc(32),md5(128),sha1(160)。
  5. DataNode在文件创建后,周期性验证CheckSum。

掉线时限参数设置

当DataNode死亡或网络故障无法通信的时候,NameNode不会立即把该结点判定为死亡,经过一段时间(超时时长)后,宣布DataNode死亡。HDFS默认超时时长是10分钟+30秒。
TimeOut = 2 * dfs.namenode.heartbeat.recheck-interval + 10 * dfs.heartbeat.interval。而默认的dfs.namenode.heartbeat.recheck-interval 大小为5分钟,dfs.heartbeat.interval默认为3秒。
在hdfs-site.xml里可以找到heartbeat.recheck.interval和dfs.heartbeat.interval,heartbeat.recheck.interval的单位是毫秒,dfs.heartbeat.interval的单位是秒。
当DataNode死亡后,NameNode就不会继续和它进行数据交互了,如果后面DataNode复活,会通知到NameNode,此时会做数据同步,之后保持正常运行。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值