HDFS介绍

我们前面已经知道了HDFS是一个分布式的文件系统,具体这个分布式文件系统是如何实现的呢?

pyhdfs是什么包 hdfs是什么品牌_hadoop

HDFS的全称是Hadoop Distributed File System ,Hadoop的 分布式 文件 系统

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

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

还有 GFS(谷歌的)、TFS(淘宝的)、S3(亚马逊的)

为什么会有多种分布式文件系统呢?这样不是重复造轮子吗?

不是的,因为不同的分布式文件系统的特点是不一样的,HDFS是一种适合大文件存储的分布式文件系统,不适合小文件存储,什么叫小文件,例如,几KB,几M的文件都可以认为是小文件

HDFS的Shell介绍

针对HDFS,我们可以在shell命令行下进行操作,就类似于我们操作linux中的文件系统一样,但是具体命令的操作格式是有一些区别的

格式如下:

pyhdfs是什么包 hdfs是什么品牌_apache_02

使用hadoop bin目录的hdfs命令,后面指定dfs,表示是操作分布式文件系统的,这些属于固定格式。

HDFS的schema是hdfs,authority是集群中namenode所在节点的ip和对应的端口号,把ip换成主机名也是一样的,path是我们要操作的文件路径信息

其实后面这一长串内容就是core-site.xml配置文件中fs.defaultFS属性的值,这个代表的是HDFS的地址。

bin/hdfs dfs -ls / # 看hdfs根目录下的内容,功能类似于 -ls hdfs://bigdata01:9000/
bin/hdfs dfs -put README.txt / # 从本地上传文件
bin/hdfs dfs -cat /README.txt # 查看HDFS文件内容
bin/hdfs dfs -get /README.txt README.txt.bak # 下载文件到本地,并重命名
bin/hdfs dfs -rm -r /README.txt # 删除文件/文件夹
bin/hdfs dfs -mkdir -p /abc/test # 创建文件夹

其实后面hdfs的url这一串内容在使用时默认是可以省略的,因为hdfs在执行的时候会根据HADOOP_HOME自动识别配置文件中的fs.defaultFS属性

Java代码操作HDFS

<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-client</artifactId>
    <version>3.2.0</version>
</dependency>
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
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.InputStream;

/**
 * 操作hdfs
 */
public class TestHdfsClient {
    public static void main(String[] args) throws Exception {
        testPut();
    }

    private static FileSystem fileSystem() throws Exception {
        Configuration configuration = new Configuration();
        //设置hdfs地址
        configuration.set(CommonConfigurationKeysPublic.FS_DEFAULT_NAME_KEY, "hdfs://ip:9000");
        return FileSystem.get(configuration);
    }

    private static void testPut() throws Exception {
        //文件上传
        FileSystem fileSystem = fileSystem();
        FSDataOutputStream output = fileSystem.create(new Path("/abc.txt"));
        InputStream input = new FileInputStream("C:\\D-myfiles\\testjar\\hadoop\\abc.txt");
        IOUtils.copyBytes(input, output, 1024, true);
    }

}

测试将本地文件上传到远程hdfs

遇到的问题

第一个问题

Exception in thread "main" org.apache.hadoop.security.AccessControlException: Permission denied: user=xxx, access=WRITE, inode="/":root:supergroup:drwxr-xr-x
	at org.apache.hadoop.hdfs.server.namenode.FSPermissionChecker.check(FSPermissionChecker.java:399)
	at org.apache.hadoop.hdfs.server.namenode.FSPermissionChecker.checkPermission(FSPermissionChecker.java:255)
	at org.apache.hadoop.hdfs.server.namenode.FSPermissionChecker.checkPermission(FSPermissionChecker.java:193)
	at org.apache.hadoop.hdfs.server.namenode.FSDirectory.checkPermission(FSDirectory.java:1869)
	at org.apache.hadoop.hdfs.server.namenode.FSDirectory.checkPermission(FSDirectory.java:1853)
	at org.apache.hadoop.hdfs.server.namenode.FSDirectory.checkAncestorAccess(FSDirectory.java:1812)
	at org.apache.hadoop.hdfs.server.namenode.FSDirWriteFileOp.resolvePathForStartFile(FSDirWriteFileOp.java:323)
	at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.startFileInt(FSNamesystem.java:2472)
	at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.startFile(FSNamesystem.java:2416)
	at org.apache.hadoop.hdfs.server.namenode.NameNodeRpcServer.create(NameNodeRpcServer.java:775)

解决办法有3种

  1. 第一种:去掉hdfs的用户权限检验机制,通过在hdfs-site.xml中配置dfs.permissions.enabled为false即可
  2. 第二种:把代码打包到linux中执行
  3. 第三种:将windows用户添加到supergroup中
adduser xxx
groupadd supergroup
usermod -a -G supergroup xxx

在这里为了在本地测试方便,我们使用第一种方式。
1:停止Hadoop集群
2:修改hdfs-site.xml配置文件
3:启动Hadoop集群

<property>
    <name>dfs.permissions.enabled</name>
    <value>false</value>
</property>

第二个问题

Exception in thread "main" org.apache.hadoop.ipc.RemoteException(java.io.IOException): File /abc.txt could only be written to 0 of the 1 minReplication nodes. There are 1 datanode(s) running and 1 node(s) are excluded in this operation.
	at org.apache.hadoop.hdfs.server.blockmanagement.BlockManager.chooseTarget4NewBlock(BlockManager.java:2135)
	at org.apache.hadoop.hdfs.server.namenode.FSDirWriteFileOp.chooseTargetForNewBlock(FSDirWriteFileOp.java:294)
	at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.getAdditionalBlock(FSNamesystem.java:2771)
	at org.apache.hadoop.hdfs.server.namenode.NameNodeRpcServer.addBlock(NameNodeRpcServer.java:876)
	at org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolServerSideTranslatorPB.addBlock(ClientNamenodeProtocolServerSideTranslatorPB.java:567)
	at org.apache.hadoop.hdfs.protocol.proto.ClientNamenodeProtocolProtos$ClientNamenodeProtocol$2.callBlockingMethod(ClientNamenodeProtocolProtos.java)
	at org.apache.hadoop.ipc.ProtobufRpcEngine$Server$ProtoBufRpcInvoker.call(ProtobufRpcEngine.java:524)
	at org.apache.hadoop.ipc.RPC$Server.call(RPC.java:1025)
	at org.apache.hadoop.ipc.Server$RpcCall.run(Server.java:876)
	at org.apache.hadoop.ipc.Server$RpcCall.run(Server.java:822)
	at java.security.AccessController.doPrivileged(Native Method)
	at javax.security.auth.Subject.doAs(Subject.java:422)
	at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1730)
	at org.apache.hadoop.ipc.Server$Handler.run(Server.java:2682)

折腾了大半天,终于解决了,具体原因是:
NameNode节点存放的是文件目录,也就是文件夹、文件名称,本地可以通过公网访问 NameNode,所以可以进行文件夹的创建,当上传文件需要写入数据到DataNode时,NameNode 和 DataNode 是通过局域网进行通信,NameNode返回地址为 DataNode 的私有 IP,本地无法访问。

解决方案:返回的IP地址无法返回公网IP,只能返回主机名,通过主机名与公网地址的映射便可以访问到DataNode节点,问题将解决。

private static FileSystem fileSystem() throws Exception {
        Configuration configuration = new Configuration();
        //设置hdfs地址
        configuration.set(CommonConfigurationKeysPublic.FS_DEFAULT_NAME_KEY, "hdfs://bigdata01:9000");
        configuration.set("dfs.client.use.datanode.hostname", "true");
        return FileSystem.get(configuration);
    }

本地还需要配置一下DNS域名映射,在C:\Windows\System32\drivers\etc的hosts文件中配置,

ip bigdata01

注意:这边建议把原文件复制一份到外面操作,完成之后再粘贴回来。

HDFS体系结构

HDFS支持主从结构,主节点称为 NameNode ,是因为主节点上运行的有NameNode进程,NameNode支持多个,目前我们的集群中只配置了一个

从节点称为 DataNode ,是因为从节点上面运行的有DataNode进程,DataNode支持多个。HDFS中还包含一个 SecondaryNameNode 进程,这个进程从字面意思上看像是第二个NameNode的意思,其实不是。

在这大家可以这样理解:

  • 公司BOSS:NameNode
  • 秘书:SecondaryNameNode
  • 员工:DataNode

pyhdfs是什么包 hdfs是什么品牌_hadoop_03

NameNode介绍

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

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

  • 目录树:表示目录之间的层级关系,就是我们在hdfs上执行ls命令可以看到的那个目录结构信息。
  • 文件/目录的信息:表示文件/目录的的一些基本信息,所有者 属组 修改时间 文件大小等信息
  • 每个文件对应的数据块列表:如果一个文件太大,那么在集群中存储的时候会对文件进行切割,这个时候就类似于会给文件分成一块一块的,存储到不同机器上面。所以HDFS还要记录一下一个文件到底被分了多少块,每一块都在什么地方存储着

我们现在可以到集群的9870界面查看一下,随便找一个文件看一下,点击文件名称,可以看到Block information 但是文件太小,只有一个块 叫Block 0

pyhdfs是什么包 hdfs是什么品牌_hadoop_04

NameNode主要包括以下文件:

pyhdfs是什么包 hdfs是什么品牌_apache_05

这些文件所在的路径是由hdfs-default.xml的dfs.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中同名的参数。

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

这个属性的值是由hadoop.tmp.dir属性控制的,这个属性的值默认在core-default.xml文件中。大家还有没有印象,我们在修改core-site.xml的时候设置的有hadoop.tmp.dir属性的值,值是/root/test_hadoop/tmp,所以说core-site.xml中的hadoop.tmp.dir属性会覆盖掉core-default.xml中的值,最终dfs.namenode.name.dir属性的值就是:root/test_hadoop/tmp/dfs/name

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

SecondaryNameNode介绍

SecondaryNameNode主要负责定期的把edits文件中的内容合并到fsimage中

这个合并操作称为checkpoint,在合并的时候会对edits中的内容进行转换,生成新的内容保存到fsimage文件中。

注意:在NameNode的HA架构中没有SecondaryNameNode进程,文件合并操作会由standby NameNode负责实现

所以在Hadoop集群中,SecondaryNameNode进程并不是必须的。

DataNode介绍

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

针对datanode主要掌握两个概念,一个是block,一个是replication

首先是block

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

Blokc块是HDFS读写数据的基本单位,不管你的文件是文本文件 还是视频 或者音频文件,针对hdfs而言 都是字节。

我们之前上传的一个user.txt文件,他的block信息可以在fsimage文件中看到,也可以在hdfs web界面上面看到, 里面有block的id信息,并且也会显示这个数据在哪个节点上面

pyhdfs是什么包 hdfs是什么品牌_pyhdfs是什么包_06

datanode中数据的具体存储位置是由dfs.datanode.data.dir来控制的

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

下面看一下副本

副本表示数据有多少个备份

我们现在的集群有两个从节点,所以最多可以有2个备份,这个是在hdfs-site.xml中进行配置的,dfs.replication

默认这个参数的配置是3。表示会有3个副本。

副本只有一个作用就是保证数据安全。

NameNode总结

注意:block块存放在哪些datanode上,只有datanode自己知道,当集群启动的时候,datanode会扫描自己节点上面的所有block块信息,然后把节点和这个节点上的所有block块信息告诉给namenode。这个关系是每次重启集群都会动态加载的【这个其实就是集群为什么数据越多,启动越慢的原因】

namenode维护了两份关系:

  • 第一份关系:file 与block list的关系,对应的关系信息存储在fsimage和edits文件中,当NameNode启动的时候会把文件中的元数据信息加载到内存中
  • 第二份关系:datanode与block的关系,对应的关系主要在集群启动的时候保存在内存中,当DataNode启动时会把当前节点上的Block信息和节点信息上报给NameNode

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

HDFS的回收站

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

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

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

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

注意:如果删除的文件过大,超过回收站大小的话会提示删除失败 需要指定参数 -skipTrash ,指定这个参数表示删除的文件不会进回收站

HDFS的安全模式

大家在平时操作HDFS的时候,有时候可能会遇到这个问题,特别是刚启动集群的时候去上传或者删除文件,会发现报错,提示NameNode处于safe mode。

这个属于HDFS的安全模式,因为在集群每次重新启动的时候,HDFS都会检查集群中文件信息是否完整,例如副本是否缺少之类的信息,所以这个时间段内是不允许对集群有修改操作的,如果遇到了这个情况,可以稍微等一会,等HDFS自检完毕,就会自动退出安全模式。

pyhdfs是什么包 hdfs是什么品牌_hadoop_07

或者通过hdfs命令也可以查看当前的状态

bin/hdfs dfsadmin -safemode get

Safe mode is OFF

HDFS写数据过程

流程图-processon.com

总结

使用google搜索也可能搜不到想要的结果,这种时候可以换一个搜索关键词,将使用场景带上去,如hadoop文件上传,hdfs文件上传等。

参考

解决File ~ could only be written to 0 of the 1 minReplication nodes.HDFS服务器使用命令可以上传文件,但客户端上传失败问题File ~ could only be written to 0 of the 1 minReplication nodes.