📖 前言:Hadoop的核心是HDFS(Hadoop Distributed File System,Hadoop分布式文件系统)和MapReduce。其中,HDFS是解决海量大数据文件存储的问题,是目前应用最广泛的分布式文件系统。
目录
- 🕒 1. HDFS简介
- 🕘 1.1 HDFS的演变
- 🕘 1.2 HDFS的优缺点
- 🕒 2. HDFS的架构和原理
- 🕘 2.1 HDFS存储架构
- 🕘 2.2 HDFS文件读写原理
- 🕤 2.2.1 HDFS写文件流程
- 🕤 2.2.2 HDFS读文件流程
- 🕒 3. HDFS的Shell操作
- 🕒 4. 使用Eclipse开发调试HDFS Java程序
- 🕘 4.1 安装Eclipse
- 🕘 4.2 在Eclipse中创建项目
- 🕘 4.3 为项目添加需要用到的JAR包
- 🕘 4.4 编写Java应用程序
- 🕘 4.5 编译运行程序
- 🕘 4.6 应用程序的部署
🕒 1. HDFS简介
🕘 1.1 HDFS的演变
- 传统文件系统会遇到存储瓶颈问题和文件上传与下载的效率问题,对于存储,首先考虑的就是扩容,扩容有两种形式,一种是纵向扩容,即增加磁盘和内存;另一种是横向扩容,即增加服务器数量。通过扩大规模达到分布式存储,这种存储形式就是分布式文件存储的雏形。对于上传与下载问题,常规的解决办法是将一个大的文件切分成多个数据块,将数据块以并行的方式进行存储。这里以30G的文本文件为例,将其切分成3块,每块大小10G。
- 但是文件通过数据块分别存储在服务器集群中,那么如何获取一个完整的文件呢?针对这个问题,就需要再考虑增加一台服务器,专门用来记录文件被切割后的数据块信息以及数据块的存储位置信息,即HDFS文件系统雏形。
- 如果存储数据块的服务器中突然有一台机器宕机,就无法正常的获取文件了,这个问题被称为单点故障。针对这个问题,可以采用备份的机制解决,即每个服务器中都存储两个数据块,进行备份。服务器B存储 blk-001 和 blk-002,服务器C存储 blk-002 和 blk-003,服务器D存储 blk-001 和 blk-003。当服务器C突然宕机,我们还可以通过服务器B和服务器D查询完整的数据块供客户端访问下载。这就形成了简单的HDFS。
🕘 1.2 HDFS的优缺点
优点:高容错、流式数据访问、支持超大文件、高数据吞吐量
缺点:高延迟、不适合小文件存取、不适合并发写入
🕒 2. HDFS的架构和原理
🕘 2.1 HDFS存储架构
HDFS是一个易于扩展的分布式文件系统,运行在成百上千台低成本的机器上。它与现有的分布式文件系统有许多相似之处,都是用来存储数据的系统工具,而区别于HDFS具有高度容错能力,旨在部署在低成本机器上。HDFS主要用于对海量文件信息进行存储和管理,也就是解决大数据文件(如TB乃至PB级)的存储问题。
- HDFS采用主从架构(Master/Slave架构)。
- HDFS集群是由一个NameNode和多个 DataNode组成。
- HDFS提供 SecondaryNameNode 辅助 NameNode。
-
NameNode
(名称节点)
NameNode是HDFS集群的主服务器,通常称为名称节点或者主节点。一旦NameNode关闭,就无法访问Hadoop集群。NameNode主要以元数据的形式进行管理和存储,用于维护文件系统名称并管理客户端对文件的访问;NameNode记录对文件系统名称空间或其属性的任何更改操作;HDFS负责整个数据集群的管理,并且在配置文件中可以设置备份数量,这些信息都由NameNode存储。 -
DataNode
(数据节点)
DataNode是HDFS集群中的从服务器,通常称为数据节点。文件系统存储文件的方式是将文件切分成多个数据块,这些数据块实际上是存储在DataNode节点中的,因此DataNode机器需要配置大量磁盘空间。它与NameNode通过心跳监测机制保持不断的通信,DataNode在客户端或者NameNode的调度下,存储并检索数据块,对数据块进行创建、删除等操作,并且定期向NameNode发送所存储的数据块列表。
心跳机制:HDFS在运行期间,将NameNode和每个DataNode之间建立一种心跳机制,NameNode在固定间隔时间内接收到DataNode发送的心跳信息,认为该DataNode处于存活状态,反之则认为该DataNode处于假死状态。在最大间隔时间内仍然没有接收到处于假死状态的DataNode发送的心跳信息,NameNode会认为该DataNode处于宕机状态。当某个DataNode处于宕机状态之后,可能会造成某些Block的副本数无法达到系统要求。
SecondaryNameNode
(辅助节点)
SecondaryNameNode是HDFS集群中的辅助节点。定期从NameNode拷贝FsImage文件并合并Edits文件,将合并结果发送给NameNode。SecondaryNameNode和NameNode保存的FsImage和Edits文件相同,可以作为NameNode的冷备份,它的目的是帮助 NameNode合并编辑日志,减少NameNode启动时间。当NameNode宕机无法使用时,可以通过手动操作将SecondaryNameNode切换为NameNode。
注:SecondaryNameNode并不是NameNode的备份,它的主要作用是协助NameNode处理元数据的备份和检查点操作。它并不能解决Hadoop集群的单点故障问题。因为SecondaryNameNode的备份数据并不能时刻保持与namenode同步,在每次 checkpoint 的时候SecondaryNameNode 才会合并与同步NameNode的数据,也就是说在NameNode宕机的时候SecondaryNameNode可能会丢失一段时间的数据,这段时间取决于checkpoint的周期。我们可以减小checkpoint的周期来减少数据的丢失量,但由于每次checkpoint很耗性能,而且这种方案也不能从根本上解决数据丢失的问题。故对于单点故障问题,采用即时备份NameNode元数据的方案(NFS)更为合适。
-
Block
(数据块)
每个磁盘都有默认的数据块大小,这是磁盘进行数据读/写的最小单位,HDFS同样也有块的概念,它是抽象的块,而非整个文件作为存储单元,在Hadoop3.x版本下,默认大小是128M,且备份3份,每个块尽可能地存储于不同的DataNode中。按块存储的好处主要是屏蔽了文件的大小,提供数据的容错性和可用性。 -
Rack
(机架)
Rack是用来存放部署Hadoop集群服务器的机架,不同机架之间的节点通过交换机通信,HDFS通过机架感知策略,使NameNode能够确定每个DataNode所属的机架ID,使用副本存放策略,来改进数据的可靠性、可用性和网络带宽的利用率。 -
Metadata
(元数据)
在 NameNode 内部是以元数据的形式,维护着两个文件,分别是FsImage
镜像文件和EditLog
日志文件。其中,FsImage镜像文件用于存储整个文件系统命名空间的信息,EditLog日志文件用于持久化记录文件系统元数据发生的变化。
当 NameNode启动的时候,FsImage 镜像文件就会被加载到内存中,然后对内存里的数据执行记录的操作,以确保内存所保留的数据处于最新的状态,这样就加快了元数据的读取和更新操作,但是这些操作非常消耗NameNode资源。于是HDFS文件系统引入了 EditLog 日志文件,该文件以追加方式记录内存中元数据的每一次变化,如果NameNode宕机,可以通过合并FsImage文件和Edits文件的方式恢复内存中存储的元数据。
注:EditLog和Edits是同一个东西。Edits是EditLog的文件名,它存储了EditLog的内容。
🕘 2.2 HDFS文件读写原理
🕤 2.2.1 HDFS写文件流程
以300MB大小的1.txt文件为例,介绍HDFS写文件流程
-
客户端发起上传1.txt文件到指定目录的请求,通过RPC(远程过程调用)与NameNode建立通讯。
-
NameNode检查元数据文件的系统目录树,即检查客户端是否有上传文件的权限,以及文件是否存在等。若系统目录树的父目录不存在该文件相关信息,返回客户端可以上传文件。
-
客户端根据分块策略对文件1.txt进行切分,形成3个Block,分别是blk1、blk2和blk3。
-
客户端向NameNode请求上传第一个Block,即blk1,以及数据块副本的数量。
-
NameNode根据副本机制和机架感知向客户端返回可上传blk1的DataNode列表。
-
客户端从NameNode接收到blk1上传的DataNode列表,并与虚拟机建立管道(Pipeline)。
-
Hadoop3向Hadoop2汇报管道建立成功,Hadoop2与Hadoop1汇报管道建立成功;Hadoop1与客户端汇报管道建立成功,客户端与所有DataNode列表中的所有DataNode都建立了管道。
-
客户端开始传输blk1,传输过程是以流式写入的方式实现。
1)将blk1写入到内存中进行缓存。
2)将blk1按照packet(默认为64K)为单位进行划分。
3)将第一个packet通过管道发送给Hadoop1。
4)Hadoop1接收完第一个packet之后,客户端会将第二个packet发送给Hadoop1,同时Hadoop1通过Pipeline将第一个packet发送给Hadoop2。
5)Hadoop2接收完第一个packet之后,Hadoop1会将第二个packet发送给Hadoop2,同时Hadoop2通过Pipeline将第一个packet发送给Hadoop3。
6)依次类推直至blk1上传完成。 -
Hadoop3向Hadoop2发送blk1写入完成的信息,Hadoop2向Hadoop1发送blk1写入完成的信息,最后,Hadoop1向客户端发送blk1写入完成的信息。
注意:客户端成功上传blk1后,重复第4~9步的流程,依次上传blk2和blk3,最终完成1.txt文件的上传。
练习题:客户端上传文件的时候哪项是正确的?(多选)
A、数据经过 NameNode 传递给 DataNode
B、客户端端将文件切分为多个Block,依次上传
C、客户端只上传数据到一台 DataNode,然后由 NameNode 负责 Block 复制工作
D、客户端发起文件上传请求,通过RPC与NameNode建立通讯。
答案:BD
🕤 2.2.2 HDFS读文件流程
以300MB大小的1.txt文件为例,介绍HDFS读文件流程
-
客户端发起读取1.txt文件的请求,通过RPC与NameNode建立通讯。
-
NameNode检查元数据文件的系统目录树,即检查客户端是否有读取文件的权限,以及文件是否存在等。
-
客户端按照就近原则从NameNode返回的Block列表读取Block。
-
客户端将读取所有的Block按照顺序进行合并,最终形成1.txt文件,需要注意的是,如果文件过大导致NameNode无法一次性文件的所有Block列表返回客户端时,会分批次将Block列表返回客户端。
🕒 3. HDFS的Shell操作
HDFS Shell类似于Linux操作系统中的Shell,都是一种命令语言,可以完成对HDFS上文件和目录的一系列操作。
在HDFS集群日常使用的过程时,主要是通过Client Commands类型的HDFS Shell子命令操作HDFS,Hadoop提供了多种Client Commands类型的HDFS Shell子命令,包括dfs、envvars、classpath等,dfs
主要用于操作HDFS的文件和目录,也是最常用的HDFS Shell子命令。
-ls
:查看指定路径的目录结构-du
:统计目录下所有文件大小-mv
:移动文件-cp
:复制文件-rm
:删除文件/空白文件夹-cat
:查看文件内容-text
:源文件输出为文本格式-mkdir
:创建空白文件夹-put
:上传文件-help
:删除文件/空白文件夹
🕒 4. 使用Eclipse开发调试HDFS Java程序
Hadoop采用Java语言开发的,提供了Java API与HDFS进行交互。上面介绍的Shell命令,在执行时实际上会被系统转换成Java API调用。
为了提高程序编写和调试效率,我们采用Eclipse工具编写Java程序。
现在要执行的任务是:假设在目录“hdfs://localhost:9000/user/hadoop
”下面有几个文件,分别是file1.txt、file2.txt、file3.txt、file4.abc和file5.abc,这里需要从该目录中过滤出所有后缀名不为“.abc”的文件,对过滤之后的文件进行读取,并将这些文件的内容合并到文件“hdfs://localhost:9000/user/hadoop/merge.txt
”中。
🕘 4.1 安装Eclipse
在Ubuntu中打开Firefox直接下载
🔎 Eclipse下载官网
点击 “Download Packages”
选择对应的操作系统下载
注意选择国内镜像,下载速度才快
下载完成
在“Downloads”文件夹中空白处右键,选择“在终端打开”,输入命令,解压eclipse到/opt目录下:
sudo tar -zxvf eclipse-committers-2023-12-R-linux-gtk-x86_64.tar.gz -C /opt
在Linux系统中设置Eclipse快捷方式
sudo vim /usr/share/applications/eclipse.desktop
向eclipse.desktop中添加以下内容:
[Desktop Entry]
Encoding=UTF-8
Name=Eclipse
Comment=Eclipse IDE
Exec=/opt/eclipse/eclipse
Icon=/opt/eclipse/icon.xpm
Terminal=false
StartupNotify=true
Type=Application
Categories=Application;Developmet;
给eclipse.desktop赋权
cd /usr/share/applications
sudo chmod u+x eclipse.desktop
找到/usr/share/applications/eclipse.desktop,鼠标右键选择复制,到桌面粘贴即可。
注:高版本Ubuntu会有权限检查,如报错需要右键图标,选择“允许运行”
之后双击快捷方式打开即可。
🕘 4.2 在Eclipse中创建项目
启动Eclipse。当Eclipse启动以后,会弹出如下图所示界面,提示设置工作空间(workspace)。
可以直接采用默认的设置“/home/hadoop/workspace”,点击“OK”按钮。可以看出,由于当前是采用hadoop用户登录了Linux系统,因此,默认的工作空间目录位于hadoop用户目录“/home/hadoop”下。
Eclipse启动以后,会呈现如下图所示的界面。
关闭welcome界面后,选择“File–>New–>Java Project”菜单,开始创建一个Java工程,会弹出如下图所示界面。
在“Project name”后面输入工程名称“HDFSExample”,选中“Use default location”,让这个Java工程的所有文件都保存到“/home/hadoop/workspace/HDFSExample”目录下。在“JRE”这个选项卡中,可以选择当前的Linux系统中已经安装好的JDK,比如jdk1.8.0_371。然后,点击界面底部的“Next>”按钮,进入下一步的设置。
🕘 4.3 为项目添加需要用到的JAR包
进入下一步的设置以后,会弹出如下图所示界面。
需要在这个界面中加载该Java工程所需要用到的JAR包,这些JAR包中包含了可以访问HDFS的Java API。这些JAR包都位于Linux系统的Hadoop安装目录下,对于本文而言,就是在“/usr/local/hadoop/share/hadoop”目录下。点击界面中的“Libraries”选项卡,然后,点击界面右侧的“Add External JARs…”按钮,会弹出如下图所示界面。
为了编写一个能够与HDFS交互的Java应用程序,一般需要向Java工程中添加以下JAR包:
(1)“/usr/local/hadoop/share/hadoop/common”目录下的所有JAR包,包括hadoop-common-3.3.5.jar、hadoop-common-3.3.5-tests.jar、haoop-nfs-3.3.5.jar、haoop-kms-3.3.5.jar和hadoop-registry-3.3.5.jar,注意,不包括目录jdiff、lib、sources和webapps;
(2)“/usr/local/hadoop/share/hadoop/common/lib”目录下的所有JAR包;
(3)“/usr/local/hadoop/share/hadoop/hdfs”目录下的所有JAR包,注意,不包括目录jdiff、lib、sources和webapps;
(4)“/usr/local/hadoop/share/hadoop/hdfs/lib”目录下的所有JAR包。
全部添加完毕以后,就可以点击界面右下角的“Finish”按钮,完成Java工程HDFSExample的创建。
🕘 4.4 编写Java应用程序
下面编写一个Java应用程序。
请在Eclipse工作界面左侧的“Package Explorer”面板中(如下图所示),找到刚才创建好的工程名称“HDFSExample”,然后在该工程名称上点击鼠标右键,在弹出的菜单中选择“New–>Class”菜单。
选择“New–>Class”菜单以后会出现如下图所示界面。
在该界面中,只需要在“Name”后面输入新建的Java类文件的名称,这里采用名称“MergeFile”,其他都可以采用默认设置,然后,点击界面右下角“Finish”按钮,Eclipse就自动创建了一个名为“MergeFile.java”的源代码文件,请在该文件中输入以下代码:
import java.io.IOException;
import java.io.PrintStream;
import java.net.URI;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*;
/**
* 过滤掉文件名满足特定条件的文件
*/
class MyPathFilter implements PathFilter {
String reg = null;
MyPathFilter(String reg) {
this.reg = reg;
}
public boolean accept(Path path) {
if (!(path.toString().matches(reg)))
return true;
return false;
}
}
/***
* 利用FSDataOutputStream和FSDataInputStream合并HDFS中的文件
*/
public class MergeFile {
Path inputPath = null; //待合并的文件所在的目录的路径
Path outputPath = null; //输出文件的路径
public MergeFile(String input, String output) {
this.inputPath = new Path(input);
this.outputPath = new Path(output);
}
public void doMerge() throws IOException {
Configuration conf = new Configuration();
conf.set("fs.defaultFS","hdfs://localhost:9000");
conf.set("fs.hdfs.impl","org.apache.hadoop.hdfs.DistributedFileSystem");
FileSystem fsSource = FileSystem.get(URI.create(inputPath.toString()), conf);
FileSystem fsDst = FileSystem.get(URI.create(outputPath.toString()), conf);
//下面过滤掉输入目录中后缀为.abc的文件
FileStatus[] sourceStatus = fsSource.listStatus(inputPath,
new MyPathFilter(".*\\.abc"));
FSDataOutputStream fsdos = fsDst.create(outputPath);
PrintStream ps = new PrintStream(System.out);
//下面分别读取过滤之后的每个文件的内容,并输出到同一个文件中
for (FileStatus sta : sourceStatus) {
//下面打印后缀不为.abc的文件的路径、文件大小
System.out.print("路径:" + sta.getPath() + " 文件大小:" + sta.getLen()
+ " 权限:" + sta.getPermission() + " 内容:");
FSDataInputStream fsdis = fsSource.open(sta.getPath());
byte[] data = new byte[1024];
int read = -1;
while ((read = fsdis.read(data)) > 0) {
ps.write(data, 0, read);
fsdos.write(data, 0, read);
}
fsdis.close();
}
ps.close();
fsdos.close();
}
public static void main(String[] args) throws IOException {
MergeFile merge = new MergeFile(
"hdfs://localhost:9000/user/hadoop/",
"hdfs://localhost:9000/user/hadoop/merge.txt");
merge.doMerge();
}
}
🕘 4.5 编译运行程序
在开始编译运行程序之前,请一定确保Hadoop已经启动运行,如果还没有启动,需要打开一个Linux终端,输入以下命令启动Hadoop,并检查是否都正常启动:
cd /usr/local/hadoop
./sbin/start-dfs.sh
然后,要确保HDFS的“/user/hadoop”目录下已经存在file1.txt、file2.txt、file3.txt、file4.abc和file5.abc,每个文件里面有内容。这里,假设文件内容如下:
file1.txt的内容是:this is file1.txt
file2.txt的内容是:this is file2.txt
file3.txt的内容是:this is file3.txt
file4.abc的内容是:this is file4.abc
file5.abc的内容是:this is file5.abc
我们可以在Windows端创建好文件,之后通过Win端的浏览器上传
浏览器输入: http://[虚拟机IP]:9870/
先创建好目录,再上传文件
注:如果遇到如下报错,需要修改权限
在终端输入:./bin/hdfs dfs -chmod -R 777 /
现在就可以编译运行上面编写的代码。可以直接点击Eclipse工作界面上部的运行程序的快捷按钮,当把鼠标移动到该按钮上时,在弹出的菜单中选择“Run As”,继续在弹出来的菜单中选择“Java Application”,如下图所示。
然后,会弹出如下图所示界面。
在该界面中,点击界面右下角的“OK”按钮,开始运行程序。程序运行结束后,会在底部的“Console”面板中显示运行结果信息(如下图所示)。同时,“Console”面板中还会显示一些类似“log4j:WARN…”的警告信息,可以不用理会。
如果程序运行成功,这时,可以到HDFS中查看生成的merge.txt文件
在终端查看:
在Web查看:
🕘 4.6 应用程序的部署
下面介绍如何把Java应用程序生成JAR包,部署到Hadoop平台上运行。首先,在Hadoop安装目录下新建一个名称为myapp的目录,用来存放我们自己编写的Hadoop应用程序,可以在Linux的终端中执行如下命令:
cd /usr/local/hadoop
mkdir myapp
然后,在Eclipse工作界面左侧的“Package Explorer”面板中,在工程名称“HDFSExample”上点击鼠标右键,在弹出的菜单中选择“Export”,如下图所示。
在弹出的界面中,选择“Runnable JAR file”,然后,点击“Next>”按钮,弹出如下图所示界面。
在该界面中,“Launch configuration”用于设置生成的JAR包被部署启动时运行的主类,需要在下拉列表中选择刚才配置的类“MergeFile-HDFSExample”。在“Export destination”中需要设置JAR包要输出保存到哪个目录,比如,这里设置为“/usr/local/hadoop/myapp/HDFSExample.jar”。在“Library handling”下面选择“Extract required libraries into generated JAR”。然后,点击“Finish”按钮,之后会出现一些警告信息,点击OK无视即可。
至此,已经顺利把HDFSExample工程打包生成了HDFSExample.jar。可以到Linux系统中查看一下生成的HDFSExample.jar文件,可以在Linux的终端中执行如下命令:
可以看到,“/usr/local/hadoop/myapp”目录下已经存在一个HDFSExample.jar文件。
由于之前已经运行过一次程序,已经生成了merge.txt,因此,需要首先执行如下命令删除该文件:
cd /usr/local/hadoop
./bin/hdfs dfs -rm /user/hadoop/merge.txt
现在,就可以在Linux系统中,使用hadoop jar命令运行程序,并到HDFS中查看生成的merge.txt文件:
cd /usr/local/hadoop
./bin/hadoop jar ./myapp/HDFSExample.jar
./bin/hdfs dfs -cat /user/hadoop/merge.txt
注:如果遇到如下图报错
说明是由较高版本的JDK编译的java class文件试图在较低版本的JVM上运行产生的错误,可以通过下述方法进行解决:
1、检查jvm(java命令)和jdk(javac命令)版本是否一致。如果是linux版本,则在命令行中分别输入java -version和javac -version命令来查看版本是否一致。
2、如果都一致,但还是解决不了问题,那么肯定不是直接在命令行中用javac来编译的,而是用类似于eclipse、netbeans这样的编译器来编译的。因为很多编译器都自带javac,而不是采用操作系统中的编译器。
3、如果你的编译器是eclipse的话,那么需要在项目的属性里设置jdk版本,方法是右击项目–>properties–>java compiler --> Enable project specific settings -->将compiler compliance level设置为1.8,即可解决问题。
OK,以上就是本期知识点“HDFS分布式文件系统”的知识啦~~ ,感谢友友们的阅读。后续还会继续更新,欢迎持续关注哟📌~
💫如果有错误❌,欢迎批评指正呀👀~让我们一起相互进步🚀
🎉如果觉得收获满满,可以点点赞👍支持一下哟~
❗ 转载请注明出处
作者:HinsCoder
博客链接:🔎 作者博客主页