Hadoop-2-HDFS

3. HDFS(Hadoop Distributed File System)

3.1 Hadoop分布式文件系统

  • HDFS是一种允许文件通过网络在多台主机上分享的文件系统可以让多台机器上的多个用户分享文件和存储文件。

  • 分布式文件管理系统有很多,HDFS只是其中一种实现。

  • 注意:HDFS不适合存储小文件

image-20211201101048213

操作格式:bin/hdfs dfs -xxx(HDFS操作) scheme://authority/path

image-20211201101812494

3.2 HSFS常用操作

# 查看目录文件信息
hdfs dfs -ls /
hdfs dfs -ls -R /  # 递归查看所有目录及其子目录
# 上传文件
hdfs dfs -put README.txt /
# 查看文件信息
hdfs dfs -cat /README.txt
# 下载文件到本地
hdfs dfs -get /README.txt .
# 创建文件夹
hdfs dfs -mkdir /test
hdfs dfs -mkdir -p /test/a/b  # 创建递归目录
# 删除目录或文件
hdfs dfs -rm /README.txt  # 删除文件
hdfs dfs -rm -r /text  # 删除目录

HDFS具体案例

需求:统计HDFS中文件的个数和每个文件的大小

[root@master hadoop-3.2.0]# hdfs dfs -ls / | wc -l
	4
[root@master hadoop-3.2.0]# hdfs dfs -ls /
    Found 3 items
    -rw-r--r--   2 root supergroup     150569 2021-12-01 10:40 /LICENSE.txt
    -rw-r--r--   2 root supergroup      22125 2021-12-01 10:40 /NOTICE.txt
    -rw-r--r--   2 root supergroup       1361 2021-12-01 10:40 /README.txt
# 可以看出HDFS将第一行的Found 3 items也当作一条记录输出了,想要准确得到文件数量需要过滤第一条
[root@master hadoop-3.2.0]# hdfs dfs -ls / | grep /
    -rw-r--r--   2 root supergroup     150569 2021-12-01 10:40 /LICENSE.txt
    -rw-r--r--   2 root supergroup      22125 2021-12-01 10:40 /NOTICE.txt
    -rw-r--r--   2 root supergroup       1361 2021-12-01 10:40 /README.txt
[root@master hadoop-3.2.0]# hdfs dfs -ls / | grep / | wc -l
    3
[root@master hadoop-3.2.0]# hdfs dfs -ls / | grep / | awk '{print $5,$8}'
    150569 /LICENSE.txt
    22125 /NOTICE.txt
    1361 /README.txt

3.3 JAVA代码操作HDFS

3.3.1 安装MAVEN

在windows系统中解压apache-maven-3.8.4文件

image-20211201110251719

然后再系统环境变量中配置

image-20211201110318685

将bin目录加入到PATH中

image-20211201110350470

修改settings.xml文件,D:\Program Files\apache-maven-3.8.4\conf\settings.xml,把maven仓库的地址修改到其他盘,默认在C盘用户目录下。

localRepository标签从注释中移出来,然后将值改为 D:\.m2 ,效果如下:
这里的目录名字可以随意起,只要易于识别就可以

image-20211201110629308

这样修改之后,maven管理的依赖jar包都会保存到D:\.m2目录下了。

打开IDE配置maven。

image-20211201120111828

点击设置,配置MAVEN

image-20211201120203576

开启自动加载依赖

image-20211201140607347

在pom.xml中加入Maven hadoop支持,并等其自动加载java依赖。

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

构建文件

image-20211201140857561

构建HdfsOp类,并输入以下代码:

package com.imooc.hdfs;


import org.apache.hadoop.conf.Configuration;
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 java.io.FileInputStream;

/*
* JAVA代码操作HDFS
* 文件操作:上传文件,下载文件,删除文件
* Created by Luoming
*
*
* */
public class HdfsOp {
    public static void main(String[] args) throws Exception{
        // 创建一个配置对象
        Configuration conf = new Configuration();
        conf.set("fs.defaultFS", "hdfs://master:9000");
        // 获取操作HDFS的对象
        FileSystem fileSystem = FileSystem.get(conf);
        // 上传文件
        // 获取本地文件的输入流
        FileInputStream fils = new FileInputStream("D:\\config.ini");
        // 获取HDFS文件系统的输出流
        FSDataOutputStream fos = fileSystem.create(new Path("/user.txt"));
        // 上传文件:通过工具类把输入流拷贝到输出流里面,实现本例文件上传到HDFS
        IOUtils.copyBytes(fils, fos, 1024, true);
    }
}

右键运行java代码,如果出现java: 错误: 不支持发行版本 5错误,参考https://blog.csdn.net/qq_22076345/article/details/82392236。

出现错误,权限拒绝:

image-20211201141114938

解决办法:

  1. 将jar包打包到集群上运行
  2. 关闭集群权限校验机制
# 关闭集群
sbin/stop-all.sh
# 修改 etc/hadoop下hdfs-site.xml文件
[root@master hadoop]# vim hdfs-site.xml 
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<!--
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License. See accompanying LICENSE file.
-->

<!-- Put site-specific property overrides in this file. -->

<configuration>
    <property>
        <name>dfs.replication</name>
        <value>2</value>
    </property>
    <property>
        <name>dfs.namenode.secondary.http-address</name>
        <value>master:50090</value>
    </property>
    <property>
        <name>dfs.permissions.enable</name>
        <value>false</value>
    </property>
</configuration>

并同步到其他2台机器

[root@master hadoop]# scp -rq hdfs-site.xml slave1:/data/soft/hadoop-3.2.0/etc/hadoop/
[root@master hadoop]# scp -rq hdfs-site.xml slave2:/data/soft/hadoop-3.2.0/etc/hadoop/

重新运行代码

3.3.2 上传文件,下载文件,删除文件方法

package com.imooc.hdfs;


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 java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/*
* JAVA代码操作HDFS
* 文件操作:上传文件,下载文件,删除文件
* Created by Luoming
*
*
* */
public class HdfsOp {
    public static void main(String[] args) throws Exception{
        // 创建一个配置对象
        Configuration conf = new Configuration();
        conf.set("fs.defaultFS", "hdfs://master:9000");
        // 获取操作HDFS的对象
        FileSystem fileSystem = FileSystem.get(conf);
        // 上传文件
        // put(fileSystem);
        // 下载文件
        // get(fileSystem);
        // 删除文件
        delete(fileSystem);
    }

	// 将HDFS根目录下的LICENSE.txt文件删除    
    private static void delete(FileSystem fileSystem) throws IOException {
        // 删除文件,目录也可以删除
        // 如果要递归删除目录,则第二个参数需要设置为true
        // 如果删除的是文件或者空目录,第二个参数及被忽略
        boolean flag = fileSystem.delete(new Path("/LICENSE.txt"), true);
        if(flag){
            System.out.println("删除成功!");
        }else{
            System.out.println("删除失败!");
        }
    }
    
	// 将HDFS根目录下的README.txt文件下载到本地D:\README1.txt
    private static void get(FileSystem fileSystem) throws IOException {
        // 获取HDFS文件系统中的输入流
        FSDataInputStream fis = fileSystem.open(new Path("/README.txt"));
        // 获取本地文件的输出流
        FileOutputStream fos = new FileOutputStream("D:\\README1.txt");
        // 下载文件
        IOUtils.copyBytes(fis, fos, 1024, true);
    }
    private static void put(FileSystem fileSystem) throws IOException {
        // 获取本地文件的输入流
        FileInputStream fils = new FileInputStream("D:\\config.ini");
        // 获取HDFS文件系统的输出流
        FSDataOutputStream fos = fileSystem.create(new Path("/user.txt"));
        // 上传文件:通过工具类把输入流拷贝到输出流里面,实现本例文件上传到HDFS
        IOUtils.copyBytes(fils, fos, 1024, true);
    }
}

消除警告信息

image-20211201144106602

加入slf4j-api依赖

在pom.xml文件中加入

<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.10</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.10</version>
</dependency>

然后再resources中创建log4j.properties文件,文件内容为

image-20211201145350244

 ### 设置###
log4j.rootLogger = debug,stdout

### 输出信息到控制抬 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n

3.4 HDFS体系结构

  • HDFS支持主从结构,主节点称为NameNode,支持多个;从节点称为DataNode,支持多个

  • HDFS中还包含一个SecondaryNameNode进程

image-20211201150029034

3.4.1 NameNode

NameNode是整个文件系统的管理节点

它主要维护着整个文件系统的文件目录树,文件/目录的信息每个文件对于的数据块列表,并且还负责接收用户操作请求。

  • 文件系统的文件目录树:指整个系统的目录结构信息,类似使用ls命令得到的返回结果。

  • 文件/目录的信息:表示文件/目录的的一些基本信息,所有者 属组 修改时间 文件大小等信息。

  • 每个文件对应的数据块列表:如果一个文件太大,那么在集群中存储的时候会对文件进行切割,这个
    时候就类似于会给文件分成一块一块的,存储到不同机器上面。所以HDFS还要记录一下一个文件到
    底被分了多少块,每一块都在什么地方存储着。

  • 接收用户的操作请求:其实我们在命令行使用hdfs操作的时候,是需要先和namenode通信 才能开
    始去操作数据的。

NameNode主要包含以下文件:

这些文件所在的路径是由hdfs-default.xmldfs.namenode.name.dir属性控制的

hdfs-default.xml文件在hadoop-3.2.0\share\hadoop\hdfs\hadoop-hdfs-3.2.0.jar中,这个文件中包含了HDFS相关的所有默认参数,咱们在配置集群的时候会修改一个hdfs-site.xml文件,hdfs-site.xml文件属于hdfs-default.xml的一个扩展,它可以覆盖掉hdfs-default.xml中同名的参数。

那我们来看一下这个文件中的dfs.namenode.name.dir属性

<property>
  <name>dfs.namenode.name.dir</name>
  <value>file://${hadoop.tmp.dir}/dfs/name</value>
  <description>Determines where on the local filesystem the DFS name node
      should store the name table(fsimage).  If this is a comma-delimited list
      of directories then the name table is replicated in all of the
      directories, for redundancy. </description>
</property>

这个属性的值是由hadoop.tmp.dir属性控制的,这个属性的值默认再core-default.xml文件中。

image-20211201160827492

在修改core-site.xml的时候设置的有hadoop.tmp.dir属性的值是/data/hadoop_repo,所以说core-site.xml中的hadoop.tmp.dir属性会覆盖掉core-default.xml中的${hadoop.tmp.dir},最终dfs.namenode.name.dir属性的值就是:/data/hadoop_repo/dfs/name

在master节点中的/data/hadoop_repo/dfs/name目录下发现这个下面会有一个current目录,表示当前的意思,还有一个in_use.lock这个只是一个普通文件,但是它其实有特殊含义,其文件名后缀值lock表示是锁的意思,文件名是in_use表示这个文件现在正在使用,不允许你再启动namenode

当我们启动namonde的时候 会判断这个目录下是否有in_use.lock 这个相当于一把锁,如果没有的话,才可以启动成功,启动成功之后就会加一把锁, 停止的时候会把这个锁去掉。

[root@master hadoop-3.2.0]# cd /data/hadoop_repo/dfs/name
[root@master name]# ll
    总用量 8
    drwxr-xr-x. 2 root root 4096 121 15:19 current
    -rw-r--r--. 1 root root   11 121 14:18 in_use.lock
[root@master name]# cd current/
[root@master current]# ll
    总用量 4152
    -rw-r--r--. 1 root root      42 1130 17:54 edits_0000000000000000001-0000000000000000002
    -rw-r--r--. 1 root root 1048576 1130 17:54 edits_0000000000000000003-0000000000000000003
    -rw-r--r--. 1 root root      42 1130 17:59 edits_0000000000000000004-0000000000000000005
    -rw-r--r--. 1 root root 1048576 1130 17:59 edits_0000000000000000006-0000000000000000006
    -rw-r--r--. 1 root root      42 121 10:20 edits_0000000000000000007-0000000000000000008
    -rw-r--r--. 1 root root    2547 121 11:20 edits_0000000000000000009-0000000000000000042
    -rw-r--r--. 1 root root      42 121 12:20 edits_0000000000000000043-0000000000000000044
    -rw-r--r--. 1 root root      42 121 13:20 edits_0000000000000000045-0000000000000000046
    -rw-r--r--. 1 root root 1048576 121 13:20 edits_0000000000000000047-0000000000000000047
    -rw-r--r--. 1 root root      42 121 14:19 edits_0000000000000000048-0000000000000000049
    -rw-r--r--. 1 root root     529 121 15:19 edits_0000000000000000050-0000000000000000058
    -rw-r--r--. 1 root root 1048576 121 15:50 edits_inprogress_0000000000000000059
    -rw-r--r--. 1 root root     652 121 14:19 fsimage_0000000000000000049
    -rw-r--r--. 1 root root      62 121 14:19 fsimage_0000000000000000049.md5
    -rw-r--r--. 1 root root     664 121 15:19 fsimage_0000000000000000058
    -rw-r--r--. 1 root root      62 121 15:19 fsimage_0000000000000000058.md5
    -rw-r--r--. 1 root root       3 121 15:19 seen_txid
    -rw-r--r--. 1 root root     218 121 10:19 VERSION

里面有edits文件和fsimage文件

3.4.1.1 fsimage文件

fsimage文件有两个文件名相同的,有一个后缀是md5(md5是一种加密算法),这个其实主要是为了做md5校验的,为了保证文件传输的过程中不出问题,相同内容的md5是一样的,所以后期如果我把这个fsimage和对应的fsimage.md5发给你 然后你根据md5fsimage的内容进行加密,获取一个值和fsimage.md5中的内容进行比较,如果一样,说明你接收到的文件就是完整的。

在这里可以把fsimage 拆开fs是文件系统 filesystem image是镜像。

说明是文件系统镜像,就是给文件照了一个像,把文件的当前信息记录下来。

我们可以看一下这个文件,这个文件需要使用特殊的命令进行查看。

-i 输入文件 -o 输出文件。

[root@master current]# hdfs oiv -p XML -i fsimage_0000000000000000058 -o fsimage56.xml
<?xml version="1.0"?>
<fsimage><version><layoutVersion>-65</layoutVersion><onDiskVersion>1</onDiskVersion><oivRevision>e97acb3bd8f3befd27418996fa5d4b50bf2e17bf</oivRevision></version>
<NameSection><namespaceId>276211861</namespaceId><genstampV1>1000</genstampV1><genstampV2>1005</genstampV2><genstampV1Limit>0</genstampV1Limit><lastAllocatedBlockId>1073741829</lastAllocatedBlockId><txid>58</txid></NameSection>
2021-12-01 16:27:49,417 INFO offlineImageViewer.FSImageHandler: Loading 5 strings
2021-12-01 16:27:49,455 INFO namenode.FSDirectory: GLOBAL serial map: bits=29 maxEntries=536870911
2021-12-01 16:27:49,455 INFO namenode.FSDirectory: USER serial map: bits=24 maxEntries=16777215
2021-12-01 16:27:49,455 INFO namenode.FSDirectory: GROUP serial map: bits=24 maxEntries=16777215
2021-12-01 16:27:49,455 INFO namenode.FSDirectory: XATTR serial map: bits=24 maxEntries=16777215
<ErasureCodingSection>
<erasureCodingPolicy>
<policyId>5</policyId><policyName>RS-10-4-1024k</policyName><cellSize>1048576</cellSize><policyState>DISABLED</policyState><ecSchema>
<codecName>rs</codecName><dataUnits>10</dataUnits><parityUnits>4</parityUnits></ecSchema>
</erasureCodingPolicy>

<erasureCodingPolicy>
<policyId>2</policyId><policyName>RS-3-2-1024k</policyName><cellSize>1048576</cellSize><policyState>DISABLED</policyState><ecSchema>
<codecName>rs</codecName><dataUnits>3</dataUnits><parityUnits>2</parityUnits></ecSchema>
</erasureCodingPolicy>

<erasureCodingPolicy>
<policyId>1</policyId><policyName>RS-6-3-1024k</policyName><cellSize>1048576</cellSize><policyState>ENABLED</policyState><ecSchema>
<codecName>rs</codecName><dataUnits>6</dataUnits><parityUnits>3</parityUnits></ecSchema>
</erasureCodingPolicy>

<erasureCodingPolicy>
<policyId>3</policyId><policyName>RS-LEGACY-6-3-1024k</policyName><cellSize>1048576</cellSize><policyState>DISABLED</policyState><ecSchema>
<codecName>rs-legacy</codecName><dataUnits>6</dataUnits><parityUnits>3</parityUnits></ecSchema>
</erasureCodingPolicy>

<erasureCodingPolicy>
<policyId>4</policyId><policyName>XOR-2-1-1024k</policyName><cellSize>1048576</cellSize><policyState>DISABLED</policyState><ecSchema>
<codecName>xor</codecName><dataUnits>2</dataUnits><parityUnits>1</parityUnits></ecSchema>
</erasureCodingPolicy>

</ErasureCodingSection>

<INodeSection><lastInodeId>16395</lastInodeId><numInodes>4</numInodes><inode><id>16385</id><type>DIRECTORY</type><name></name><mtime>1638340273212</mtime><permission>root:supergroup:0755</permission><nsquota>9223372036854775807</nsquota><dsquota>-1</dsquota></inode>
<inode><id>16393</id><type>FILE</type><name>NOTICE.txt</name><replication>2</replication><mtime>1638326445633</mtime><atime>1638326445602</atime><preferredBlockSize>134217728</preferredBlockSize><permission>root:supergroup:0644</permission><blocks><block><id>1073741827</id><genstamp>1003</genstamp><numBytes>22125</numBytes></block>
</blocks>
<storagePolicyId>0</storagePolicyId></inode>
<inode><id>16394</id><type>FILE</type><name>README.txt</name><replication>2</replication><mtime>1638326445728</mtime><atime>1638339974053</atime><preferredBlockSize>134217728</preferredBlockSize><permission>root:supergroup:0644</permission><blocks><block><id>1073741828</id><genstamp>1004</genstamp><numBytes>1361</numBytes></block>
</blocks>
<storagePolicyId>0</storagePolicyId></inode>
<inode><id>16395</id><type>FILE</type><name>user.txt</name><replication>3</replication><mtime>1638339650726</mtime><atime>1638339650078</atime><preferredBlockSize>134217728</preferredBlockSize><permission>ming_log:supergroup:0644</permission><blocks><block><id>1073741829</id><genstamp>1005</genstamp><numBytes>20</numBytes></block>
</blocks>
<storagePolicyId>0</storagePolicyId></inode>
</INodeSection>
<INodeReferenceSection></INodeReferenceSection><SnapshotSection><snapshotCounter>0</snapshotCounter><numSnapshots>0</numSnapshots></SnapshotSection>
<INodeDirectorySection><directory><parent>16385</parent><child>16393</child><child>16394</child><child>16395</child></directory>
</INodeDirectorySection>
<FileUnderConstructionSection></FileUnderConstructionSection>
<SecretManagerSection><currentId>0</currentId><tokenSequenceNumber>0</tokenSequenceNumber><numDelegationKeys>0</numDelegationKeys><numTokens>0</numTokens></SecretManagerSection><CacheManagerSection><nextDirectiveId>1</nextDirectiveId><numDirectives>0</numDirectives><numPools>0</numPools></CacheManagerSection>
</fsimage>

里面最外层是一个fsimage标签,看里面的inode标签,
这个inode表示是hdfs中的每一个目录或者文件信息。

例如:

<inode>
    <id>16394</id>  <!-- 唯一编号 -->
    <type>FILE</type>  <!-- 文件类型 -->
    <name>README.txt</name>  <!-- 文件名 -->
    <replication>2</replication>  <!-- 文件的副本数量 -->
    <mtime>1638326445728</mtime>  <!-- 修改时间 -->
    <atime>1638339974053</atime>  <!-- 访问时间 -->
    <preferredBlockSize>134217728</preferredBlockSize>  <!-- 推荐每一个数据块的大小 -->	<permission>root:supergroup:0644</permission>  <!-- 权限信息 -->
    <blocks>  <!-- 包含多少数据块【文件被切成数据块】 -->
        <block>  <!-- 每个块信息 -->
            <id>1073741828</id>  <!-- 块id -->
            <genstamp>1004</genstamp>  <!-- 唯一编号 -->
            <numBytes>1361</numBytes>  <!-- 当前数据块的实际大小 -->
        </block>
    </blocks>
	<storagePolicyId>0</storagePolicyId>  <!-- 数据的存储策略 -->
</inode>

这个文件中其实就维护了整个文件系统的文件目录树,文件/目录的元信息和每个文件对应的数据块列
表,所以说fsimage中存放了hdfs最核心的数据。

3.4.1.2 edits文件

当我们上传一个文件的时候,上传一个10G的文件,假设传到9G的时候上传失败了,这个时候就需要重
新传,那hdfs怎么知道这个文件失败了呢?这个是在edits文件中记录的。

当我们上传大文件的时候,一个大文件会分为多个block,那么edits文件中就会记录这些block的上传状
态,只有当全部block都上传成功了以后,这个时候edits中才会记录这个文件上传成功了,那么我们执行
hdfs dfs -ls 的时候就能查到这个文件了,所以当我们在hdfs中执行ls命令的时候,其实会查询fsimage和edits中的内容。

为什么会有这两个文件呢?

  • 首先,我们固化的一些文件内容是存储在fsimage文件中,当前正在上传的文件信息是存储在edits文件
    中。

这个时候我们来查看一下这个edits文件的内容,挑一个edits文件内容多一些的文件

hdfs oev -i edits_0000000000000000009-0000000000000000042  -o edits.xml

这个edits.xml中可以大致看一下,里面有很多record。每一个record代表不同的操作,

例如 OP_ADD,OP_CLOSE 等等,具体挑一个实例进行分析。

  • OP_ADD:执行上传操作
  • OP_ALLOCATE_BLOCK_ID:申请block块id
  • OP_SET_GENSTAMP_V2:设置GENSTAMP
  • OP_ADD_BLOCK:添加block块
  • OP_CLOSE:关闭上传操作
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<EDITS>
  <EDITS_VERSION>-65</EDITS_VERSION>
  <RECORD>
    <OPCODE>OP_ADD</OPCODE>
    <DATA>
      <TXID>10</TXID>
      <LENGTH>0</LENGTH>
      <INODEID>16386</INODEID>
      <PATH>/README.txt._COPYING_</PATH>  <!-- 上传 -->
      <REPLICATION>2</REPLICATION>
      <MTIME>1638325559657</MTIME>
      <ATIME>1638325559657</ATIME>
      <BLOCKSIZE>134217728</BLOCKSIZE>
      <CLIENT_NAME>DFSClient_NONMAPREDUCE_-1470139500_1</CLIENT_NAME>
      <CLIENT_MACHINE>192.168.128.130</CLIENT_MACHINE>
      <OVERWRITE>true</OVERWRITE>
      <PERMISSION_STATUS>
        <USERNAME>root</USERNAME>
        <GROUPNAME>supergroup</GROUPNAME>
        <MODE>420</MODE>
      </PERMISSION_STATUS>
      <ERASURE_CODING_POLICY_ID>0</ERASURE_CODING_POLICY_ID>
      <RPC_CLIENTID>b59e1a54-f5a6-46e6-a494-e3ec486335cc</RPC_CLIENTID>
      <RPC_CALLID>3</RPC_CALLID>
    </DATA>
  </RECORD>
  <RECORD>
    <OPCODE>OP_ALLOCATE_BLOCK_ID</OPCODE>  <!-- 申请BLOCK ID -->
    <DATA>
      <TXID>11</TXID>
      <BLOCK_ID>1073741825</BLOCK_ID>
    </DATA>
  </RECORD>
  <RECORD>
    <OPCODE>OP_SET_GENSTAMP_V2</OPCODE>  <!-- 设置GENSTAMP编号 -->
    <DATA>
      <TXID>12</TXID>
      <GENSTAMPV2>1001</GENSTAMPV2>
    </DATA>
  </RECORD>
    <OPCODE>OP_ADD_BLOCK</OPCODE>  <!-- 添加BLOCK块 -->
    <DATA>
      <TXID>13</TXID>
      <PATH>/README.txt._COPYING_</PATH>
      <BLOCK>
        <BLOCK_ID>1073741825</BLOCK_ID>
        <NUM_BYTES>0</NUM_BYTES>
        <GENSTAMP>1001</GENSTAMP>
      </BLOCK>
      <RPC_CLIENTID/>
      <RPC_CALLID>-2</RPC_CALLID>
    </DATA>
  </RECORD>
  <RECORD>
    <OPCODE>OP_CLOSE</OPCODE>  <!-- 关闭文件 -->
    <DATA>
      <TXID>14</TXID>
      <LENGTH>0</LENGTH>
      <INODEID>0</INODEID>
      <PATH>/README.txt._COPYING_</PATH>
      <REPLICATION>2</REPLICATION>
      <MTIME>1638325561339</MTIME>
      <ATIME>1638325559657</ATIME>
      <BLOCKSIZE>134217728</BLOCKSIZE>
      <CLIENT_NAME/>
      <CLIENT_MACHINE/>
      <OVERWRITE>false</OVERWRITE>
      <BLOCK>
        <BLOCK_ID>1073741825</BLOCK_ID>
        <NUM_BYTES>1361</NUM_BYTES>
        <GENSTAMP>1001</GENSTAMP>
      </BLOCK>
      <PERMISSION_STATUS>
        <USERNAME>root</USERNAME>
        <GROUPNAME>supergroup</GROUPNAME>
        <MODE>420</MODE>
      </PERMISSION_STATUS>
    </DATA>
  </RECORD>
</EDITS>

这里面的每一个record都有一个事务idtxid,事务id是连续的,其实一个put操作会在edits文件中产生很多的record,对应的就是很多步骤,这些步骤对我们是屏蔽的。

注意了,根据我们刚才的分析,我们所有对hdfs的增删改操作都会在edits文件中留下信息,那么fsimage
文件中的内容是从哪来的?

其实是这样的,edits文件会定期合并到fsimage文件中。

有同学可能有疑问了,edits文件和fsimage文件中的内容是不一样的,这怎么能是合并出来的呢?

注意,这个其实是框架去做的,在合并的时候会对edits中的内容进行转换,生成新的内容,其实
edits中保存的内容是不是太细了,单单一个上传操作就分为了好几步,其实上传成功之后,我们
只需要保存文件具体存储的block信息就行了把,所以在合并的时候其实是对edits中的内容进行了
精简。

他们具体合并的代码我们不用太过关注,但是我们要知道是那个进程去做的这个事情,其实就是我们之前提到的secondarynamenode

这个进程就是负责定期的把edits中的内容合并到fsimage中。他只做一件事,这是一个单独的进程,在实际工作中部署的时候,也需要部署到一个单独的节点上面。

3.4.1.3 seen_txid文件

current目录中还有一个seen_txid文件,HDFS format之后是0,它代表的是namenode里面的edits_*
件的尾数,namenode重启的时候,会按照seen_txid的数字,顺序从头跑edits_0000001~seen_txid
数字。如果根据对应的seen_txid无法加载到对应的文件,NameNode进程将不会完成启动以保护数据一
致性。

[root@master current]# cat seen_txid
	77
3.4.1.4 VERSION文件
[root@master current]# cat VERSION 
    #Wed Dec 01 10:19:09 CST 2021
    namespaceID=276211861
    clusterID=CID-a2d22775-7406-4788-ba91-13c7aaf13a6b
    cTime=1638265933793
    storageType=NAME_NODE
    blockpoolID=BP-820095417-192.168.128.130-1638265933793
    layoutVersion=-65

这里面显示的集群的一些信息、当重新对hdfs格式化 之后,这里面的信息会变化。

之前我们说过 在使用hdfs的时候只格式化一次,不要格式化多次,为什么呢?

一会在讲datanode的时候会详细解释。

3.4.1.5 总结
  • fsimage:元数据镜像文件,存储某一时刻NameNode内存中的元数据信息,就类似是定时做了一个快照
    操作。【这里的元数据信息是指文件目录树、文件/目录的信息、每个文件对应的数据块列表】。
  • edits:操作日志文件【事物文件】。这里面会实时记录用户的所有操作。
  • seen_txid: 是存放transactionId的文件,format之后是0,它代表的是namenode里面的edits_*文件的尾数,namenode重启的时候,会按照seen_txid的数字,顺序从头跑edits_0000001~到seen_txid的数字。如果根据对应的seen_txid无法加载到对应的文件,NameNode进程将不会完成启动以保护数据一致性。
  • VERSION:保存了集群的版本信息

3.4.2 SecondaryNameNode

  • 主要负责定期的把edits文件中的内容合并到fsimage
  • 这个合并操作称为cheakpoint,在合并的时候会对edits中的内容进行转换,生成新的内容保存到fsimage文件中。
  • 注意:在NameNode的HA架构中没有SecondaryNameNode进程,文件合并操作会由standby NameNode负责实现。

3.4.3 DataNode

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

HDFS会按照固定的大小,顺序对文件进行划分并编号,划分好的每一个块称一个BlockHDFS默认Block大小是128MB

datanode中数据的具体存储位置是由dfs.datanode.data.dir来控制的,通过查询hdfs-default.xml可以知道,具体的位置在这里。

<property>
  <name>dfs.datanode.data.dir</name>
  <value>file://${hadoop.tmp.dir}/dfs/data</value>
  <description>Determines where on the local filesystem an DFS data node
  should store its blocks.  If this is a comma-delimited
  list of directories, then data will be stored in all named
  directories, typically on different devices. The directories should be tagged
  with corresponding storage types ([SSD]/[DISK]/[ARCHIVE]/[RAM_DISK]) for HDFS
  storage policies. The default storage type will be DISK if the directory does
  not have a storage type tagged explicitly. Directories that do not exist will
  be created if local filesystem permission allows.
  </description>
</property>

连接到slave1这个节点上去看一下

[root@slave1 ~]# cd /data/hadoop_repo/dfs/data/
[root@slave1 data]# ll
总用量 4
drwxr-xr-x. 3 root root 71 1130 17:53 current
-rw-r--r--. 1 root root 11 122 09:40 in_use.lock
[root@slave1 data]# cd current/
[root@slave1 current]# ll
总用量 4
drwx------. 4 root root  54 122 09:40 BP-820095417-192.168.128.130-1638265933793
-rw-r--r--. 1 root root 229 122 09:40 VERSION
[root@slave1 current]# cd BP-820095417-192.168.128.130-1638265933793/
[root@slave1 BP-820095417-192.168.128.130-1638265933793]# ll
总用量 4
drwxr-xr-x. 4 root root  64 121 17:53 current
-rw-r--r--. 1 root root 166 1130 17:53 scanner.cursor
drwxr-xr-x. 2 root root   6 122 09:40 tmp
[root@slave1 BP-820095417-192.168.128.130-1638265933793]# cd current/
[root@slave1 current]# ll
总用量 8
-rw-r--r--. 1 root root  19 121 17:53 dfsUsed
drwxr-xr-x. 3 root root  21 121 10:26 finalized
drwxr-xr-x. 2 root root   6 121 15:49 rbw
-rw-r--r--. 1 root root 145 122 09:40 VERSION
[root@slave1 current]# cd finalized/
[root@slave1 finalized]# ll
总用量 0
drwxr-xr-x. 3 root root 21 121 10:26 subdir0
[root@slave1 finalized]# cd subdir0/
[root@slave1 subdir0]# ll
总用量 0
drwxr-xr-x. 2 root root 168 121 15:50 subdir0
[root@slave1 subdir0]# cd subdir0/
[root@slave1 subdir0]# ll
总用量 44
-rw-r--r--. 1 root root 22125 121 10:40 blk_1073741827
-rw-r--r--. 1 root root   183 121 10:40 blk_1073741827_1003.meta
-rw-r--r--. 1 root root  1361 121 10:40 blk_1073741828
-rw-r--r--. 1 root root    19 121 10:40 blk_1073741828_1004.meta
-rw-r--r--. 1 root root    20 121 14:20 blk_1073741829
-rw-r--r--. 1 root root    11 121 14:20 blk_1073741829_1005.meta

这里面就有很多block块了

  • 其中.meta文件也是做校验用的

根据前面看到的blockid信息到这对应的找到文件,可以直接查看,发现文件内容是我们之前上传上去的内容。

[root@slave1 subdir0]# cat blk_1073741829
[pagecfg]
index=2

注意:这个block中的内容可能只是文件的一部分,如果你的文件较大的话,就会分为多个block存储,默认 hadoop3中一个block的大小为128M。根据字节进行截取,截取到128M就是一个block。如果文件大小没有默认的block块大,那最终就只有一个block

HDFS中,如果一个文件小于一个数据块的大小,那么并不会占用整个数据块的存储空间。

image-20211202102224713

size是表示我们上传的实际大小,blocksize是指文件的最大块大小。

注意;这个block块是hdfs产生的,如果我们直接把文件上传到这个block文件所在的目录,这个时候hdfs是不识别的,没有用的。

假设我们上传了两个10M的文件 又上传了一个200M的文件
问1:会产生多少个block块? 4个
问2:在hdfs中会显示几个文件?3个

下面看一下副本,副本表示数据有多少个备份
我们现在的集群有两个从节点,所以最多可以有2个备份,这个是在hdfs-site.xml中进行配置的,
dfs.replication默认这个参数的配置是3。表示会有3个副本。
副本只有一个作用就是保证数据安全。

3.4.4 NameNode 总结

NameNode维护了两份关系:

  • 第一份关系:File与Block list的关系,对应的关系信息存储在fsimageedits文件中(当NameNode启动的时候会把文件中的源数据信息加载到内存中)
  • 第二份关系:DataNode与Block的关系,当DataNode启动时会把当前节点上的Block信息和节点信息上报给NameNode

注意了,刚才我们说了NameNode启动的时候会把文件中的元数据信息加载到内存中,然后每一个文件的元数据信息会占用150字节的内存空间,这个是恒定的,和文件大小没有关系,咱们前面在介绍HDFS的时候说过,HDFS不适合存储小文件,其实主要原因就在这里,不管是大文件还是小文件,一个文件的元数据信息在NameNode中都会占用150字节,NameNode节点的内存是有限的,所以它的存储能力也是有限的,如果我们存储了一堆都是几KB的小文件,最后发现NameNode的内存占满了,确实存储了很多文件,但是文件的总体大小却很小,这样就失去了HDFS存在的价值。

最后,在datanode的数据目录下面的current目录中也有一个VERSION文件

这个VERSIONnamenodeVERSION文件是有一些相似之处的,我们来具体对比一下两个文件的内
容。

namenodeVERSION文件

[root@master current]# cat VERSION 
#Thu Dec 02 09:40:18 CST 2021
namespaceID=276211861
clusterID=CID-a2d22775-7406-4788-ba91-13c7aaf13a6b
cTime=1638265933793
storageType=NAME_NODE
blockpoolID=BP-820095417-192.168.128.130-1638265933793
layoutVersion=-65

datanodeVERSION文件

[root@slave1 current]# cat VERSION 
#Thu Dec 02 09:40:22 CST 2021
storageID=DS-9ed24dd5-32ab-4d81-93c1-136b7b5c8949
clusterID=CID-a2d22775-7406-4788-ba91-13c7aaf13a6b
cTime=0
datanodeUuid=88d2c438-11ce-46ec-9081-f1b8ed515d69
storageType=DATA_NODE
layoutVersion=-57

我们前面说了namenode不要随便格式化,因为格式化了以后VERSION里面的clusterID会变,但是datanodeVERSION中的clusterID并没有变,所以就对应不上了。

咱们之前说过如果确实要重新格式化的话需要把/data/hadoop_repo数据目录下的内容都清空,全部都重新生成是可以的。

3.5 HDFS的回收站

我们windows系统里面有一个回收站,当想恢复删除的文件的话就可以到这里面进行恢复,HDFS也有回收站。

HDFS会为每一个用户创建一个回收站目录:/user/用户名/.Trash/,每一个被用户在Shell命令行删除的文件/目录,会进入到对应的回收站目录中,在回收站中的数据都有一个生存周期,也就是当回收站中的文件/目录在一段时间之内没有被用户恢复的话,HDFS就会自动把这个文件/目录彻底删除,之后,用户就永远也找不回这个文件/目录了。

默认情况下hdfs的回收站是没有开启的,需要通过一个配置来开启,在core-site.xml中添加如下配置,value的单位是分钟,1440分钟表示是一天的生存周期。

<property>
	<name>fs.trash.interval</name>
	<value>1440</value>
</property>

在修改配置信息之前先验证一下删除操作,显示的是直接删除掉了。

[root@master current]# hdfs dfs -rm /NOTICE.txt
Deleted /NOTICE.txt

修改回收站配置,现在master上操作,然后再同步到其他两个节点,先停止集群。

[root@master hadoop-3.2.0]# sbin/start-all.sh 

修改core-site.xml文件后发送给其他两个节点

[root@master hadoop-3.2.0]# vim etc/hadoop/core-site.xml 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
      You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

      Unless required by applicable law or agreed to in writing, software
      distributed under the License is distributed on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      See the License for the specific language governing permissions and
      limitations under the License. See accompanying LICENSE file.
    -->

    <!-- Put site-specific property overrides in this file. -->

    <configuration>
        <property>
            <name>fs.defaultFS</name>
            <value>hdfs://master:9000</value>
        </property>
        <property>
            <name>hadoop.tmp.dir</name>
            <value>/data/hadoop_repo</value>
        </property>
        <property>
            <name>fs.trash.interval</name>
            <value>1440</value>
        </property>
    </configuration>
    "etc/hadoop/core-site.xml" 32L, 1061C 已写入      
[root@master hadoop-3.2.0]# scp -rq etc/hadoop/core-site.xml 			    		s slave1:/data/soft/hadoop-3.2.0/etc/hadoop/
[root@master hadoop-3.2.0]# scp -rq etc/hadoop/core-site.xml 						slave2:/data/soft/hadoop-3.2.0/etc/hadoop/

再次启动集群。

[root@master hadoop-3.2.0]# sbin/staop-all.sh 

删除README.txt文件

[root@master hadoop-3.2.0]# hdfs dfs -rm /README.txt
2021-12-02 11:00:32,864 INFO fs.TrashPolicyDefault: Moved: 'hdfs://master:9000/README.txt' to trash at: hdfs://master:9000/user/root/.Trash/Current/README.txt

在网页目录中也可以看到多了一个user文件夹。

打开文件夹可以看到

image-20211202110702157

在目录/user/root/.Trash/Current下有我们刚删除的README.txt文件。

回收站的文件也是可以下载到本地的。其实在这回收站只是一个具备了特殊含义的HDFS目录。

注意:如果删除的文件过大,超过回收站大小的话会提示删除失败
需要指定参数 -skipTrash ,指定这个参数表示删除的文件不会进回收站
[root@master hadoop-3.2.0]# hdfs dfs -rm -skipTrash /user.txt
Deleted /user.txt

不止文件过大可以用,只要加上-skipTrash参数就不会进入回收站。类似与windows中的shitf+delete永久删除。

3.6 HDFS的安全模式

集群刚启动时HDFS会进入安全模式,此时无法执行写操作。(此时耐心等待HDFS自检完退出安全模式即可)

查看安全模式:hdfs dfsadmin -safemode get

离开安全模式:hdfs dfsadmin -safemode leave

[root@master hadoop-3.2.0]# hdfs dfsadmin -safemode get
Safe mode is OFF

3.7 HDFS实战:定时上传数据至HDFS

日志文件格式:access_2020_01_01.log

日志每天产生一个,需要每天凌晨上传到hdfs。

HDFS中的目录格式为:20200101

  1. 第一步:我们需要获取到昨天的日志文件的名称。
  2. 第二步:在HDFS上面使用昨天的日期创建目录
  3. 第三步:将昨天的日志文件上传到刚创建的HDFS目录中
  4. 第四步:要考虑脚本重跑,补数据的情况
  5. 第五步:配置crontab任务

3.7.1 编写shell脚本

# 获取昨天日期字符串,为了方便以后可以上传其他日期的数据,在这可加一个判断,如果给了日期就上传指定日期的数据,如果没给就同步昨天的数据
yesterday=$1
if [ "$yesterday" = "" ]
then
	yesterday=`date +%Y_%m_%d --date="1 days ago"`
fi
# 拼接日志文件
logpath=/data/log/access_${yesterday}.log

# 将日期字符串中的_去掉,并且拼接成hdfs的路径
hdfsPath=/log/${yesterday//_/}

# 在hdfs上面创建目录
hdfs dfs -mkdir -p ${hdfsPath}

# 将数据上传到hdfs的指定目录中
hdfs dfs -put ${logpath} ${hdfsPath}

3.7.2 配置crontab任务

0 1 * * * root sh /data/shell/uploadLogData.sh >> /data/shell/uploadLogData.log

每天凌晨1点进行同步,并将同步日期重定向到/data/shell/uploadLogData.log目录

3.8 HDFS的高可用和高扩展

  • NameNode节点宕机了怎么办?

    我们前面分析了NameNode负责接收用户的操作请求,所有的读写请求都会经过它,如果它挂了怎么办?

    这个时候集群是不是就无法正常提供服务了?是的,那现在我们这个集群就太不稳定了,因为NameNode只有一个,是存在单点故障的,咱们在现实生活中,例如,县长,是有正的和副的,这样就是为了解决当正县长遇到出差的时候,副县长可以顶上去。

    所以在HDFS的设计中,NameNode也是可以支持多个的,一个主的多个备用的,当主的挂掉了,备用的可以顶上去,这样就可以解决NameNode节点宕机导致的单点故障问题了,也就实现了HDFS的高可用

    还有一个问题是,前面我们说了NameNode节点的内存是有限的,只能存储有限的文件个数,那使用一个主NameNode,多个备用的NameNode能解决这个问题吗?

不能!

一个主NameNode,多个备用的NameNode的方案只能解决NameNode的单点故障问题,无法解决单个NameNode内存不够用的问题,那怎么办呢?不用担心,官方提供了Federation机制,可以翻译为联邦,它可以解决单节点内存不够用的情况,具体实现思路我们稍后分析,这个就是HDFS的高扩展

  • NameNode节点内存不够用怎么办?

    image-20211202134813484

3.8.1 高可用(HA

HDFSHA,指的是在一个集群中存在多个NameNode,分别运行在独立的物理节点上。在任何时间点,只有一个NameNode是处于Active状态,其它的是处于Standby状态。 Active NameNode(简写为Active NN)负责所有的客户端的操作,而Standby NameNode(简写为Standby NN)用来同步ActiveNameNode的状态信息,以提供快速的故障恢复能力。

为了保证Active NNStandby NN节点状态同步,即元数据保持一致。除了DataNode需要向这些NameNode发送block位置信息外,还构建了一组独立的守护进程”JournalNodes”(简写为JN),用来同步Edits信息。当Active NN执行任何有关命名空间的修改,它需要持久化到一半以上的JNs上。而Standby NN负责观察JNs的变化,读取从Active NN发送过来的Edits信息,并更新自己内部的命名空间。一旦Active NN遇到错误,Standby NN需要保证从JNs中读出了全部的Edits,然后切换成Active状态,如果有多个Standby NN,还会涉及到选主的操作,选择一个切换为Active 状态。

需要注意一点,为了保证Active NNStandby NN节点状态同步,即元数据保持一致。

这里的元数据包含两块,一个是静态的,一个是动态的

静态的是fsimageedits,其实fsimage是由edits文件合并生成的,所以只需要保证edits文件内容的一致性。这个就是需要保证多个NameNodeedits文件内容的事务性同步。这块的工作是由JournalNodes集群进行同步的。

动态数据是指blockDataNode节点的信息,这个如何保证呢?
DataNode启动的时候,上报数据信息的时候需要向每个NameNode都上报一份。这样就可以保证多个NameNode的元数据信息都一样了,当一个NameNode down掉以后,立刻从Standby NN中选择一个进行接管,没有影响,因为每个NameNode 的元数据时刻都是同步的。

注意:使用HA的时候,不能启动SecondaryNameNode,会出错。之前是SecondaryNameNode负责合并editsfsimage文件 那么现在这个工作被standby NN负责了。

NameNode 切换可以自动切换,也可以手工切换,如果想要实现自动切换,需要使用到zookeeper集群。

使用zookeeper集群自动切换的原理是这样的

当多个NameNode 启动的时候会向zookeeper中注册一个临时节点,当NameNode挂掉的时候,这个临时节点也就消失了,这属于zookeeper的特性,这个时候,zookeeper就会有一个watcher监视器监视到,就知道这个节点down掉了,然后会选择一个节点转为Active,把down掉的节点转为Standby
注意:下面的配置步骤建议大家有空闲时间了再来操作就行,作为一个课外扩展,因为在工作中这个配置是不需要我们做的,我们只需要知道这种特性即可。


总结:

  • HDFSHA,表示一个集群中存在多个NameNode,只有一个NameNodeActive状态,其它的是Standby状态
  • ActiveNameNode(ANN)负责左右客户端的操作,StandbyNameNode(SNN)用来同步ANN的状态信息,以提供快速故障恢复能力。
  • 使用HA的时候,不能启动SecondaryNameNode,会出错。

3.8.2 高扩展(Federation

Federation可解决单一命名空间的一些问题,提供以下特征:

  • HDFS集群扩展性
  • 性能更高效
  • 良好的隔离性

如果真用到了Federation,一般也会和前面我们讲的HA结合起来使用,来看这个图

image-20211202140818508

这里面用到了4个NameNode和6个DataNode

NN-1NN-2NN-3NN-4

DN-1DN-2DN-3DN-4DN-5DN-6

其中NN-1、和NN-3配置了HA,提供了一个命令空间,/share,其实可理解为一个顶级目录

NN-2NN-4配置了HA,提供了一个命名空间,/user这样后期我们存储数据的时候,就可以根据数据的业务类型来区分是存储到share目录下还是user目录下,此时HDFS的存储能力就是/share/user两个命名空间的总和了。

注意:由于Federation+HA需要的机器比较多,大家本地的机器开不了那么多虚拟机,所以暂时在这就不再提供对应的安装步骤了,大家主要能理解它的原理就可以了,在工作中也不需要我们去配置。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值