Hadoop生态之HDFS
Posted 红糖番薯
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Hadoop生态之HDFS相关的知识,希望对你有一定的参考价值。
今天我们要了解的是Hadoop生态中的HDFS,什么是HDFS呢?
如果把Hadoop当作王者荣耀来分析的话,那么HDFS的功能呢。就好比我们的点券,只有你充钱了,麻花总部收到了,这时候HDFS的作用就发挥出来了,那面后台就会把你充的钱转化为点券上传上去,这样诸佬们的理解是不是会深刻了呢。
Hadoop生态之HDFS
1.HDFS概述
1.1 HDFS产出背景及定义
1)HDFS产生背景
随着数据量越来越大,在一个操作系统存不下所有的数据,那么就分配到更多的操作系统管理的磁盘中,但是不方便管理和维护,迫切需要一种系统来管理多台机器上的文件,这就是分布式文件管理系统。HDFS只是分布式文件管理系统中的一种。
2)HDFS定义
HDFS(Hadoop Distributed File System),它是一个文件系统,用于存储文件,通过目录树来定位文件;其次,它是分布式的,由很多服务器联合起来实现其功能,集群中的服务器有各自的角色。
HDFS的使用场景:适合一次写入,多次读出的场景。一个文件经过创建、写入和关闭之后就不需要改变。
1.2 HDFS优缺点
优点
1)高容错性
- 数据自动保存多个副本。它通过增加副本的形式,来提高集群容错性。
- 某一个副本丢失之后,它可以自动恢复。
2)适合处理大数据
- 数据规模:能够处理数据规模达到GB,TB,甚至是PB规模的数据。
- 文件规模:能够处理百万规模以上的文件数据,数量相当之大。
3)可构建在廉价机器上,通过多副本机制,提高集群可靠性。
缺点
1)不适合低延时数据访问,比如毫秒级的存储数据,是做不到的。
2)无法高效的对大量小文件进行存储。
- 存储大量小文件的话,会占用NameNode大量的内存来存储文件目录和块信息。这样对资源的浪费是非常严重的,因为NameNode的内存总是有限的嘛。
- 小文件存储的寻址时间会超过读取时间,违反了当时HDFS的设计目标。
3)不支持并发写入,文件随即修改。
- 一个文件只能有一个写,不允许多个线程同时。
- 仅支持数据append(追加),不支持文件的随机修改。
1.3HDFS组成架构
HDFS 是一个主从 Master/Slave 架构。一个 HDFS 集群包含一个 NameNode,这是一个 Master Server,用来管理文件系统的命名空间,以及调节客户端对文件的访问。一个 HDFS 集群还包括多个 DataNode,用来存储数据。
HDFS 会对外暴露一个文件系统命名空间,并允许用户数据以文件的形式进行存储。在内部,一个文件被分成多个块并且这些块被存储在一组 DataNode 上。
1)NameNode
文件的元数据采用集中式存储方案存放在 NameNode 当中。NameNode 负责执行文件系统命名空间的操作,如打幵、关闭、重命名文件和目录。NameNode 同时也负责将数据块映射到对应的 DataNode 中。
2) DataNode
DataNode 是文件系统的工作结点。它们根据需要存储并检索数据块,并且定期向 NameNode 发送他们所存储的块的列表。文件数据块本身存储在不同的 DataNode 当中,DataNode 可以分布在不同机架上。
DataNode 负责服务文件系统客户端发出的读/写请求。DataNode 同时也负责接收 NameNode 的指令来进行数据块的创建、删除和复制。
3)Client
HDFS 的 Client 会分别访问 NameNode 和 DataNode 以获取文件的元信息及内容。HDFS 集群的 Client 将直接访问 NameNode 和 DataNode,相关数据会直接从 NameNode 或者 DataNode 传送到客户端。
NameNode 和 DataNode 都是被设计为在普通 PC 上运行的软件程序。HDFS 是用 Java 语言实现的,任何支持 Java 语言的机器都可以运行 NameNode 或者 DataNode。Java 语言本身的可移植性意味着 HDFS 可以被广泛地部署在不同的机器上。
一个典型的部署就是,集群中的一台专用机器运行 NameNode,集群中的其他机器每台运行一个 DataNode 实例。该架构并不排除在同一台机器上运行多个 DataNode 实例的可能,但在实际的部署中很少会这么做。
单一 NameNode 的设计极大地简化了集群的系统架构,它使得所有 HDFS 元数据的仲裁和存储都由单一 NameNode 来决定,避免了数据不一致性的问题。
博主整理的架构也是关于自身理解哈,感谢大家提出建议。
1.4HDFS文件块大小(面试重点)
HDFS中的文件在物理上是分块存储(Block),块的大小可以通过配置参数(dfs.blocksize)来规定的,默认大小在Hadoop2.x/3.x中是128M,在1.x版本中是64M。
2)如果寻址时间约为100ms,即查找到目标block的时间为10ms。
3)寻址时间为传输时间的1%时,则为最佳状态。(专家)
因此,传播时间=100ms/0.01=1000ms=1s
4)而目前磁盘的传输速率普遍为100MB/S。
但是为什么文件快不能设置太小也不能设置太大呢?
(1)HDFS的块设置太小,会增加寻址时间,程序会一直寻找块的开始位置。
(2)如果块设置的太大,从磁盘传输数据的时间会明显大于定位这个块开始位置所需的时间,导致程序在处理这块数据时,会变得很慢。
并且HDFS的块大小设置主要取决于磁盘的传输速率。
2.HDFS的读写流程(面试非常重要)
HDFS 的文件访问机制为流式访问机制,即通过 API 打开文件的某个数据块之后,可以顺序读取或者写入某个文件。由于 HDFS 中存在多个角色,且对应的应用场景主要为一次写入、多次读取的场景,因此其读和写的方式有较大不同。读/写操作都由客户端发起,并且由客户端进行整个流程的控制,NameNode 和 DataNode 都是被动式响应。
读取流程
客户端发起读取请求时,首先与 NameNode 进行连接。
连接建立完成后,客户端会请求读取某个文件的某一个数据块。NameNode 在内存中进行检索,查看是否有对应的文件及文件块,若没有则通知客户端对应文件或数据块不存在,若有则通知客户端对应的数据块存在哪些服务器之上。
客户端接收到信息之后,与对应的 DataNode 连接,并开始进行数据传输。客户端会选择离它最近的一个副本数据进行读操作。
如图 1 所示,读取文件的具体过程如下。
-
客户端调用 DistributedFileSystem 的 Open() 方法打开文件。
-
DistributedFileSystem 用 RPC 连接到 NameNode,请求获取文件的数据块的信息;NameNode 返回文件的部分或者全部数据块列表;对于每个数据块,NameNode 都会返回该数据块副本的 DataNode 地址;DistributedFileSystem 返回 FSDataInputStream 给客户端,用来读取数据。
-
客户端调用 FSDataInputStream 的 Read() 方法开始读取数据。
-
FSInputStream 连接保存此文件第一个数据块的最近的 DataNode,并以数据流的形式读取数据;客户端多次调用 Read(),直到到达数据块结束位置。
-
FSInputStream连接保存此文件下一个数据块的最近的 DataNode,并读取数据。
-
当客户端读取完所有数据块的数据后,调用 FSDataInputStream 的 Close() 方法。
HDFS读取流程
图 1 HDFS 读取流程
在读取数据的过程中,如果客户端在与数据结点通信时出现错误,则尝试连接包含此数据块的下一个数据结点。失败的数据结点将被记录,并且以后不再连接。
写入流程
写入文件的过程比读取较为复杂,在不发生任何异常情况下,客户端向 HDFS 写入数据的流程如图 2 所示,具体步骤如下。
HDFS 写入流程
图 2 HDFS 读取流程
-
客户端调用 DistribuedFileSystem 的 Create() 方法来创建文件。
-
DistributedFileSystem 用 RPC 连接 NameNode,请求在文件系统的命名空间中创建一个新的文件;NameNode 首先确定文件原来不存在,并且客户端有创建文件的权限,然后创建新文件;DistributedFileSystem 返回 FSOutputStream 给客户端用于写数据。
-
客户端调用 FSOutputStream 的 Write() 函数,向对应的文件写入数据。
-
当客户端开始写入文件时,FSOutputStream 会将文件切分成多个分包(Packet),并写入其內部的数据队列。FSOutputStream 向 NameNode 申请用来保存文件和副本数据块的若干个 DataNode,这些 DataNode 形成一个数据流管道。
队列中的分包被打包成数据包,发往数据流管道中的第一个 DataNode。第一个 DataNode 将数据包发送给第二个 DataNode,第二个 DataNode 将数据包发送到第三个 DataNode。这样,数据包会流经管道上的各个 DataNode。
-
为了保证所有 DataNode 的数据都是准确的,接收到数据的 DataNode 要向发送者发送确认包(ACK Packet)。确认包沿着数据流管道反向而上,从数据流管道依次经过各个 DataNode,并最终发往客户端。当客户端收到应答时,它将对应的分包从内部队列中移除。
-
不断执行第 (3)~(5)步,直到数据全部写完。
-
调用 FSOutputStream 的 Close() 方法,将所有的数据块写入数据流管道中的数据结点,并等待确认返回成功。最后通过 NameNode 完成写入。
3.NameNode和SecondaryNameNode
3.1 NN和2NN工作机制
思考:NameNode中的元数据是存储在哪里的?
我们先假设一下,如果存储在NameNode节点的磁盘中,因为经常需要进行随机访问,还有响应客户请求,必然是效率过低。因此,元数据需要存放在内存中。但如果只存在内存中,一旦断电,元数据丢失,整个集群就无法工作了。因此产生在磁盘中备份元数据的FsImage。
这样又会带来新的问题,当在内存中的元数据更新时,如果同时更新FsImage,就会导致效率过低,但如果不更新,就会发生一致性问题,一旦NameNode节点断电,就会产生数据丢失。因此,引入Edits文件(只进行追加操作,效率很高)。每当元数据有更新或者添加元数据时,修改内存中的元数据并追加到Edits中。这样,一旦NameNode节点断电,可以通过FsImage和Edits的合并,合成元数据。
但是,如果长时间添加数据到Edits中,会导致该文件数据过大,效率降低,而且一旦断电,恢复元数据需要的时间过长。因此,需要定期进行FsImage和Edits的合并,如果这个操作由NameNode节点完成,又会效率过低。因此,引入一个新的节点SecondaryNamenode,专门用于FsImage和Edits的合并。
1)第一阶段:NameNode启动
(1)第一次启动NameNode格式化后,创建Fsimage和Edits文件。如果不是 第一次启动,直接加载编辑日志和镜像文件到内存。
(2)客户端对元数据进行增删改的请求。
(3)NameNode记录操作日志,更新滚动日志。
(4)NameNode在内存中对元数据进行增删改。
2)第二阶段: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。
4.关于HDFS的客户端与Linux的两种操作方式
HDFS 文件操作有两种方式:一种是命令行方式,Hadoop 提供了一套与 Linux 文件命令类似的命令行工具;另一种是 Java API,即利用 Hadoop 的 Java 库,采用编程的方式操作 HDFS 的文件。
博主将介绍 Linux 操作系统中关于 HDFS 文件操作的常用命令行,并将介绍利用 Hadoop 提供的 Java API 进行基本的文件操作,以及利用 Web 界面查看和管理 HDFS 的方法。
HDFS 常用命令
在 Linux 命令行终端,可以使用命令行工具对 HDFS 进行操作。使用这些命令行可以完成 HDFS 文件的上传、下载和复制,还可以查看文件信息、格式化 NameNode 等。
HDFS 命令行的统一格式如下。
hadoop fs -cmd <args>
其中,cmd是具体的文件操作命令,是一组数目可变的参数。
- 添加文件和目录
HDFS 有一个默认工作目录 /usr/ U S E R , 其 中 , USER,其中, USER,其中,USER是登录用户名,如 root。该目录不能自动创建,需要执行 mkdir 命令创建。
hadoop fs -mkdir /usr/root
使用 Hadoop 的命令put将本地文件 README.txt 上传到 HDFS。
hadoop fs -put README.txt
注意,上面这个命令的最后一个参数是:“.”,这意味着把本地文件上传到默认的工作目录下,该命令等价于以下代码。
hadoop fs -put README.txt /user/root
- 下载文件
下载文件是指从 HDFS 中获取文件,可以使用 Hadoop 的 get 命令。例如,若本地文件没有 README.txt 文件,则需要从 HDFS 中取回,可以执行以下命令。
hadoop fs -get README.txt
或者执行以下命令。
hadoop fs -get README.txt /usr/root/README.txt
- 删除文件
Hadoop 删除文件的命令为rm。例如,要删除从本地文件上传到 HDFS 的 README.txt,可以执行以下命令。
hadoop fs -rm README.txt
- 检索文件
检索文件即查阅 HDFS 中的文件内容,可以使用 Hadoop 中的cat命令。例如,要查阅 README.txt 的内容,可以执行以下命令。
hadoop fs -cat README.txt
另外,Hadoop 的cat命令的输出也可以使用管道传递给 UNIX 命令的 head,可以只显示文件的前一千个字节。
hadoop fs -cat README.txt | head
Hadoop 也支持使用tail命令查看最后一千字节。例如,要查阅 README.txt 最后一千个字节,可以执行如下命令。
hadoop fs -tail README.txt
- 查阅帮助
查阅 HDFS 命令帮助,可以更好地了解和使用 Hadoop 的命令。用户可以执行hadoop fs来获取所用版本 HDFS 的一个完整命令类别,也可以使用help来显示某个具体命令的用法及简短描述。
例如,要了解ls命令,可执行以下命令。
hadoop fs -help ls
HDFS 的 Web 界面
在配置好 Hadoop 集群之后,用户可以通过 Web 界面查看 HDFS 集群的状态,以及访问 HDFS,访问地址如下。
http://[NameNodeIP]:50070
其中,[NameNodeIP]为 HDFS 集群的 NameNode 的 IP 地址。登录后,用户可以查看 HDFS 的信息。
如图 1 所示,通过 HDFS NameNode 的 Web 界面,用户可以查看 HDFS 中各个结点的分布信息,浏览 NameNode 上的存储、登录等日志,以及下载某个 DataNode 上某个文件的内容。
通过 HDFS 的 Web 界面,还可以查看整个集群的磁盘总容量,HDFS 已经使用的存储空间量,非 HDFS 已经使用的存储空间量,HDFS 剩余的存储空间量等信息,以及查看集群中的活动结点数和宕机结点数。
图 2 显示了一个 DataNode 的信息,如磁盘的数量,每块磁盘的使用情况等。通过 Web 界面中的“Utilities”→“Browse the file system”可以查看当前 HDFS 的目录列表,以及每个目录的相关信息,包括访问权限、最后修改日期、文件拥有者、目录大小等。
进一步,用户还可以通过 Web 界面查看文件的信息,如图 3 所示。用户不仅可以查看文件的权限、大小等信息,还可以查看该文件的每个数据块所在的数据结点。
因为每一个文件都是分成好多数据块的,每个数据块又有 3 个副本,这些数据块的副本全部分布存放在多个 DataNode 中,所以用户不可能像传统文件系统那样来访问文件。HDFS Web 界面给用户提供了一个方便、直观地查看 HDFS 文件信息的方法。通过 Web 界面完成的所有操作,都可以通过 Hadoop 提供的命令来实现。
HDFS NameNode的WEB界面
图 1 HDFS NameNode的WEB界面
HDFS DataNode的WEB界面
图 2 HDFS NameNode的WEB界面
HDFS文件界面
图 3 HDFS文件界面
HDFS 的 Java API
HDFS 设计的主要目的是对海量数据进行存储,也就是说在其上能够存储很大量的文件。
HDFS 将这些文件分割之后,存储在不同的 DataNode 上,HDFS 提供了通过Java API 对 HDFS 里面的文件进行操作的功能,数据块在 DataNode 上的存放位置,对于开发者来说是透明的。
使用 Java API 可以完成对 HDFS 的各种操作,如新建文件、删除文件、读取文件内容等。下面将介绍 HDFS 常用的 Java API 及其编程实例。
对 HDFS 中的文件操作主要涉及以下几个类。
名称 | 作用 |
---|---|
org.apache.hadoop.con.Configuration | 该类的对象封装了客户端或者服务器的配置。 |
org.apache.hadoop.fs.FileSystem | 该类的对象是一个文件系统对象,可以用该对象的一些方法来对文件进行操作。 |
org.apache.hadoop.fs.FileStatus | 该类用于向客户端展示系统中文件和目录的元数据,具体包括文件大小、块大小、副本信息、所有者、修改时间等。 |
org.apache.hadoop.fs.FSDatalnputStream | 该类是 HDFS 中的输入流,用于读取 Hadoop 文件。 |
org.apache.hadoop.fs.FSDataOutputStream | 该类是 HDFS 中的输出流,用于写 Hadoop 文件; |
org.apache.hadoop.fs.Path | 该类用于表示 Hadoop 文件系统中的文件或者目录的路径。 |
下面我们通过一个实例来说明如何对文件进行具体操作。
1. 获取文件系统
/**
* 获取文件系统
* @author Tim
*/
public static FileSystem getFileSystem()
// 读取配置文件
Configuration conf = new Configuration();
// 文件系统
FileSystem fs = null;
String hdfsUri = HDFSUri;
if(StringUtils.isBlank(hdfsUri))
// 返回默认文件系统,如果在Hadoop集群下运行,使用此方法可直接获取默认文件系统
try
fs = FileSystem.get(conf);
catch(IOException e)
logger.error("",e);
else
// 根据指定的文件系统,如果在本地测试,需要使用此方法获取文件系统
try
URI uri = new URI(hdfsUri.trim());
fs = FileSystem.get(uri,conf);
catch(URISyntaxException | IOExeption e)
logger.error("",e);
return fs;
2. 创建文件目录
/**
* 创建文件目录
* @author Tim
*/
public static void mkdir(String path)
try
// 获取文件系统
FileSystem fs = getFileSystem();
String hdfsUri = HDFSUri;
if(StringUtils.isNotBlank(hdfsUri))
path = hdfsUri + path;
// 创建目录
fs.mkdirs(new Path(path));
// 释放资源
fs.close();
catch(IllegalArgumentException | IOException e)
logger.error("",e);
3. 删除文件或者文件目录
/**
* 删除文件或者文件目录
* @author Tim
*/
public static void rmdir(String path)
try
// 返回 FileSystem 对象
FileSystem fs = getFileSystem();
String hdfsUri = HDFSUri;
if(StringUtils.isNotBlank(hdfsUri))
path = hdfsUri + path;
// 删除文件或者文件目录
fs.delete(new Path(path),true);
// 释放资源
fs.close();
catch(IllegalArgumentException | IOException e)
logger.error("",e);
4. 将文件上传至 HDFS
/**
* 将文件上传至 HDFS
* @author Tim
*/
public static void copyFileToHDFS(boolean delSrc,boolean overwrite,String srcFile,String destPath)
// 源文件路径是 Linux 下的路径
Path srcPath = new Path(srcFile);
// 目的路径
String hdfsUri = HDFSUri;
if(StringUtils.isNotBlank(hdfsUri))
destPath = hdfsUri + destPath;
Path dstPath = new Path(destPath);
// 实现文件上传
try
// 获取 FileSystem 对象
FileSystem fs = getFileSystem();
fs.copyFromLocalFile(srcPath,dstPath);
fs.copyFromLocalFile(delSrc,overwrite,srcPath,dstPath);
// 释放资源
fs.close();
catch(IOException e)
logger.error("",e);
5. 从 HDFS 下载文件
/**
* 从 HDFS 下载文件
* @author Tim
*
*/
public static void getFile(String srcFile,String destPath)
// 源文件路径
String hdfsUri = HDFSUri;
if(StringUtils.isNotBlank(hdfsUri))
srcFile = hdfsUri + srcFile;
Path srcPath = new Path(srcFile);
// 目的路径是 Linux 下的路径
Path dstPath = new Path(destPath);
try
// 获取FileSystem对象
FileSystem fs = getFileSystem();
// 下载HDFS上的文件
fs.close();
catch(IOException e)
logger.error("",e);
PS:
关于概念类的博主就整理这么多辣,有建议可以私信或者评论哦,欢迎佬们支持。
以上是关于Hadoop生态之HDFS的主要内容,如果未能解决你的问题,请参考以下文章