HDFS前言

设计的的思想:主要的是分而治之,将大的文件分割称为一个个小的文件,存储在各个机器上。

在大数据中的应用:为大数据框架提供储存数据的服务

重点概念:文件分块、副本存放、元数据。

HDFS的概念和特性

首先,它是一个文件系统,用于存储文件,通过统一的命名空间——目录树来定位文件。

其次,它是分布式的,很多服务器联合实现功能。

HDFS组成结构图

hdfs 插入分区 hdfs分块_hdfs 插入分区

HDFS 写文件的基本流程

先来了解几个概念

block

文件上传前需要分块,这个块就是block,一般为128MB,当然你可以去改(改成多少跟磁盘性能有关)。block太小的话,会导致block的数量多,寻址block的时间就会增加。block太大的话,会导致Map任务数太少,作业执行速度变慢。它是最大的一个单位。

packet

packet是第二大的单位,它是client端向DataNode,或DataNode的PipLine之间传数据的基本单位,默认64KB。

chunk

chunk是最小的单位,它是client向DataNode,或DataNode的PipLine之间进行数据校验的基本单位,默认512Byte,因为用作校验,故每个chunk需要带有4Byte的校验位。所以实际每个chunk写入packet的大小为516Byte。由此可见真实数据与校验值数据的比值约为128 : 1。(即64*1024 / 512)

例如,在client端向DataNode传数据的时候,HDFSOutputStream会有一个chunk buff,写满一个chunk后,会计算校验和并写入当前的chunk。之后再把带有校验和的chunk写入packet,当一个packet写满后,packet会进入dataQueue队列,其他的DataNode就是从这个dataQueue获取client端上传的数据并存储的。同时一个DataNode成功存储一个packet后之后会返回一个ack packet,放入ack Queue中。

详细步骤

hdfs 插入分区 hdfs分块_元数据_02

  • client请求NameNode上传文件,NameNode查询对应权限,目录存不存在等 返回是否可以上传。
  • client对文件进行block切片,并请求NameNode第一个 Block 上传到哪几个 DataNode 服务器上。
  • NameNode 根据副本数等信息返回可用的DataNode节点,例如上面的 dn1,dn2,dn3。
  • client 请求3台节点中的一台服务器dn1,进行传送数据(本质上是一个RPC调用,建立管道Pipeline),dn1收到请求会继续调用服务器dn2,然后服务器dn3调用服务器dn3。将这个通信管道建立完成。
  • client 开始往 dn1上传第一个Block(先从磁盘读取数据放到一个本地内存缓存),以 Packet为单位,dn1收到一个 Packet就会传给 dn2,dn2传给 dn3;dn1每传一个 packet会放入一个应答队列等待应答。
  • 数据被分割成一个个Packet数据包在Pipeline上依次传输,而在Pipeline反方向上,将逐个发送Ack(命令正确应答),最终由Pipeline中第一个DataNode节点dn1将Pipeline的 Ack信息发送给客户端。
  • 当一个 Block传输完成之后,客户端再次请求 NameNode上传第二个 Block的服务器。

HDFS 读文件的基本流程

  • client 通过 Distributed FileSystem向 NameNode请求下载文件,NameNode通过查询元数据,找到文件块所在的DataNode地址进行返回。
  • 挑选一台 DataNode(就近原则,然后随机)服务器,请求读取数据。当第一次读取完成之后,才进行第二次块的读取。
  • DataNode开始传输数据给客户端(从磁盘里面读取数据输入流,以 Packet为单位来做校验)。
  • 客户端以 Packet为单位接收,先在本地缓存,然后写入目标文件。

NameNode+SecondaryNameNode工作原理

hdfs 插入分区 hdfs分块_大数据_03

  • 集群启动,会加载edits日志和fsimage文件到内存得到元数据。
  • client发起元数据的修改操作时,NameNode先将操作顺序追加到edits_inprogress001文件(效率极高),然后更新到内存(保证元数据最新)。
  • SecondaryNameNode会定期(一小时)或者 edits日志满了(比如到达了100w条),会请求NameNode进行CheckPoint。
  • NameNode会生成一个新的edits_inprogress002 用于存储新的请求。然后将原来的edits_inprogress001 修改成edits_001。
  • SecondaryNameNode 会将NameNode的edits_001 和 fsimage拉取过来,进行计算(执行edits里面的操作)合并成新的镜像文件(fsimage.chkpoint),然后发送给NameNode。
  • NameNode将fsimage.chkpoint改名覆盖原来的fsimage 。这样fsimage 就保持了当前SecondaryNameNode计算合并的最新(加上edits_inprogress002 才是最新的元数据)。

为什么要有fsimage?

为了服务器断电后恢复元数据。fsimage就是元数据的磁盘镜像。

为什么要有edits日志?

没有这个日志,当我们修改元数据,就只能修改fsimage文件,修改磁盘文件是效率极低的。edits日志只能顺序追加,记录的是元数据的操作(类似redis的AOF),这个效率是极高的。后面通过 SecondaryNameNode 的定期合并 生成新的fsimage,这样保证了元数据高效备份到磁盘。

DataNode 工作原理

DataNode用于存储文件的表现形式block,还存储了block的长度,校验和,以及时间戳。

DataNode启动后向NameNode注册 当前可用的block信息,以后每隔(N小时)向NameNode上报所有的block信息 。

DataNode同时每隔3秒向NameNode发送心跳,如果超过10分钟没有收到某个datanode的心跳,则认为该节点不可用。

HDFS Shell基本命令

1.查看有哪些命令

[root@hadoop1 ~]# 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>]


2.创建文件夹


# 创建文件夹
[root@hadoop1 ~]# hadoop fs -mkdir /hadluo
# 列出文件
[root@hadoop1 ~]# hadoop fs -ls /hadluo
#查看文件
[root@hadoop1 ~]# hadoop fs -cat /hadluo/b.txt
# 跟linux一样 ,还有 rm -r,tail ,cp,mv,du


3.上传


#从本地剪切上传到hdfs
[root@hadoop1 local]# hadoop fs -moveFromLocal /usr/local/b.txt /hadluo
#从本地复制上传到hdfs
[root@hadoop1 local]# hadoop fs -put /usr/local/b.txt /hadluo
#追加一个文件到已经存在的文件末尾
[root@hadoop1 local]# hadoop fs -appendToFile /usr/local/a.txt /hadluo/b.txt


4.下载


# 拷贝到本地
[root@hadoop1 local]# hadoop fs -get /hadluo/b.txt ./


HDFS Java Api

引入 maven:


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


java代码:


public class HDFS {
    public static void main(String[] args) throws IOException, URISyntaxException, InterruptedException {
        Configuration configuration = new Configuration();
        // 设置副本数
        configuration.set("dfs.replication", "2");
        // 创建客户端
        FileSystem fs = FileSystem.get(new URI("hdfs://hadoop1:8020"), configuration, "root");
        // 创建文件夹
        fs.mkdirs(new Path("/abc"));
        // 上传
        fs.copyFromLocalFile(new Path("C:\\Users\\youh\\Desktop\\斗店爬虫v0.0.1\\geckodriver.exe"), new Path("/abc"));
        // 下载
        fs.copyToLocalFile(new Path("/abc/geckodriver.exe"), new Path("C:\\Users\\youh\\Desktop\\斗店爬虫v0.0.1"));
        // 删除 参数2:是否递归删除
        fs.delete(new Path("/abc"), false);
        // 文件移动 并且 改名
        fs.rename(new Path("/abc/geckodriver.exe"), new Path("/klk/geckodriver22.exe"));
        // 递归遍历文件夹
        RemoteIterator<LocatedFileStatus> it = fs.listFiles(new Path("/abc"), false);
        while (it.hasNext()) {
            LocatedFileStatus file = it.next();
            System.out.println("文件名:" + file.getPath().getName());
            System.out.println("块存储信息:" + file.getBlockLocations());
            System.out.println("副本数:" + file.getReplication());
        }
        // 判断是否是文件
        for (FileStatus status : fs.listStatus(new Path("/abc"))) {
            System.out.println("是否是文件: " + status.isFile());
        }
        // 关闭客户端
        fs.close();
    }
}

总结

HDFS是存储基石 , MapReduce负责计算(后面会介绍), 今天我们详细讲解了hdfs的存储系统, 就是一个核心思想: 分而治之。