一文弄懂HDFS

产生背景

随着数据量越来越大,在一个操作系统存不下所有的数据,那么就分配到更多的操作系统管理的磁盘中,但是不方便管理和维护,迫切需要一种系统来管理多台机器上的文件,这就是分布式文件管理系统,HDFS只是分布式文件管理系统中的一种。

HDFS(Hadoop Distributed File System),它是一个文件系统,用于存储文件,通过目录树来定位文件; 其次,它是分布式的,由很多服务器联合起来来实现其功能,集群中的服务器有各自的角色。

HDFS的使用场景,适合一次写入,多次读出的场景,且不支持对文件的修改,适合用来做数据分析。

HDFS优缺点

优点
高容错性

  1. 数据自动保存多个副本,它通过增加副本的形式,提高容错性。


    image.png
  2. 某一个副本丢失以后,它可以自动恢复


    image.png

适合处理大数据

  1. 数据规模: 能够处理数据规模达到GB, TB,甚至PB级别的数据
  2. 文件规模: 能够处理百万规模以上的文件数量。

可构建在廉价机器上,通过多副本机制,提高可靠性

缺点

  1. 不适合低延时的数据访问,比如毫秒级别的存储数据,是做不到的
  2. 无法高效的对大量小文件进行存储。
  • 存储大量小文件的话,它会占用NameNode大量的内存来存储文件目录和块信息,这样是不可取的,因为namenode的内存总是有限的。
  • 小文件存储的寻址时间会超过读取时间,它违反了HDFS的设计目标。
  1. 不支持并发写入,文件随机修改。
  • 一个文件只能一个写,不允许多个线程同时写;
  • 仅支持数据追加(append),不支持对文件的随机修改。


    一文弄懂HDFS_第1张图片
    image.png

HDFS组成架构

一文弄懂HDFS_第2张图片
image.png

NameNode: 就是Master, 它是一个主管, 管理者

  • 管理HDFS的namespace
  • 配置副本策略
  • 管理数据块(Block)映射信息
  • 处理客户端读写请求

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

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

Client 就是客户端

  • 文件切分。文件上传HDFS的时候,Client将文件切分成一个一个的Block,然后进行上传。
  • 与NameNode交互,获取文件的位置信息
  • 与DataNode交互,读取或者写入数据
  • Client提供一些命令来管理HDFS,比如NameNode格式化
  • Client可以通过一些命令来访问HDFS,比如对HDFS增删改查操作。

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

  • 辅助NameNode,分担其工作量,比如定期合并Fsimage和Edits, 并推送给NameNode
  • 在紧急情况下,可辅助恢复NameNode(做HA)

HDFS 文件块大小

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

一文弄懂HDFS_第3张图片
image.png

为什么块的大小不能设置太小或者太大?

  • HDFS块设置的太小,会增加寻址时间,(程序一直在找块的开始位置)
  • 如果块设置的太大,从磁盘传输数据的时间会明显大于定位这个块开始位置所需的时间,导致程序在处理这块数据时,会非常慢

HDFS块的大小设置和磁盘传输速率有很大关系。

HDFS操作

package com.zouxxyy.hdfs;

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

import java.io.FileInputStream;
import java.io.IOException;
import java.net.URI;

public class HDFSClient {

    private FileSystem fs;

    @Before
    public void before() throws IOException, InterruptedException {
        // 获取HDFS的抽象对象
        fs = FileSystem.get(URI.create("hdfs://server-2:9000"), new Configuration(), "xxx");
    }

    @Test
    public void put() throws IOException, InterruptedException {

        Configuration configuration = new Configuration();

        configuration.setInt("dfs.replication", 1);

        fs = FileSystem.get(URI.create("hdfs://server-2:9000"), configuration, "xxx");

        // 本地文件上传到HDFS
        fs.copyFromLocalFile(new Path("data/input/wordCount/1.txt"), new Path("/"));
    }

    @Test
    public void get() throws IOException{

         // HDFS文件下载到本地
         fs.copyToLocalFile(new Path("/1.txt"), new Path("./"));
    }

    @Test
    public void rename() throws IOException{

        // HDFS重命名
        fs.rename(new Path("/1.txt"), new Path("/2.txt"));
    }

    @Test
    public void delete() throws IOException{

        // HDFS删除
        boolean delete = fs.delete(new Path("/1.txt"), true);
        if (delete) {
            System.out.println("删除成功");
        }
        else{
            System.out.println("删除失败");
        }
    }

    @Test
    public void append() throws IOException{

        // HDFS 文件追加测试
        FSDataOutputStream append = fs.append(new Path("/2.txt"), 1024);
        FileInputStream open = new FileInputStream("data/input/wordCount/1.txt");
        IOUtils.copyBytes(open, append, 1024, true);
    }

    @Test
    public void ls() throws IOException{

        // fileStatuses包含文件和文件夹
        FileStatus[] fileStatuses = fs.listStatus(new Path("/"));

        for (FileStatus fileStatus : fileStatuses) {
            if(fileStatus.isFile()) {
                System.out.println("文件:");
                System.out.println(fileStatus.getPath());
                System.out.println(fileStatus.getOwner());
            }
            else {
                System.out.println("文件夹:");
                System.out.println(fileStatus.getModificationTime());
                System.out.println(fileStatus.getPermission());
            }
        }
    }

    @Test
    public void listFiles() throws IOException {

        // 注意listFiles方法只能得到文件
        RemoteIterator files = fs.listFiles(new Path("/"), true);

        while (files.hasNext()) {
            LocatedFileStatus file = files.next();

            System.out.println("===========================");
            System.out.println(file.getPath());
            System.out.println("块信息:");
            BlockLocation[] blockLocations = file.getBlockLocations();
            for (BlockLocation blockLocation : blockLocations) {
                String[] hosts = blockLocation.getHosts();
                System.out.print("块在: ");
                for(String host : hosts) {
                    System.out.println(host + " ");
                }
            }
        }
    }

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

}

参数优先级: 客户端代码中设置的值 > ClassPath下用户自定义的配置文件 > 服务器的默认配置

上面的API操作HDFS都是框架封装好的,如果我们想自己实现上述API呢?

HDFS的IO流操作

更底层一点的操作

package com.zouxxyy.hdfs;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.junit.Test;

public class HDFSIO {

    // 把本地e盘上的banhua.txt文件上传到HDFS根目录
    @Test
    public void putFileToHDFS() throws IOException, InterruptedException, URISyntaxException{
        
        // 1 获取对象
        Configuration conf = new Configuration();
        FileSystem fs = FileSystem.get(new URI("hdfs://server-2:9000"), conf , "xxx");
        
        // 2 获取输入流
        FileInputStream fis = new FileInputStream(new File("data/input/wordCount/1.txt"));
        
        // 3 获取输出流
        FSDataOutputStream fos = fs.create(new Path("/test.txt"));
        
        // 4 流的对拷
        IOUtils.copyBytes(fis, fos, conf);
        
        // 5 关闭资源
        IOUtils.closeStream(fos);
        IOUtils.closeStream(fis);
        fs.close();
    }
    
    
    // 从HDFS上下载banhua.txt文件到本地e盘上
    @Test
    public void getFileFromHDFS() throws IOException, InterruptedException, URISyntaxException{
        
        // 1 获取对象
        Configuration conf = new Configuration();
        FileSystem fs = FileSystem.get(new URI("hdfs://server-2:9000"), conf , "xxx");
        
        // 2 获取输入流
        FSDataInputStream fis = fs.open(new Path("/banhua.txt"));
        
        // 3 获取输出流
        FileOutputStream fos = new FileOutputStream(new File("e:/banhua.txt"));
        
        // 4 流的对拷
        IOUtils.copyBytes(fis, fos, conf);
        
        // 5 关闭资源
        IOUtils.closeStream(fos);
        IOUtils.closeStream(fis);
        fs.close();
    }
    
    // 下载第一块
    @Test
    public void readFileSeek1() throws IOException, InterruptedException, URISyntaxException{
        
        // 1 获取对象
        Configuration conf = new Configuration();
        FileSystem fs = FileSystem.get(new URI("hdfs://server-2:9000"), conf , "xxx");
        
        // 2 获取输入流
        FSDataInputStream fis = fs.open(new Path("/1.txt"));

        // 3 获取输出流
        FileOutputStream fos = new FileOutputStream(new File("./1.txt.part1"));
        
        // 4 流的对拷(只拷贝128m)
        byte[] buf = new byte[1024];
        for (int i = 0; i < 1024 * 128; i++) {
            fis.read(buf);
            fos.write(buf);
        }
        
        // 5 关闭资源
        IOUtils.closeStream(fos);
        IOUtils.closeStream(fis);
        fs.close();
    }
    
    // 下载第二块
    @SuppressWarnings("resource")
    @Test
    public void readFileSeek2() throws IOException, InterruptedException, URISyntaxException{
        
        // 1 获取对象
        Configuration conf = new Configuration();
        FileSystem fs = FileSystem.get(new URI("hdfs://server-2:9000"), conf , "xxx");
        
        // 2 获取输入流
        FSDataInputStream fis = fs.open(new Path("/hadoop-2.7.2.tar.gz"));
        
        // 3 设置指定读取的起点
        fis.seek(1024*1024*128);
        
        // 4 获取输出流
        FileOutputStream fos = new FileOutputStream(new File("e:/hadoop-2.7.2.tar.gz.part2"));
        
        // 5 流的对拷
        IOUtils.copyBytes(fis, fos, conf);
        
        // 6 关闭资源
        IOUtils.closeStream(fos);
        IOUtils.closeStream(fis);
        fs.close();
    }
    
    
    
    
    
    
    
}

HDFS的数据流

写数据流程

一文弄懂HDFS_第4张图片
image.png

  1. 客户端通过Distributed FileSystem模块向NameNode请求上传文件,NameNode检查目标文件是否已存在,父目录是否存在。
  2. NameNode返回是否可以上传
  3. 客户端请求第一个Block上传到哪几个DataNode服务器上。
  4. NameNode返回3个DataNode节点,分别为dn1,dn2,dn3
  5. 客户端通过FSDataOutputStream模块请求dn1上传数据,dn1收到请求会继续调用dn2,然后dn2调用dn3,
  6. dn1、dn2、dn3逐级应答客户端。
  7. 客户端开始往dn1上传第一个block(先从磁盘读取数据放到一个本地内存缓存)以packet为单位, dn1收到第一个packet就会传给dn2,dn2传给dn3。
  8. 当一个Block传输完成之后,客户端再次请求NameNode,请求下一个Block传到哪几个DataNode服务器上。(重复执行3-7)

节点距离计算

在HDFS写数据的过程中,NameNode会选择距离带上传数据最近的DataNode接收数据。这个最近距离怎么计算呢?

一文弄懂HDFS_第5张图片
image.png

机架感知

For the common case, when the replication factor is three, HDFS’s placement policy is to put one replica on the local machine if the writer is on a datanode, otherwise on a random datanode, another replica on a node in a different (remote) rack, and the last on a different node in the same remote rack.

在默认情况下,一个文件有三个副本。当writer(执行写请求的客户端)在datanode上时,第一个副本写在本机上;当writer没在datanode上时,随机选一个机架里的datanode放置。第二个副本放在和第一个副本不同的机架上的随机daanode上。第三个副本和第二个副本在同一个机架,但是在不同的datanode上。

更多副本:随机节点放置。

一文弄懂HDFS_第6张图片
image.png

HDFS读数据流程

一文弄懂HDFS_第7张图片
image.png
  1. 客户端通过DistributedFileSystem向NameNode请求下载文件,NameNode通过查询元数据,找到文件块所在的DataNode地址。(按距离顺序)
  2. 客户端挑选一台DataNode服务器,请求读取数据
  3. DataNode开始传输数据给客户端(从磁盘里面读取数据输入流,以packet为单位)
  4. 客户端以packet为单位接收,先在本地缓存,然后写入目标文件。

SecondaryNameNode

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

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

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

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

一文弄懂HDFS_第8张图片
image.png
  1. 第一阶段:NameNode启动
    (1)第一次启动NameNode格式化后,创建Fsimage和Edits文件。如果不是第一次启动,直接加载编辑日志和镜像文件到内存。
    (2)客户端对元数据进行增删改的请求。
    (3)NameNode记录操作日志,更新滚动日志。
    (4)NameNode在内存中对元数据进行增删改。

第二阶段:Secondary NameNode工作
(1)Secondary NameNode询问NameNode是否需要CheckPoint。直接带回NameNode是否检查结果。
(2)Secondary NameNode请求执行CheckPoint。
(3)NameNode滚动正在写的Edits日志。
(4)将滚动前的编辑日志和镜像文件拷贝到Secondary NameNode。
(5)Secondary NameNode加载编辑日志和镜像文件到内存,并合并。
(6)生成新的镜像文件fsimage.chkpoint。
(7)拷贝fsimage.chkpoint到NameNode。
(8)NameNode将fsimage.chkpoint重新命名成fsimage

NN和2NN工作机制详解

  • Fsimage:NameNode内存中元数据序列化后形成的文件。
  • Edits:记录客户端更新元数据信息的每一步操作(可通过Edits运算出元数据)。

NameNode启动时,先滚动Edits并生成一个空的edits.inprogress,然后加载Edits和Fsimage到内存中,此时NameNode内存就持有最新的元数据信息。Client开始对NameNode发送元数据的增删改的请求,这些请求的操作首先会被记录到edits.inprogress中(查询元数据的操作不会被记录在Edits中,因为查询操作不会更改元数据信息),如果此时NameNode挂掉,重启后会从Edits中读取元数据的信息。然后,NameNode会在内存中执行元数据的增删改的操作。

由于Edits中记录的操作会越来越多,Edits文件会越来越大,导致NameNode在启动加载Edits时会很慢,所以需要对Edits和Fsimage进行合并(所谓合并,就是将Edits和Fsimage加载到内存中,照着Edits中的操作一步步执行,最终形成新的Fsimage)。SecondaryNameNode的作用就是帮助NameNode进行Edits和Fsimage的合并工作。

SecondaryNameNode首先会询问NameNode是否需要CheckPoint(触发CheckPoint需要满足两个条件中的任意一个,定时时间到和Edits中数据写满了)。直接带回NameNode是否检查结果。

SecondaryNameNode执行CheckPoint操作,首先会让NameNode滚动Edits并生成一个空的edits.inprogress,滚动Edits的目的是给Edits打个标记,以后所有新的操作都写入edits.inprogress,其他未合并的Edits和Fsimage会拷贝到SecondaryNameNode的本地,然后将拷贝的Edits和Fsimage加载到内存中进行合并,生成fsimage.chkpoint,然后将fsimage.chkpoint拷贝给NameNode,重命名为Fsimage后替换掉原来的Fsimage。NameNode在启动时就只需要加载之前未合并的Edits和Fsimage即可,因为合并过的Edits中的元数据信息已经被记录在Fsimage中。

一文弄懂HDFS_第9张图片
image.png
  1. oiv查看Fsimage文件
    (1)查看oiv和oev命令
    [atguigu@hadoop102 current]hdfs oiv apply the offline fsimage viewer to an fsimage oev apply the offline edits viewer to an edits file (2)基本语法 hdfs oiv -p 文件类型 -i镜像文件 -o 转换后文件输出路径 (3)案例实操 [atguigu@hadoop102 current] pwd
    /opt/module/hadoop-2.7.2/data/tmp/dfs/name/current

[atguigu@hadoop102 current]$ hdfs oiv -p XML -i fsimage_0000000000000000025 -o /opt/module/hadoop-2.7.2/fsimage.xml

[atguigu@hadoop102 current]$ cat /opt/module/hadoop-2.7.2/fsimage.xml
将显示的xml文件内容拷贝到Eclipse中创建的xml文件中,并格式化。部分显示结果如下。


    16386
    DIRECTORY
    user
    1512722284477
    atguigu:supergroup:rwxr-xr-x
    -1
    -1


    16387
    DIRECTORY
    atguigu
    1512790549080
    atguigu:supergroup:rwxr-xr-x
    -1
    -1


    16389
    FILE
    wc.input
    3
    1512722322219
    1512722321610
    134217728
    atguigu:supergroup:rw-r--r--
    
        
            1073741825
            1001
            59
        
    

思考:可以看出,Fsimage中没有记录块所对应DataNode,为什么?

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

CheckPoint时间设置

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

[hdfs-default.xml]

dfs.namenode.checkpoint.period
3600

(2)一分钟检查一次操作次数,3当操作次数达到1百万时,SecondaryNameNode执行一次。

dfs.namenode.checkpoint.txns
1000000
操作动作次数


dfs.namenode.checkpoint.check.period
60
1分钟检查一次操作次数

NameNode故障处理

NameNode故障后,可以采用如下两种方法恢复数据。
方法一:将SecondaryNameNode中数据拷贝到NameNode存储数据的目录;

  1. kill -9 NameNode进程
  2. 删除NameNode存储的数据(/opt/module/hadoop-2.7.2/data/tmp/dfs/name)
    [atguigu@hadoop102 hadoop-2.7.2]$ rm -rf /opt/module/hadoop-2.7.2/data/tmp/dfs/name/*
  3. 拷贝SecondaryNameNode中数据到原NameNode存储数据目录
    [atguigu@hadoop102 dfs]$ scp -r atguigu@hadoop104:/opt/module/hadoop-2.7.2/data/tmp/dfs/namesecondary/* ./name/
  4. 重新启动NameNode
    [atguigu@hadoop102 hadoop-2.7.2]$ sbin/hadoop-daemon.sh start namenode

DataNode

一文弄懂HDFS_第10张图片
image.png

1)一个数据块在DataNode上以文件形式存储在磁盘上,包括两个文件,一个是数据本身,一个是元数据包括数据块的长度,块数据的校验和,以及时间戳。
2)DataNode启动后向NameNode注册,通过后,周期性(1小时)的向NameNode上报所有的块信息。
3)心跳是每3秒一次,心跳返回结果带有NameNode给该DataNode的命令如复制块数据到另一台机器,或删除某个数据块。如果超过10分钟没有收到某个DataNode的心跳,则认为该节点不可用。
4)集群运行中可以安全加入和退出一些机器。

DataNode节点保证数据完整性的方法。
1)当DataNode读取Block的时候,它会计算CheckSum。
2)如果计算后的CheckSum,与Block创建时值不一样,说明Block已经损坏。
3)Client读取其他DataNode上的Block。
4)DataNode在其文件创建后周期验证CheckSum

掉线时限参数设置

一文弄懂HDFS_第11张图片
image.png

HDFS 2.X新特性

集群间数据拷贝
1.scp实现两个远程主机之间的文件复制
scp -r hello.txt root@hadoop103:/user/atguigu/hello.txt // 推 push
scp -r root@hadoop103:/user/atguigu/hello.txt hello.txt // 拉 pull
scp -r root@hadoop103:/user/atguigu/hello.txt root@hadoop104:/user/atguigu //是通过本地主机中转实现两个远程主机的文件复制;如果在两个远程主机之间ssh没有配置的情况下可以使用该方式。
2.采用distcp命令实现两个Hadoop集群之间的递归数据复制
[atguigu@hadoop102 hadoop-2.7.2]$ bin/hadoop distcp
hdfs://haoop102:9000/user/atguigu/hello.txt hdfs://hadoop103:9000/user/atguigu/hello.txt

小文件存档

一文弄懂HDFS_第12张图片
image.png

案例实操

(1)需要启动YARN进程
[atguigu@hadoop102 hadoop-2.7.2]$ start-yarn.sh
(2)归档文件
    把/user/atguigu/input目录里面的所有文件归档成一个叫input.har的归档文件,并把归档后文件存储到/user/atguigu/output路径下。
[atguigu@hadoop102 hadoop-2.7.2]$ bin/hadoop archive -archiveName input.har –p  /user/atguigu/input   /user/atguigu/output
(3)查看归档
[atguigu@hadoop102 hadoop-2.7.2]$ hadoop fs -lsr /user/atguigu/output/input.har
[atguigu@hadoop102 hadoop-2.7.2]$ hadoop fs -lsr har:///user/atguigu/output/input.har
(4)解归档文件
[atguigu@hadoop102 hadoop-2.7.2]$ hadoop fs -cp har:/// user/atguigu/output/input.har/*    /user/atguigu

HDFS HA

HDFS-HA工作要点

  1. 元数据管理方式需要改变
    内存中各自保存一份元数据;
    Edits日志只有Active状态的NameNode节点可以做写操作;
    两个NameNode都可以读取Edits;
    共享的Edits放在一个共享存储中管理(qjournal和NFS两个主流实现)
  2. 需要一个状态管理功能模块
    实现了一个zkfailover,常驻在每一个namenode所在的节点,每一个zkfailover负责监控自己所在NameNode节点,利用zk进行状态标识,当需要进行状态切换时,由zkfailover来负责切换,切换时需要防止brain split现象的发生。
  3. 隔离(Fence),即同一时刻仅仅有一个NameNode对外提供服务

自动故障转移为HDFS部署增加了两个新组件:ZooKeeper和ZKFailoverController(ZKFC)进程,如图3-20所示。ZooKeeper是维护少量协调数据,通知客户端这些数据的改变和监视客户端故障的高可用服务。HA的自动故障转移依赖于ZooKeeper的以下功能:
1)故障检测:集群中的每个NameNode在ZooKeeper中维护了一个持久会话,如果机器崩溃,ZooKeeper中的会话将终止,ZooKeeper通知另一个NameNode需要触发故障转移。
2)现役NameNode选择:ZooKeeper提供了一个简单的机制用于唯一的选择一个节点为active状态。如果目前现役NameNode崩溃,另一个节点可能从ZooKeeper获得特殊的排外锁以表明它应该成为现役NameNode。
ZKFC是自动故障转移中的另一个新组件,是ZooKeeper的客户端,也监视和管理NameNode的状态。每个运行NameNode的主机也运行了一个ZKFC进程,ZKFC负责

1)健康监测:ZKFC使用一个健康检查命令定期地ping与之在相同主机的NameNode,只要该NameNode及时地回复健康状态,ZKFC认为该节点是健康的。如果该节点崩溃,冻结或进入不健康状态,健康监测器标识该节点为非健康的。
2)ZooKeeper会话管理:当本地NameNode是健康的,ZKFC保持一个在ZooKeeper中打开的会话。如果本地NameNode处于active状态,ZKFC也保持一个特殊的znode锁,该锁使用了ZooKeeper对短暂节点的支持,如果会话终止,锁节点将自动删除。
3)基于ZooKeeper的选择:如果本地NameNode是健康的,且ZKFC发现没有其它的节点当前持有znode锁,它将为自己获取该锁。如果成功,则它已经赢得了选举,并负责运行故障转移进程以使它的本地NameNode为Active。故障转移进程与前面描述的手动故障转移相似,首先如果必要保护之前的现役NameNode,然后本地NameNode转换为Active状态。

一文弄懂HDFS_第13张图片
image.png

HDFS Federation架构设计

NameNode架构的局限性
(1)Namespace(命名空间)的限制
由于NameNode在内存中存储所有的元数据(metadata),因此单个NameNode所能存储的对象(文件+块)数目受到NameNode所在JVM的heap size的限制。50G的heap能够存储20亿(200million)个对象,这20亿个对象支持4000个DataNode,12PB的存储(假设文件平均大小为40MB)。随着数据的飞速增长,存储的需求也随之增长。单个DataNode从4T增长到36T,集群的尺寸增长到8000个DataNode。存储的需求从12PB增长到大于100PB。

(2)隔离问题
由于HDFS仅有一个NameNode,无法隔离各个程序,因此HDFS上的一个实验程序就很有可能影响整个HDFS上运行的程序。
(3)性能的瓶颈
由于是单个NameNode的HDFS架构,因此整个HDFS文件系统的吞吐量受限于单个NameNode的吞吐量

能不能有多个NameNode?

一文弄懂HDFS_第14张图片
image.png

不同应用可以使用不同NameNode进行数据管理
图片业务、爬虫业务、日志审计业务
Hadoop生态系统中,不同的框架使用不同的NameNode进行管理NameSpace。(隔离性)
在hadoop1.0的架构中,HDFS的所有的元数据都放在一个namenode中,只有一个namespace(名字空间)。这样随着HDFS的数据越来越多,单个namenode的资源使用必然会达到上限,而且namenode的负载也会越来越高,限制了HDFS的性能。

在hadoop2.0架构中,namenode federation(联合)通过多个namenode/namespace把元数据的存储和管理分散到多个节点中,使到namenode/namespace可以通过增加机器来进行水平扩展,并且能把单个namenode的负载分散到多个节点中,在HDFS数据规模较大的时候不会也降低HDFS的性能。还有可以通过多个namespace来隔离不同类型的应用,把不同类型应用的HDFS元数据的存储和管理分派到不同的namenode中。

一文弄懂HDFS_第15张图片
image.png

如果上图所示,一个block pool由属于同一个namespace的数据块组成,每个namenode管理一个namespace,即每个namenode负责存储和管理一个block pool的元数据。而每个datanode是会连接所有的namenode的,为所有的block pools所共享,即每个datanode都会存储所有的block pools的数据块。每个block pool通过namespace隔离开来,对一个block pool的操作不会影响另外一个block pool。

从配置和使用的角度来看,整个HDFS有一个唯一的clusterid,如“hellokitty”,它可以配置多个block pool/namespace(也叫name service),如“mycluster”和“yourcluster”。为了方便访问不同名字空间的目录和文件,federation还提供了一个类似linux的Client Side Mount Table的挂载机制,提供了一个统一的全局的文件系统视图(viewfs)。用户可以根据自己的需要把各个namespace挂载到一个叫做viewFS的文件系统视图的不同目录下。例如namespace/name service “mycluster”和“yourcluster”分别挂载到viewfs的“/my”和“/your”目录下,如下图所示:

一文弄懂HDFS_第16张图片
image.png
federation和HA

上面提到的每个namespace/name service配置一个namenode,这样这个namespace/name service的单点问题还是存在,因此可以给每个namespace/name service配置成HA。

假设我们有4台namenode,分别是namenode1,namenode2,namenode3,namenode4。其中namenode1和namenode2是namespace/name service“mycluster”的两个主备namenode节点,NN_ID分别是“mycluster”的“nn1”和“nn2”;而namenode3和namenode4是namespace/name service“yourcluster”的两个主备namenode节点,NN_ID分别是“yourcluster”的“nn1”和“nn2”。

“mycluster”和“yourcluster”分别挂载在viewfs的“/my”和“/your”目录下。

一文弄懂HDFS_第17张图片
image.png

一般1000台机器一下的中小规模的hadoop集群,一个namespace/name service就足够了,不需要考虑federation,以免增加不必要的复杂性。

你可能感兴趣的:(一文弄懂HDFS)