HDFS

优点

高容错性

  • 数据自动保存多个副本,通过增加多个副本的形式提高容错性
  • 某个副本丢失以后,可以自动恢复

image-20210920111031409

image-20210920111041386

适合处理大数据

数据规模:能够处理数据规模达到GB、TB甚至PB级别的数据

文件规模:能够处理百万规模以上的文件数量,数量相当之大

可构建在廉价机器上

通过多副本机制,提高可靠性

缺点

不适合低延时数据访问

无法毫秒级存储

无法高效地对大量小文件进行存储

存储大量小文件会占用NameNode大量的内存来存储文件目录和块信息。这样是不可取的,因为NameNode的内存总是有限的。

小文件存储的寻址时间会超过读取时间,违反了HDFS的设计目标

不支持并发写入、文件随机修改

一个文件只能有一个写,不允许多个线程同时写。

仅支持数据append(追加),不支持文件的随机修改。

组成架构

image-20210920112324285

NameNode(nn) Master

  1. 管理HDFS的名称空间。

    元数据

  2. 配置副本策略;

    每个文件有多少副本

  3. 管理数据块(Block)映射信息。

    副本存储位置

  4. 处理客户端读写请求。

DataNode Slave

NameNode下达命令,DataNode执行实际的操作。

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

Client

客户端

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

Secondary NameNode

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

  1. 辅助NameNode,分担其工作量,比如定期合并Fsimage和Edits,并推送给NameNode
  2. 在紧急情况下,可辅助恢复NameNode

Block

HDFS中的文件在物理上是分块存储(Block),块的大小可以通过配置参数(dfs.blocksize)来规定,默认大小在2.x、3.x是128M,在1.x是64M

<property>
  <name>dfs.blocksize</name>
  <value>134217728</value>
  <description>
      The default block size for new files, in bytes.
      You can use the following suffix (case insensitive):
      k(kilo), m(mega), g(giga), t(tera), p(peta), e(exa) to specify the size (such as 128k, 512m, 1g, etc.),
      Or provide complete size in bytes (such as 134217728 for 128 MB).
  </description>
</property>

如果寻址时间约为10ms,即查找到目标block的时间为10ms

寻址时间传输时间的1%时,则为最佳状态。

而目前磁盘的传输速率普遍为100MB/s

如果为200~300MB/s,则数据块大小取256M

HDFS块大小设置主要取决于磁盘传输速率

太小,增加寻址时间吗,程序一直在找块的开始位置

太大,从磁盘传输数据的时间会明显大于定位这个块开始位置所需的时间,导致程序在处理这块数据时会非常慢(计算的单元是块)。

Shell命令

datanode命令

开启本地的datanode

hdfs --daemon start datanode

文件相关命令

以下两个等效

  • hadoop fs

  • hdfs dfs

基本

-help rm

查看rm帮助

-mkdir /xxx

创建xxx目录

-ls

类似于linux的ls

-cat

类似于linux的cat

-chgrp

-chmod

-chown

同linux

-cp

同linux

-mv

同linux

-tail

同linux

-rm

删除文件或文件夹

-rm -r

递归删除目录及目录里的内容

-du

统计文件大小

image-20210921130934053

-du -h 人性化

-du -s 显示总计

-setrep num targetfile:设置HDFS中文件的副本数量

上传

-moveFromLocal local targetdir

将本地文件移动到hdfs指定目录

-copyFromLocal local targetdir

将本地文件复制到hdfs指定目录

-put local targetdir

等同于-copyFromLocal

-appendToFile localfile targetfile

将本地文件追加到hdfs指定文件末尾

下载

-copyToLocal target localdir

将目标文件拷贝到本地

-get

等同于-copyToLocal

安装hadoop的windows依赖

https://gitee.com/eeasy/hadoop_windows_dependency

https://github.com/steveloughran/winutils

创建Maven项目

dependency

<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-client</artifactId>
    <version>3.3.1</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.32</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.java (API)

package com.hayaku.hdfs;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

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

public class HdfsClient {

    private FileSystem fs;
    @BeforeEach
    public void init() throws IOException, InterruptedException, URISyntaxException {
        // 内部通讯端口8020
        URI uri = new URI("hdfs://hadoop102:8020");
        Configuration entries = new Configuration();
        entries.set("dfs.replication", "7");
        String user = "root";
        fs = FileSystem.get(uri, entries, user);
    }
    @AfterEach
    public void close() throws IOException {
        // 关闭资源
        fs.close();
    }

    @Test
    // 新建目录
    public void testMkdir() throws URISyntaxException, IOException, InterruptedException {
        // 创建文件夹
        Path path = new Path("/hello/hdfs1");
        fs.mkdirs(path);
    }

    @Test
    // 上传
    public void testPut() throws IOException {
        Path src = new Path("C:\\Users\\86191\\OneDrive\\桌面\\doctest测试框架\\doctest.h");
        Path dest = new Path("hdfs://hadoop102/doctest");
        // 是否删除原数据、是否允许覆盖、原数据路径、目的地路径
        fs.copyFromLocalFile(false, true, src, dest);
    }

    @Test
    // 下载
    public void testDownload() throws IOException {
        Path src = new Path("/doctest/doctest.h");
        Path dest = new Path("C:\\Users\\86191\\OneDrive\\桌面");
        // 是否删除原文件,源路径,目的地路径,使用原试本地文件系统(false则会下载crc文件)
        fs.copyToLocalFile(false, src, dest, false);
    }

    @Test
    // 删除
    public void testDelete() throws IOException {
        Path path1 = new Path("/doctest/doctest.h");
        Path path2 = new Path("/doctest");
        Path path3 = new Path("/a/b");
        // 如果要删除一个非空目录,则要指定递归删除
        fs.delete(path1,false);
        fs.delete(path2, false);
        fs.mkdirs(path3);
        fs.delete(new Path("/a"), true);
    }

    @Test
    // 移动
    public void testMove() throws IOException {
        Path dst = new Path("/sunquan.txt");
        Path src = new Path("/sanguo/wuguo.txt");
        fs.rename(src, dst);
    }

    @Test
    // 获取文件信息
    public void testDetail() throws IOException {
        RemoteIterator<LocatedFileStatus> listFiles = fs.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());
            BlockLocation[] blockLocations = fileStatus.getBlockLocations();
            System.out.println(Arrays.toString(blockLocations));
        }
    }

    @Test
    // 判断是文件还是目录
    public void testIsfile() throws IOException {
        FileStatus[] fileStatuses = fs.listStatus(new Path("/"));
        for (FileStatus status : fileStatuses) {
            if (status.isFile()) {
                System.out.println("File: " + status.getPath().getName());
            } else {
                System.out.println("Directory: " + status.getPath().getName());
            }
        }
    }
}

配置的优先级

代码 > resource中的hdfs-site.xml > hdfs-site.xml > hdfs-default.xml

HDFS读写数据流程

写详细步骤

  1. 客户端向NameNode发出写文件请求。
  2. 检查是否已存在文件、检查权限。若通过检查,直接先将操作写入EditLog,并返回输出流对象。
    (注:WAL,write ahead log,先写Log,再写内存,因为EditLog记录的是最新的HDFS客户端执行所有的写操作。如果后续真实写操作失败了,由于在真实写操作之前,操作就被写入EditLog中了,故EditLog中仍会有记录,我们不用担心后续client读不到相应的数据块,因为在第5步中DataNode收到块后会有一返回确认信息,若没写成功,发送端没收到确认信息,会一直重试,直到成功)
  3. client端按block(128MB)的块切分文件。
  4. client将NameNode返回的分配的可写的DataNode列表和Data数据一同发送给最近的第一个DataNode节点,此后client端和NameNode分配的多个DataNode构成pipeline管道,client端向输出流对象中写数据。client每向第一个DataNode写入一个packet,这个packet便会直接在pipeline里传给第二个、第三个…DataNode。
    (注:并不是写好一个块或一整个文件后才向后分发)
  5. 每个DataNode写完一个块后,会返回确认信息。
    (注:并不是每写完一个packet后就返回确认信息,个人觉得因为packet中的每个chunk都携带校验信息,没必要每写一个就汇报一下,这样效率太慢。正确的做法是写完一个block块后,对校验信息进行汇总分析,就能得出是否有块写错的情况发生)
  6. 写完数据,关闭输出流。
  7. 发送完成信号给NameNode。
    (注:发送完成信号的时机取决于集群是强一致性还是最终一致性,强一致性则需要所有DataNode写完后才向NameNode汇报。最终一致性则其中任意一个DataNode写完后就能单独向NameNode汇报,HDFS一般情况下都是强调强一致性)

副本节点选择

距离指的是到最近公共祖先的距离之和

image-20210929094117173

读详细步骤

  1. client访问NameNode,查询元数据信息,获得这个文件的数据块位置列表,返回输入流对象。
  2. 就近挑选一台datanode服务器(同时考虑负载均衡),请求建立输入流 。
  3. DataNode向输入流中中写数据,以packet为单位来校验。
  4. 关闭输入流

读block是串行读,读完block1再读block2,追加

image-20211001085609177

NameNode和2nn工作流程

企业中基本不用2nn,而是用2个nn搭建高可用

hadoop-3.3.1/data/dfs/name/current

fsimage

HDFS文件系统元数据的一个永久性的检查点,其中包含HDFS文件系统的所有目录和文件inode的序列化信息。

使用oiv查看fsimage文件:

hdfs oiv -p 文件类型 -i 镜像文件 -o 转换后文件输出路径

hdfs oiv -p XML -i fsimage_0000000000000000392 -o /opt/software/image.xml

Edits

存放HDFS文件系统的所有更新操作的路径,文件系统客户端执行的所有写操作首先会被记录到Edits文件中

使用oev查看edits文件:

hdfs oev -p 文件类型 -i 编辑日志 -o 转换后文件输出路径

hdfs oev -p XML -i edits_0000000000000000396-0000000000000000402 -o /opt/software/edits2.xml

只有在NameNode重启时,edits才会合并到fsimage文件中,从而得到一个文件系统的最新快照。但是在生产环境集群中的NameNode是很少重启的,这意味者当NameNode运行来很长时间后,edits文件会变的很大。在这种情况下就会出现下面这些问题:

  1. edits文件会变的很大,如何去管理这个文件?
  2. NameNode的重启会花费很长的时间,因为有很多改动要合并到fsimage文件上。
  3. 如果NameNode宕掉了,那我们就丢失了很多改动,因为此时的fsimage文件时间戳比较旧。

因此为了克服这个问题,我们需要一个易于管理的机制来帮助我们减小edits文件的大小和得到一个最新的fsimage文件,这样也会减小在NameNode上的压力。而Secondary NameNode就是为了帮助解决上述问题提出的,它的职责是合并NameNode的edits到fsimage文件中。如图所示:

img

seen_txid

seen_txid文件保存的是一个数字,就是最后一个edits_的数字

image-20211001110647947

2nn合并edits到fsimage流程

image-20211001091252859

2nn默认每小时检查一次

image-20211001112915792

如果edits数超过默认100 0000也会进行合并,默认每60秒查询一次uncheckpointed transactions

image-20211001113440292

DataNode

提供真实文件数据的存储服务。

image-20211001150650940

DataNode与NameNode

image-20211001151147462

超出TimeOut仍没有收到心跳则认为不可用

TimeOut = 2 * dfs.namenode.heartbeat.recheck-interval + 10 * dfs.heartbeat.interval

image-20211001161427672

image-20211001161550311

上报周期

默认每6小时上报

image-20211001151954288

自查周期

默认每6小时扫描自己的块

image-20211001152142889

crc校验

循环冗余校验(Cyclic Redundancy Check)

image-20211001160115617

posted @ 2021-10-01 20:02  iku-iku-iku  阅读(149)  评论(0编辑  收藏  举报