Hadoop之HDFS简介

719 阅读11分钟

HDFS介绍

HDFS的全称是Hadoop Distributed File System ,Hadoop的分布式文件系统
它是一种允许文件通过网络在多台主机上分享的文件系统,可以让多台机器上的多个用户分享文件和存储空间
其实分布式文件管理系统有很多,HDFS只是其中一种实现而已
还有 GFS(谷歌的)、TFS(淘宝的)、S3(亚马逊的)
为什么会有多种分布式文件系统呢?这样不是重复造轮子吗?
不是的,因为不同的分布式文件系统的特点是不一样的,HDFS是一种适合大文件存储的分布式文件系统,不适合小文件存储,什么叫小文件,例如,几KB,几M的文件都可以认为是小文件
我们前面已经知道了HDFS是一个分布式的文件系统,具体这个分布式文件系统是如何实现的呢? 如下图:

HDFS的Shell介绍

通过前面的学习,我们对HDFS有了基本的了解,下面我们就想实际操作一下,来通过实操加深对HDFS的理解
针对HDFS,我们可以在shell命令行下进行操作,就类似于我们操作linux中的文件系统一样,但是具体命令的操作格式是有一些区别的
格式如下:

使用hadoop bin目录的hdfs命令,后面指定dfs,表示是操作分布式文件系统的,这些属于固定格式。
如果在PATH中配置了hadoop的bin目录,那么这里可以直接使用hdfs就可以了
这里的xxx是一个占位符,具体我们想对hdfs做什么操作,就可以在这里指定对应的命令了
大多数hdfs 的命令和对应的Linux命令类似
HDFS的schema是hdfs,authority是集群中namenode所在节点的ip和对应的端口号,把ip换成主机名也是一样的,path是我们要操作的文件路径信息
其实后面这一长串内容就是core-site.xml配置文件中fs.defaultFS属性的值,这个代表的是HDFS的地址。

HDFS的常见Shell操作

下面我们就来学习一下HDFS中的一些常见的shell操作
其实hdfs后面支持很多的参数,但是有很多是很少用的,在这里我们把一些常用的带着大家一块学习一下,如果大家后期有一些特殊的需求,可以试着来看一下hdfs的帮助文档
直接在命令行中输入hdfs dfs,可以查看dfs后面可以跟的所有参数
注意:这里面的[]表示是可选项,<>表示是必填项

-ls:查询指定路径信息

首先看第一个ls命令
查看hdfs根目录下的内容,什么都不显示,因为默认情况下hdfs中什么都没有

[root@bigdata01 hadoop-3.2.0]# hdfs dfs -ls hdfs://bigdata01:9000/ 
[root@bigdata01 hadoop-3.2.0]#

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

[root@bigdata01 hadoop-3.2.0]# hdfs dfs -ls / 
[root@bigdata01 hadoop-3.2.0]#

-put:从本地上传文件

接下来我们向hdfs中上传一个文件,使用Hadoop中的README.txt,直接上传到hdfs的根目录即可

[root@bigdata01 hadoop-3.2.0]# hdfs dfs -put README.txt /

上传成功之后没有任何提示,注意,没有提示就是最好的结果
确认一下刚才上传的文件

[root@bigdata01 hadoop-3.2.0]# hdfs dfs -ls / 
Found 1 items 
-rw-r--r-- 2 root supergroup 1361 2020-04-08 15:34 /README.txt

在这里可以发现使用hdfs中的ls查询出来的信息和在linux中执行ll查询出来的信息是类似的

-cat:查看HDFS文件内容

[root@bigdata01 hadoop-3.2.0]# hdfs dfs -cat /README.txt

-get:下载文件到本地

如果我们想把hdfs中的文件下载到本地linux文件系统中需要怎么做呢?使用get即可实现

[root@bigdata01 hadoop-3.2.0]# hdfs dfs -get /README.txt .  //.表示下载到当前目录
get: `README.txt': File exists

注意:这样执行报错了,提示文件已存在,我这条命令的意思是要把HDFS中的README.txt下载当前目录中,但是当前目录中已经有这个文件了,要么换到其它目录,要么给文件重命名

-mkdir [-p]:创建文件夹

后期我们需要在hdfs中维护很多文件,所以就需要创建文件夹来进行分类管理了
下面我们来创建一个文件夹,hdfs中使用mkdir命令

[root@bigdata01 hadoop-3.2.0]# hdfs dfs -mkdir /test 
[root@bigdata01 hadoop-3.2.0]# hdfs dfs -ls / 
Found 2 items 
-rw-r--r-- 2 root supergroup 1361 2020-04-08 15:34 /README.txt 
drwxr-xr-x - root supergroup 0 2020-04-08 15:43 /test

如果要递归创建多级目录,还需要再指定-p参数

[root@bigdata01 hadoop-3.2.0]# hdfs dfs -mkdir -p /abc/xyz

想要递归显示所有目录的信息,可以在ls后面添加-R参数

[root@bigdata01 hadoop-3.2.0]# hdfs dfs -ls -R / 
-rw-r--r-- 2 root supergroup 1361 2020-04-08 15:34 /README.txt 
drwxr-xr-x - root supergroup 0 2020-04-08 15:44 /abc 
drwxr-xr-x - root supergroup 0 2020-04-08 15:44 /abc/xyz 
drwxr-xr-x - root supergroup 0 2020-04-08 15:43 /test 

-rm [-r]:删除文件/文件夹

如果想要删除hdfs中的目录或者文件,可以使用rm
删除文件

[root@bigdata01 hadoop-3.2.0]# hdfs dfs -rm /README.txt 
Deleted /README.txt

删除目录,注意,删除目录需要指定-r参数

[root@bigdata01 hadoop-3.2.0]# hdfs dfs -rm /test 
rm: `/test': Is a directory
[root@bigdata01 hadoop-3.2.0]# hdfs dfs -rm -r /test 
Deleted /test

如果是多级目录,可以递归删除吗?可以

[root@bigdata01 hadoop-3.2.0]# hdfs dfs -rm -r /abc 
Deleted /abc

HDFS案例实操

需求:统计HDFS中文件的个数和每个文件的大小
我们先向HDFS中上传几个文件,把hadoop目录中的几个txt文件上传上去

[root@bigdata01 hadoop-3.2.0]# hdfs dfs -put LICENSE.txt / 
[root@bigdata01 hadoop-3.2.0]# hdfs dfs -put NOTICE.txt / 
[root@bigdata01 hadoop-3.2.0]# hdfs dfs -put README.txt /

1:统计根目录下文件的个数

[root@bigdata01 hadoop-3.2.0]# hdfs dfs -ls / |grep /| wc -l 
3

2、统计文件大小

[root@bigdata01 hadoop-3.2.0]# hdfs dfs -ls / |grep /| awk '{print $8,$5}'

Java操作HDFS

<dependencies>
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-client</artifactId>
        <version>3.2.1</version>
    </dependency>
</dependencies>
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
 */
public class HDFSOp {
    public static void main(String[] args) throws Exception{
        // 创建一个配置对象
        Configuration conf = new Configuration();
        // 指定HDFS的地址
        conf.set("fs.defaultFS","hdfs://192.168.170.128:9000");
        // 获取操作HDFS的对象
        FileSystem fileSystem = FileSystem.get(conf);
        // 上传文件
        // 获取本地文件的输入流
        FileInputStream fis = new FileInputStream("C:\\Users\\***\\Desktop\\test.txt");
        // 获取HDFS文件系统的输出流
        FSDataOutputStream fos = fileSystem.create(new Path("/user.txt"));
        // 通过工具类把输入流拷贝到输出流,实现本地文件上传到hdfs
        IOUtils.copyBytes(fis,fos,1024,true);
    }
}

启动测试,报错如下:
解决办法有两个
第一种:去掉hdfs的用户权限检验机制,通过在hdfs-site.xml中配置dfs.permissions.enabled为false即可
第二种:把代码打包到linux中执行
在这里为了在本地测试方便,我们先使用第一种方式
1:停止Hadoop集群

[root@bigdata01 hadoop-3.2.0]# sbin/stop-all.sh

2:修改hdfs-site.xml配置文件
注意:集群内所有节点中的配置文件都需要修改,先在bigdata01节点上修改,然后再同步到另外两个节点上
在bigdata01上操作

[root@bigdata01 hadoop-3.2.0]# vi etc/hadoop/hdfs-site.xml 
<configuration> 
    <property> 
        <name>dfs.replication</name> 
        <value>2</value>
    </property> 
    <property> 
        <name>dfs.namenode.secondary.http-address</name> 
        <value>bigdata01:50090</value> 
    </property> 
    <property> 
        <name>dfs.permissions.enabled</name>
        <value>false</value> 
    </property> 
</configuration>

同步到另外两个节点中

[root@bigdata01 hadoop-3.2.0]# scp -rq etc/hadoop/hdfs-site.xml bigdata02:/ //相应的位置
[root@bigdata01 hadoop-3.2.0]# scp -rq etc/hadoop/hdfs-site.xml bigdata03:/

3:启动Hadoop集群

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

测试成功。

  • 下载操作 下面还需要实现其他功能,对代码进行封装提取
 private static void get(FileSystem fileSystem) throws IOException { 
        //获取HDFS文件系统的输入流 
        FSDataInputStream fis = fileSystem.open(new Path("/README.txt")); 
        //获取本地文件的输出流 
        FileOutputStream fos = new FileOutputStream("D:\\README.txt"); 
        //下载文件
        IOUtils.copyBytes(fis, fos, 1024, true);
    }
  • 删除操作
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体系结构

HDFS支持主从结构,主节点称为 NameNode ,是因为主节点上运行的有NameNode进程,NameNode支持多个,目前我们的集群中只配置了一个
从节点称为 DataNode ,是因为从节点上面运行的有DataNode进程,DataNode支持多个,目前我们的集群中有两个
HDFS中还包含一个 SecondaryNameNode 进程,这个进程从字面意思上看像是第二个NameNode的意思,其实不是,一会我们会详细分析。
在这大家可以这样理解:
公司BOSS:NameNode
秘书:SecondaryNameNode
员工:DataNode
接着看一下这张图,这就是HDFS的体系结构,这里面的TCP、RPC、HTTP表示是不同的网络通信方式,通过这张图是想加深大家对HDFS体系结构的理解,我们前面配置的集群NameNode和SecondaryNameNode进程在同一台机器上面,在这个图里面是把它们分开到多台机器中了。

那接下来我们就详细分析这里面的每一个进程。

NameNode介绍

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

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

NameNode主要包括以下文件:


这些文件所在的路径是由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中同名的参数。
那我们来看一下这个文件中的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文件中。大家还有没有印象,我们在修改core-site.xml的时候设置的有hadoop.tmp.dir属性的值,值是/usr/local/hadoop_repo,所以说core-site.xml中的hadoop.tmp.dir属性会覆盖掉core-default.xml中的值
最终dfs.namenode.name.dir属性的值就是:/usr/local/hadoop_repo/dfs/name
那我们到bigdata01节点上看一下
进入到/usr/local/hadoop_repo/dfs/name目录下
发现这个下面会有一个current 目录,表示当前的意思,还有一个in_use.lock 这个只是一个普通文件,但是它其实有特殊的含义,你看他的文件名后缀值lock 表示是锁的意思,文件名是in_use 表示这个文件现在正在使用,不允许你再启动namenode。
当我们启动namonde的时候 会判断这个目录下是否有in_use.lock 这个相当于一把锁,如果没有的话,才可以启动成功,启动成功之后就会加一把锁, 停止的时候会把这个锁去掉

进入current目录,发现里面有edits文件 和fsimage文件
fsimage文件有两个文件名相同的,有一个后缀是md5 md5是一种加密算法,这个其实主要是为了做md5校验的,为了保证文件传输的过程中不出问题,相同内容的md5是一样的,所以后期如果我把这个fsimage和对应的fsimage.md5发给你 然后你根据md5对fsimage的内容进行加密,获取一个值 和fsimage.md5中的内容进行比较,如果一样,说明你接收到的文件就是完整的。
我们在网站下载一些软件的时候 也会有一些md5文件,方便验证下载的文件是否完整。
在这里可以把fsimage 拆开 fs 是文件系统 filesystem image是镜像
说明是文件系统镜像,就是给文件照了一个像,把文件的当前信息记录下来

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

SecondaryNameNode 介绍

SecondaryNameNode主要负责定期的把edits文件中的内容合并到fsimage中
这个合并操作称为checkpoint,在合并的时候会对edits中的内容进行转换,生成新的内容保存到fsimage文件中。
注意:在NameNode的HA架构中没有SecondaryNameNode进程,文件合并操作会由standbyNameNode负责实现 所以在Hadoop集群中,SecondaryNameNode进程并不是必须的。

DataNode介绍

DataNode是提供真实文件数据的存储服务
针对datanode主要掌握两个概念,一个是block,一个是replication
首先是block
HDFS会按照固定的大小,顺序对文件进行划分并编号,划分好的每一个块称一个Block,HDFS默认Block大小是 128MB。 Blokc块是HDFS读写数据的基本单位,不管你的文件是文本文件 还是视频 或者音频文件,针对hdfs而言都是字节。
我们之前上传的一个user.txt文件,他的block信息可以在fsimage文件中看到,也可以在hdfs webui上面看到, 里面有block的id信息,并且也会显示这个数据在哪个节点上面
这里显示在bigdata02和bigdata03上面都有,那我们过去看一下,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 tagg with corresponding storage types ([SSD]/[DISK]/[ARCHIVE]/[RAM_DISK]) for HD storage policies. The default storage type will be DISK if the directory do not have a storage type tagged explicitly. Directories that do not exist wi be created if local filesystem permission allows. 
    </description> 
</property>

那我们连接到bigdata02这个节点上去看一下,注意要到从节点上看,bigdata01上看不到信息的。

然后进入current目录,继续一路往下走


注意: 这里面的.meta文件也是做校验用的。

注意:这个block中的内容可能只是文件的一部分,如果你的文件较大的话,就会分为多个block存储,默认 hadoop3中一个block的大小为128M。根据字节进行截取,截取到128M就是一个block。如果文件大小没有默认的block块大,那最终就只有一个block。
HDFS中,如果一个文件小于一个数据块的大小,那么并不会占用整个数据块的存储空间
size是表示我们上传文件的实际大小,blocksize是指文件的最大块大小。
注意;这个block块是hdfs产生的,如果我们直接把文件上传到这个block文件所在的目录,这个时候hdfs是不识别的,没有用的

假设我们上传了两个10M的文件 又上传了一个200M的文件
问1:会产生多少个block块? 4个
问2:在hdfs中会显示几个文件?3个
下面看一下副本,副本表示数据有多少个备份
我们现在的集群有两个从节点,所以最多可以有2个备份,这个是在hdfs-site.xml中进行配置的,dfs.replication
默认这个参数的配置是3。表示会有3个副本。
副本只有一个作用就是保证数据安全。

总结

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

咱们之前说的fsimage(edits)文件中保存的有文件和block块之间的信息。
这里说的是block块和节点之间的关系,这两块关联在一起之后,就可以根据文件找到对应的block块,再根据block块找到对应的datanode节点,这样就真正找到了数据。
所以说 其实namenode中不仅维护了文件和block块的信息 还维护了block块和所在的datanode节点的信息。 可以理解为namenode维护了两份关系:
第一份关系:file 与block list的关系,对应的关系信息存储在fsimage和edits文件中,当NameNode启动的时候会把文件中的元数据信息加载到内存中
第二份关系:datanode与block的关系,对应的关系主要在集群启动的时候保存在内存中,当DataNode启动时会把当前节点上的Block信息和节点信息上报给NameNode

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

解释下为什么不能轻易格式化,除了一开始的初始化
最后,在datanode的数据目录下面的current目录中也有一个VERSION文件
这个VERSION和namenode的VERSION文件是有一些相似之处的,我们来具体对比一下两个文件的内容。
namenode的VERSION文件

[root@bigdata01 current]# cat VERSION 
#Wed Apr 08 20:30:00 CST 2020 
namespaceID=498554338 
clusterID=CID-cc0792dd-a861-4a3f-9151-b0695e4c7e70 
cTime=1586268855170 
storageType=NAME_NODE 
blockpoolID=BP-1517789416-192.168.182.100-1586268855170 
layoutVersion=-65

datanode的VERSION文件

[root@bigdata02 current]# cat VERSION 
#Wed Apr 08 20:30:04 CST 2020 
storageID=DS-0e86cd27-4961-4526-bacb-3b692a90b1b0 
clusterID=CID-cc0792dd-a861-4a3f-9151-b0695e4c7e70 
cTime=0 
datanodeUuid=0b09f3d7-442d-4e28-b3cc-2edb0991bae3 
storageType=DATA_NODE 
layoutVersion=-57

我们前面说了namenode不要随便格式化,因为格式化了以后VERSION里面的clusterID会变,但是datanode的VERSION中的clusterID并没有变,所以就对应不上了。
咱们之前说过如果确实要重新格式化的话需要把/usr/local/hadoop_repo数据目录下的内容都清空,全部都重新生成是可以的。