HDFS文件系统

HDFS(Hadoop Distributed File System) : 分布式文件系统,适合一次写入,多次读出的场景,不支持文件修改,适合做数据分析,不适合做网盘类似应用.

优点:

(1)高容错性 : 增加副本形式,提高容错性

(2)适合处理大数据 规模大,数据级别高(GB,TB,PB…)

(3)可构建在廉价机器上,通过增加副本提高可靠性

缺点:

(1)不适合实时性,低延迟数据访问(ms级做不到)

(2)大量小文件存储低效,Namenode大量内存来存索引和块信息,大量小文件存储寻址时间超过读取时间,不可取

(3)不支持并发写入和随机修改,只支持数据追加

HDFS的组成架构

hadoop doris 详细对比 hadoop sbin_hdfs

hadoop doris 详细对比 hadoop sbin_hdfs_02


hadoop doris 详细对比 hadoop sbin_hdfs_03

HDFS的Shell操作

  1. 基本语法
    bin/hadoop fs
    bin/hdfs dfs
    hdfs dfs 是hadoop fs的实现类.
    我一般常用 hdfs dfs xxx
  2. 常用命令 (基本上命令同Linux命令含义)
# 启动Hadoop集群
	sbin/start-dfs.sh
	sbin/start-yarn.sh
# -ls: 显示目录信息
	hdfs dfs -ls /
# -mkdir:在HDFS上创建目录
	hdfs dfs -mkdir -p /dev/input
# -moveFromLocal:从本地剪切粘贴到HDFS
	hdfs dfs  -moveFromLocal  ./wc.txt  /dev
# -appendToFile:追加一个文件到已经存在的文件末尾
	hdfs dfs -appendToFile one.txt /dev/wc.txt
# (-cat:显示文件内容
	hdfs dfs -cat /dev/wc.txt
# -chgrp 、-chmod、-chown:Linux文件系统中的用法一样,修改文件所属权限
	hdfs dfs  -chmod  777  /dev/wc.txt
	hdfs dfs  -chown  dev:dev /dev/wc.txt
# -copyFromLocal:从本地文件系统中拷贝文件到HDFS路径去(同-put)
	hdfs dfs -copyFromLocal README.txt /
# -copyToLocal:从HDFS拷贝到本地 同(-get)
	hdfs dfs -copyToLocal /dev/wc.txt ./
# -cp :从HDFS的一个路径拷贝到HDFS的另一个路径
	hdfs dfs -cp /dev/wc.txt /other.txt
# -mv:在HDFS目录中移动文件
	hdfs dfs -mv /other.txt /dev/
# -get:等同于copyToLocal,就是从HDFS下载文件到本地
	hdfs dfs -get /dev/wc.txt ./
# -getmerge:合并下载多个文件,比如HDFS的目录 /dev/test 下有多个文件:log.1, 		log.2,log.3,...
	hdfs dfs -getmerge /dev/test/* ./testmege.txt
# -put:等同于copyFromLocal
	hdfs dfs -put ./a.txt /dev/test/
# -tail:显示一个文件的末尾
	hdfs dfs -tail /dev/wc.txt
# -rm:删除文件或文件夹
	hdfs dfs -rm /dev/wc.txt
# -rmdir:删除空目录
	hdfs dfs -rmdir /emptydir
# -du统计文件夹的大小信息
	hdfs dfs -du -s -h /dev/
# -setrep:设置HDFS中文件的副本数量 ||| 是否真的有设置的那么多副本,得看DataNode数量,设置副本数量超过DataNode数量,当节点数达到才会真正有那么多副本
	hdfs dfs -setrep 8 /hadoop.tar.gz

HDFS-API操作

pom依赖:

<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-common</artifactId>
    <version>3.3.0</version>
</dependency>
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-client</artifactId>
    <version>3.3.0</version>
</dependency>
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-hdfs-client</artifactId>
    <version>3.3.0</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.69</version>
</dependency>

代码案例:

package com.example.hadoopdemo.test;

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

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 java.util.Arrays;

/**
 * <p>
 * hdfs 客户端
 * </p>
 *
 * @author f
 * @description hdfs api 操作
 */
public class HdfsClient {
    Configuration config = null;
    FileSystem fs = null;

    @Before
    public void before() throws URISyntaxException, IOException, InterruptedException {
        config = new Configuration();
        fs = FileSystem.get(new URI("hdfs://hadoop111:9000"), config, "dev");
    }

    //创建文件夹
    @Test
    public void mkdir() throws IOException {
//        要设置用户权限,否则会抛访问控制异常(无权限读写) org.apache.hadoop.security.AccessControlException
//        config.set("fs.defaultFS","hdfs://hadoop111:9000");
//        FileSystem fileSystem = FileSystem.get(config);
//        fileSystem.mkdirs(new Path("/idea/test"));
        fs.mkdirs(new Path("/idea/test"));
        fs.close();
        //如果文件夹已存在,hadoop什么也不做
        System.out.println("mkdir success...");
    }

    //上传
    @Test
    public void copyFromLocalFile() throws IOException {
//        参数优先级排序 : 代码中设置的值 > ClassPath下的用户自定义配置文件 > 服务器的默认配置
//        config.set("dfs.replication","3");
//        是否覆盖/是否删除源文件可以设置,有很多重载方法
        fs.copyFromLocalFile(false, true, new Path("README.md"), new Path("/"));
        fs.close();
        System.out.println("file update success...");
    }

    //下载
    @Test
    public void copyToLocalFile() throws IOException, URISyntaxException, InterruptedException {
//        默认在用户目录下找文件 /user/dev/...
        FileSystem fs = FileSystem.get(new URI("hdfs://hadoop111:9000"), config, "dev");
        fs.copyToLocalFile(false, new Path("/vvv.txt"), new Path("./"), false);
        fs.close();
        System.out.println("file update success...");
    }

    //文件夹/文件删除
    @Test
    public void delDir() throws IOException {
        // path,recursive 文件路径,递归
        boolean res = fs.delete(new Path("/wc.txt"), true);
        System.out.println(res);
        fs.close();
    }

    //文件名修改
    @Test
    public void fileRename() throws IOException {
        fs.rename(new Path("/vvv.txt"), new Path("/ddd.txt"));
        fs.close();
        System.out.println("ok");
    }

    //文件详情查看
    @Test
    public void catFileDetail() throws IOException {
        RemoteIterator<LocatedFileStatus> it = fs.listFiles(new Path("/ddd.txt"), true);
        while (it.hasNext()) {
            LocatedFileStatus fileStatus = it.next();

            System.out.printf("isDirectory: %s\n", fileStatus.isDirectory());
            System.out.printf("isFile: %s\n", fileStatus.isFile());
            System.out.printf("blockSize: %d\n", fileStatus.getBlockSize());
            System.out.printf("group: %s\n", fileStatus.getGroup());
            System.out.printf("len: %s\n", fileStatus.getLen());
            System.out.printf("owner: %s\n", fileStatus.getOwner());
            System.out.printf("modificationTime: %s\n", fileStatus.getModificationTime());
            System.out.printf("permission: %s\n", fileStatus.getPermission());
            System.out.printf("replication: %s\n", fileStatus.getReplication());
//            System.out.printf("symlink: %s\n",fileStatus.getSymlink());

            System.out.println("---------------------");

            // 获取存储的块信息
            BlockLocation[] blockLocations = fileStatus.getBlockLocations();
            for (BlockLocation blockLocation : blockLocations) {
                // 获取块存储的主机节点
                String[] hosts = blockLocation.getHosts();
                System.out.printf("存储主机名 : %s\n", Arrays.toString(hosts));
            }
        }
        // 3 关闭资源
        fs.close();
    }

    //自己尝试实现HDFS上传
    @Test
    public void putFileToHDFS() throws IOException {

        FileInputStream fis = new FileInputStream("pom.xml");
        //路径不存在,会自动创建
        FSDataOutputStream fos = fs.create(new Path("/user/dev/pom.xml"));
        IOUtils.copyBytes(fis, fos, config);

        IOUtils.closeStream(fos);
        IOUtils.closeStream(fis);
        fs.close();
        System.out.println("上传成功");
    }

    //自己尝试实现HDFS下载
    @Test
    public void getFileFromHDFS() throws IOException {

        FileOutputStream fos = new FileOutputStream(new File("abc"));
        FSDataInputStream fis = fs.open(new Path("/user/dev/pom.xml"));
        IOUtils.copyBytes(fis, fos, config);

        IOUtils.closeStream(fos);
        IOUtils.closeStream(fis);
        fs.close();
        System.out.println("下载成功");
    }

    //分块读取 01 (读块1)
    @Test
    public void readFileSeek01() throws IOException {

        FileOutputStream fos = new FileOutputStream(new File("seek01.tar.gz.part1"));
        FSDataInputStream fis = fs.open(new Path("/jdk-8u221-linux-x64.tar.gz"));
        byte[] buf = new byte[ 1024];
        for (int i = 0; i < 128 * 1024; i++) {
            fis.read(buf);
            fos.write(buf);
        }

        IOUtils.closeStream(fos);
        IOUtils.closeStream(fis);
        fs.close();
        System.out.println("第一个块读完了..."); //第二块怎么读取???
    }

    //分块读取 02 (读块2)
    @Test
    public void readFileSeek02() throws IOException, InterruptedException {

        FileOutputStream fos = new FileOutputStream(new File("seek02.tar.gz.part2"));
        FSDataInputStream fis = fs.open(new Path("/jdk-8u221-linux-x64.tar.gz"));
        //定位 offset.
        // Seek to the given offset.
        // desired offset to seek to.
        fis.seek(128*1024*1024);
        //如果你不想读完或者还有多个块,想到哪位置可以通过pos去判断位置,到哪stop read
        //long pos = fis.getPos();
        IOUtils.copyBytes(fis,fos,config);

        IOUtils.closeStream(fos);
        IOUtils.closeStream(fis);
        fs.close();
        System.out.println("第二个块读完了...");
        System.out.println("Windows cmd 合并命令 : type seek02.tar.gz.part2 >> seek01.tar.gz.part1");
        System.out.println("合并后,更改下第一个文件后缀为tar.gz , 压缩工具打开完好无损~");
    }
}

HDFS数据流写流程

hadoop doris 详细对比 hadoop sbin_hadoop doris 详细对比_04


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; dn1每传一个packet会放入一个应答队列等待应答.

8) 当一个Block传输完成之后, 客户端再次请求NameNode上传第二个Block的服务器. (重复执行3-7步) .

网络拓扑-节点距离计算

在HDFS写数据的过程中, NameNode会选择距离待上传数据最近距离的DataNode接收数据.

节点距离 : 两个节点到达最近的共同祖先的距离总和.

hadoop doris 详细对比 hadoop sbin_hadoop_05

机架感知(副本存储节点选择)

机架感知官网文档 : http://hadoop.apache.org/docs/r3.3.0/hadoop-project-dist/hadoop-hdfs/HdfsDesign.html#Data_Replication

参考文档 : https://issues.apache.org/jira/secure/attachment/12345251/Rack_aware_HDFS_proposal.pdf

Replica Placement: The First Baby Steps 副本放置: 第一步

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 in the same rack as that of the writer, another replica on a node in a different (remote) rack, and the last on a different node in the same remote rack. This policy cuts the inter-rack write traffic which generally improves write performance. The chance of rack failure is far less than that of node failure; this policy does not impact data reliability and availability guarantees. However, it does not reduce the aggregate network bandwidth used when reading data since a block is placed in only two unique racks rather than three. With this policy, the replicas of a block do not evenly distribute across the racks. Two replicas are on different nodes of one rack and the remaining replica is on a node of one of the other racks. This policy improves write performance without compromising data reliability or read performance.

If the replication factor is greater than 3, the placement of the 4th and following replicas are determined randomly while keeping the number of replicas per rack below the upper limit (which is basically (replicas - 1) / racks + 2).

Because the NameNode does not allow DataNodes to have multiple replicas of the same block, maximum number of replicas created is the total number of DataNodes at that time.

通常情况下,当副本数为3时,HDFS的放置策略是:如果writer位于datanode上,则将一个副本放在本地计算机上;否则,将一个副本放在与writer相同机架中的随机数据节点上;另一个副本放在不同(远程)机架中的节点上,最后一个副本放在同一远程机架中的不同节点上。此策略可减少机架间的写入流量,从而提高写入性能。机架故障的概率远小于节点故障的概率;此策略不会影响数据可靠性和可用性保证。但是,它并没有减少读取数据时使用的总网络带宽,因为一个块只放在两个而不是三个唯一的机架中。使用此策略,块的副本不会均匀分布在机架上。两个副本位于一个机架的不同节点上,另一个副本位于另一个机架的节点上。此策略在不影响数据可靠性或读取性能的情况下提高了写入性能。

如果复制因子大于3,则随机确定第4个和后续副本的位置,同时将每个机架的副本数保持在上限以下(基本上是(副本-1)/机架+2)。

因为NameNode不允许DataNodes拥有同一块的多个副本,所以创建的最大副本数就是此时datanode的总数。

ps : 同一机架节点之间的I/O小于不同机架节点之间的I/O,

放第一个副本 : 客户端在集群中,放在与客户端所处的节点.如果客户端在集群外,随机选一个机架上的节点

放第二个副本 : 放在与第一个副本同机架不同节点上

放第三个副本 : 放其他机架,随机节点

HDFS读数据流程

hadoop doris 详细对比 hadoop sbin_System_06


1) 客户端通过Distributed FileSystem向NameNode请求下载文件, NameNode通过查询元数据, 找到文件块所在的DataNode地址.

2) 挑选一台DataNode( 就近原则, 然后随机) 服务器, 请求读取数据.

3) DataNode开始传输数据给客户端( 从磁盘里面读取数据输入流, 以Packet为单位来做校验) .

4) 客户端以Packet为单位接收, 先在本地缓存, 然后写入目标文件.

NameNode和SecondaryNameNode

FsImage : 备份备份NameNode内存中的元数据

Edits : 每当元数据有更新或者添加元数据时, 修改内存中的元数据并追加到Edits中

SecondaryNamenode : 引入一个新的节点SecondaryNamenode, 专门用于定期合并FsImage和Edits

hadoop doris 详细对比 hadoop sbin_hdfs_07

  1. 第一阶段: NameNode启动
  1. 第一次启动NameNode格式化后, 创建Fsimage和Edits文件. 如果不是第一次启动, 直接加载编辑日志和镜像文件到内存.
  2. 客户端对元数据进行增删改的请求.
  3. NameNode记录操作日志, 更新滚动日志.
  4. NameNode在内存中对数据进行增删改.
  1. 第二阶段: 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.
1. 加载编辑日志和镜像文件到内存
2. client请求更新(增删改)
3. 记录操作日志,更新滚动日志,滚动正在写的编辑日志
4. 编辑日志和镜像文件拷贝到2nn
5. 2nn加载到内存合并
6. 生成新的FsImage镜像文件
7. 拷贝到nn
8. nn重命名为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需要满足两个条件中的任意一个, 定时时间(1h)到和Edits中数据写满了(1G)) . 直接带回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中.

oiv 查看FsImage

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

hdfs oiv -p XML -i fsimage_0000000000000000032 -o ./fsimage.xml

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

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

oev查看Edits文件

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

hdfs oev -p XML -i edits_0000000000000000292-0000000000000000293 -o ./edits.xml

思考 : NameNode如何确定下次开机启动的时候合并哪些Edits?

CheckPoint时间设置(默认1h)

默认SecondaryNameNode每隔一小时执行一次

hdfs-default.xml

<property>
  <name>dfs.namenode.checkpoint.period</name>
  <value>3600</value>
</property>

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

<property>
  <name>dfs.namenode.checkpoint.txns</name>
  <value>1000000</value>
<description>操作动作次数</description>
</property>

<property>
  <name>dfs.namenode.checkpoint.check.period</name>
  <value>60</value>
<description>1分钟检查一次操作次数</description>
</property>

NameNode故障处理

NameNode 故障后,恢复数据办法:

1.拷贝SecondaryNameNode中数据到原NameNode存储数据目录

# 杀掉namenode进程
kill -9 namenode
# 删除NameNode存储的数据
rm -rf /opt/module/hadoop-3.3.0/data/tmp/dfs/name/*
# 拷贝SecondaryNameNode中数据到原NameNode存储数据目录
scp -r dev@hadoop113:/opt/module/hadoop-3.3.0/data/tmp/dfs/namesecondary/* /opt/module/hadoop-3.3.0/data/tmp/dfs/name/
# 启动namenode
hdfs --daemon start namenode

2.使用-importCheckpoint选项启动NameNode守护进程, 从而将SecondaryNameNode中数据拷贝到NameNode目录中, 如果SecondaryNameNode不和NameNode在一个主机节点上, 需要将SecondaryNameNode存储数据的目录拷贝到NameNode存储数据的平级目录, 并删除in_use.lock文件

# 当前在/opt/module/hadoop-3.3.0/data/tmp/dfs 目录拷贝文件
# 将SecondaryNameNode存储数据的目录拷贝到NameNode存储数据的平级目录
scp -r dev@hadoop1113:/opt/module/hadoop-3.3.0/data/tmp/dfs/namesecondary ./
# 删除文件正在使用锁lock
rm -rf in_use.lock

# 导入检查点数据(等待一会ctrl+c结束掉)
bin/hdfs namenode -importCheckpoint
# 启动namenode
hdfs --daemon start namenode

集群安全模式

  1. NameNode启动
    NameNode启动时,首先将镜像文件加载到内存,并执行编辑日志的各项操作.一旦在内存中成功建立文件系统元数据的映像,则创建一个新的镜像文件和一个空白的编辑日志.此时,NameNode开始监听DataNode的请求.在这个期间namenode一直运行在安全模式,即namenode的文件系统对于客户端来说是只读的.
  2. DataNode启动
    系统中的数据块的位置并不是由namenode维护的,而是以块列表的形式存储在DataNode中.在系统的正常操作期间,namenode会在内存中保留所有块位置的映射信息.在安全模式下,各个DataNode会向namenode发送最新的块信息,namenode了解到足够多的块信息之后,即可高效运行文件系统.
  3. 安全模式退出判断
    如果满足 “最小副本条件” ,namenode会在30秒之后就退出安全模式.
    最小副本条件 : 指在整个文件系统中99.9%的块满足最小副本级别(默认值 dfs.replication.min=1). 在启动一个刚刚格式化的HDFS集群时,因为系统中还没有任何块,所以namenode不会进入安全期.

集群模式处于安全期,不能执行任何重要的操作(写操作).集群启动完成后,自动退出安全模式.

# 查看安全模式
bin/hdfs dfsadmin -safemode get
# 进入安全模式
bin/hdfs dfsadmin -safemode enter
# 离开安全模式
bin/hdfs dfsadmin -safemode leave
# 等待安全模式
bin/hdfs dfsadmin -safemode wait

在安全模式期间的操作,会在离开安全模式后执行

NameNode多目录配置

NameNode的本地目录可以配置成多个, 且每个目录存放内容相同, 增加了可靠性.

修改配置文件

vim hdfs-site.xml

<property>
    <name>dfs.namenode.name.dir</name>
<value>file:///${hadoop.tmp.dir}/dfs/name1,file:///${hadoop.tmp.dir}/dfs/name2</value>
</property>

删除日志和数据后,重启集群,就可以看到namenode数据目录有name1和name2目录

DataNode工作机制

hadoop doris 详细对比 hadoop sbin_hadoop_08


1) 一个数据块在DataNode上以文件形式存储在磁盘上, 包括两个文件, 一个是数据本身, 一个是元数据包括数据块的长度, 块数据的校验和, 以及时间戳.

2) DataNode启动后向NameNode注册, 通过后, 周期性(1小时) 的向NameNode上报所有的块信息.

3) 心跳是每3秒一次, 心跳返回结果带有NameNode给该DataNode的命令如复制块数据到另一台机器, 或删除某个数据块. 如果超过10分钟没有收到某个DataNode的心跳, 则认为该节点不可用.

4) 集群运行中可以安全加入和退出一些机器.

DataNode数据完整性(crc校验)

  1. 当DataNode读取Block的时候, 它会计算CheckSum.
  2. 如果计算后的CheckSum, 与Block创建时值不一样, 说明Block已经损坏.
  3. Client读取其他DataNode上的Block.
  4. DataNode在其文件创建后周期验证CheckSum

掉线时限参数设置(默认 : 10分钟+30秒)

hadoop doris 详细对比 hadoop sbin_hdfs_09


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

hdfs-site.xml 配置文件中 :

<!-- 单位 : 毫秒  -->
<property>
    <name>dfs.namenode.heartbeat.recheck-interval</name>
    <value>300000</value>
</property>
<!-- 单位 : 秒  -->
<property>
    <name>dfs.heartbeat.interval</name>
    <value>3</value>
</property>

服役新数据节点(上线新节点)

  1. 准备一台新机器
  2. 配置好主机和静态IP
  3. 拷贝hadoop文件,删除原有的data和log
  4. 配置环境变量,刷新
  5. 直接启动DataNode和NodeManeger
hdfs --daemon start datanode
yarn --daemon start nodemanager

# 上传文件测试
hdfs dfs -put test.txt /
# 如果数据不均衡,命令实现均衡
sbin/start-balancer.sh

退役新数据节点(下线新节点)

白名单退役

添加到白名单的主机节点, 都允许访问NameNode, 不在白名单的主机节点, 都会被退出

配置白名单步骤 :

① 在 NameNode 的 /opt/module/hadoop-3.3.0/etc/hadoop 目录文件夹下创建dfs.hosts 添加主机名(不添加新上线的hadoop114,表示namenode不再使用hadoop114存数据),不能有空格空行

touch dfs.hosts
vim dfs.hosts

hadoop111
hadoop112
hadoop113

# :wq保存退出

############ example ##############
[dev@hadoop111 hadoop-3.3.0]$ pwd
/opt/module/hadoop-3.3.0/etc/hadoop
[dev@hadoop111 hadoop-3.3.0]$ cat dfs.hosts 
hadoop111
hadoop112
hadoop113
[dev@hadoop111 hadoop-3.3.0]$

② 配置 hdfs-site.xml 添加dfs.hosts属性

<property>
	<name>dfs.hosts</name>
	<value>/opt/module/hadoop-3.3.0/etc/hadoop/dfs.hosts</value>
</property>

③ 分发配置

xsync hdfs-site.xml

④ 刷新namenode

hdfs dfsadmin -refreshNodes

⑤ 刷新ResourceManager节点

yarn rmadmin -refreshNodes

⑥ 浏览器查看节点,发现新增的数据节点hadoop114已经不在列表中了

如果数据不均衡,可以执行:

./start-balancer.sh

黑名单退役

在黑名单上面的主机都会被强制退出

配置步骤如下:

① 在 NameNode 的 /opt/module/hadoop-3.3.0/etc/hadoop 目录文件夹下创建dfs.hosts.exclude文件

# 创建文件,并添加需要被添加到黑名单的主机名
echo "hadoop114" > dfs.hosts.exclude


############ example ##############
[dev@hadoop111 hadoop]$ pwd
/opt/module/hadoop-3.3.0/etc/hadoop
[dev@hadoop111 hadoop]$ cat dfs.hosts.exclude 
hadoop114
[dev@hadoop111 hadoop]$

② 在NameNode的hdfs-site.xml配置文件中增加dfs.hosts.exclude属性

<property>
	<name>dfs.hosts.exclude</name>
    <value>/opt/module/hadoop-3.3.0/etc/hadoop/dfs.hosts.exclude</value>
</property>

③ 刷新NameNode、刷新ResourceManager

hdfs dfsadmin -refreshNodes

yarn rmadmin -refreshNodes

④ 浏览器查看退役节点的状态为decommission in progress(退役中), 说明数据节点正在复制块到其他节点,等待退役节点状态为**decommissioned(所有块已经复制完成) , 停止该节点及节点资源管理器. 注意 : 如果副本数是3,服役的节点小于等于3,是不能退役成功的,需要修改副本数后才能退役

⑤ 退役完成后,在 hadoop114 上停止进程 (datanode , nodemanager)

hdfs --daemon stop datanode
yarn --daemon stop nodemanager

如果数据不均衡,可以执行:

./start-balancer.sh

注意 : 不允许一个主机名,同时出现在白名单和黑名单中

DataNode多目录配置

跟NameNode多目录配置基本一样

① DataNode也可以配置成多个目录,每个目录存储的数据不一样。即:数据不是副本

② 修改 hdfs-site.xml 配置:

<property>
     <name>dfs.datanode.data.dir</name>
<value>file:///${hadoop.tmp.dir}/dfs/data1,file:///${hadoop.tmp.dir}/dfs/data2</value>
</property>

重启就可以看到DataNode目录下面有多个目录了

今天就写到这里吧,每天进步一点点~