HDFS(Hadoop Distributed File System)是一个文件系统,用于存储文件,通过目录树来定位文件;随着数据量激增,单个操作系统无法对海量数据进行存储,因此将数据分散到多个系统中,而为了方便管理和维护,迫切需要一种系统来管理多台机器上的文件,这就是分布式文件管理系统,HDFS就是其中的一种。
它具有以下优点
相应的也有缺点:
HDFS中的文件在物理上是分块处理的,块(Block)的大小可以通过参数dfs.blocksize来规定,其默认值为128M,当文件大小超过128M后会被切分为多个Block。块大小规定的是数据存储的最大值而不是实际值,例如1KB文件在块中实际空间就是1KB而不是128M。
文件块大小的选取和硬盘的传输速度有关,研究表明寻址时间最好为传输时间的1%,若寻址时间为10ms,那么传输时间最好为1s,当前普通机械硬盘的传输速率为100MB/s,因此选取块大小为较为接近的128M;若使用固态硬盘传输速率大约为200MB/s,那么Block大小可以设为256M。因此文件块不能设置得太大。另一方面,如果文件块设置得过小,那么文件数量就会变多,寻址时间就会变大,也不利于数据读取。
如下所示为客户端向HDFS读取文件内容的过程
节点距离
在HDFS写数据的过程中,NameNode会选择距离待上传数据最近距离的DataNode接收数据。而节点距离是指两个节点到达最近的共同祖先的距离总和。
节点选择
如下所示,不同的数据节点n-0、n-1、n-2放在一个服务器机架r1上,多个机架一起构成了一个大的集群
如下所示为从HDFS中读取数据的流程
为了对数据的随机访问作出快速响应,NameNode将元数据信息加载到内存中,但是为了防止内存中的数据断电丢失,HDFS会在磁盘中保存一份元数据备份文件FsImage 。在NameNode格式化时会创建FsImage,之后每次启动会将其中的内容加载到内存。
在Hadoop的data目录下可以看到fsimage文件,但是镜像文件无法直接打开,可以通过oiv(offline image viewer)来转换成可以查看的格式,其语法为
# hdfs oiv -p 文件类型 -i 镜像文件 -o 转换后文件输出路径
hdfs oiv -p XML -i fsimage_0000000000000000025 -o ./fsimage.xml
可以看到fsimage.xml文件中保存着namenode元数据信息
<inode>
<id>16386id>
<type>DIRECTORYtype>
<name>username>
<mtime>1512722284477mtime>
<permission>atguigu:supergroup:rwxr-xr-xpermission>
<nsquota>-1nsquota>
<dsquota>-1dsquota>
inode>
为了保证内存中的元数据和FsImage中的一致性,通过Edits文件以追加操作的方式记录内存中元数据的变化,每当元数据有更新或者添加元数据时,修改内存中的元数据并追加到Edits中。这样,一旦NameNode节点断电,FsImage可以根据Edits中的记录进行更新,从而保证数据的一致性。
同理可以对Edits文件进行查看
# -p 文件类型 -i编辑日志 -o 转换后文件输出路径
hdfs oev -p XML -i edits_0000000000000000012-0000000000000000013 -o ./edits.xml
查看edits.xml,其中记录了对于数据的更改操作
<RECORD>
<OPCODE>OP_ADDOPCODE>
<DATA>
<TXID>130TXID>
<LENGTH>0LENGTH>
<INODEID>16407INODEID>
<PATH>/hello7.txtPATH>
<REPLICATION>2REPLICATION>
<MTIME>1512943607866MTIME>
<ATIME>1512943607866ATIME>
<BLOCKSIZE>134217728BLOCKSIZE>
<CLIENT_NAME>DFSClient_NONMAPREDUCE_-1544295051_1CLIENT_NAME>
<CLIENT_MACHINE>192.168.10.102CLIENT_MACHINE>
<OVERWRITE>trueOVERWRITE>
<PERMISSION_STATUS>
<USERNAME>atguiguUSERNAME>
<GROUPNAME>supergroupGROUPNAME>
<MODE>420MODE>
PERMISSION_STATUS>
<RPC_CLIENTID>908eafd4-9aec-4288-96f1-e8011d181561RPC_CLIENTID>
<RPC_CALLID>0RPC_CALLID>
DATA>
RECORD>
NameNode数据更新过程如下:
Edits文件会随着时间逐渐增大,因此需要定期更新FsImage并清空Edits,更新操作由SecondaryNameNode辅助完成,其过程如下:
CheckPoint的更新有定期和存满了两种情况,默认每隔一小时SecondaryNameNode会执行一次清理合并。另一方面,2nn默认如果操作次数满了一百万次也会触发清理,同时每一分钟会检查一次是否够了一百万次。
<property>
<name>dfs.namenode.checkpoint.periodname>
<value>3600svalue>
property>
<property>
<name>dfs.namenode.checkpoint.txnsname>
<value>1000000value>
<description>操作动作次数description>
property>
<property>
<name>dfs.namenode.checkpoint.check.periodname>
<value>60svalue>
<description> 1分钟检查一次操作次数description>
property>
数据块在DataNode上以文件形式存储在磁盘上,包括两个文件,一个是数据本身和元数据.meta文件,其中包括数据块的长度,块数据的校验和,以及时间戳等信息。
DataNode启动后向NameNode注册,之后会周期性(6小时)地向NameNode上报所有的块信息。DataNode会每3秒一次进行心跳反应,心跳返回结果带有NameNode给该DataNode的命令,如复制块数据或删除某个数据块。如果超过10分钟没有收到某个DataNode的心跳,则认为该节点不可用。
通过hdfs的命令行可以对文件进行简单的增删改查等操作
上传下载
# 在hdfs上创建文件夹
hadoop fs -mkdir /document
# 将本地文件word.txt剪切到hdfs的/document目录下
hadoop fs -moveFromLocal ./word.txt /document
# 将本地文件word.txt复制到hdfs的/document目录下,put较为常用
hadoop fs -put ./word.txt /document
hadoop fs -copyFromLocal ./word.txt /document
# 将hdfs上的文件下载到本地
hadoop fs -get /document/word.txt ./localword.txt
hadoop fs -copyToLocal /document/word.txt ./localword.txt
# 在两个hdfs目录中复制文件
hadoop fs -cp /document/word.txt /newdoc
# 在两个hdfs目录中移动文件
hadoop fs -mv /document/word.txt /newdoc
删除
# 递归删除hdfs中document目录下的文件
hadoop fs -rm -r /document
修改
# 将本地的new.txt中的内容追加到word.txt末尾
hadoop fs -appendToFile new.txt /document/word.txt
# 修改script文件的执行权限,同Linux中的命令一样,类似的还有修改文件所属组chgrp、修改文件所属用户chown
hadoop fs -chmod 666 /document/script.sh
# 设置word.txt文件副本的数量为3
hadoop fs -setrep 3 /document/word.txt
查看
# 查看hdfs上document目录下的文件
hadoop fs -ls /document
# 统计document文件夹下的文件信息,其中52代表word.txt的文件大小,156=52×3表示它有三个副本
hadoop fs -du /document
52 156 /document/word.txt
# 查看word.txt文件内容
hadoop fs -cat /document/word.txt
# 查看word.txt文件末尾1kB的内容
hadoop fs -tail /document/word.txt
如果在Windows环境下和Hadoop集群进行交互,还需要配置Windows的环境,首先需要下载相关的工具包winutils:https://github.com/cdarlint/winutils,选择相应的版本解压,然后配置到Windows系统环境变量HADOOP_HOME中
双击依赖包中的winutils.ext,如无报错代表环境变量配置正常。
在IDEA中创建一个Maven工程HdfsClientDemo,并导入相应的依赖
<dependencies>
<dependency>
<groupId>org.apache.hadoopgroupId>
<artifactId>hadoop-clientartifactId>
<version>3.1.3version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.7.30version>
dependency>
dependencies>
在src/main/resources目录下,创建log4j的配置文件log4j.properties
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
如下所示创建HdfsClient类用于操作Hadoop集群,使用方式大致分为三步,第一步通过FileSystem.get()获取文件系统fs,其中可以传入集群地址,集群配置和登录用户作为参数;第二部通过fs对象的方法完成相关操作;第三步关闭资源。
如下所示,首先使用mkdirs()
方法在hdfs系统上创建目录,然后使用copyFromLocalFile()
方法将本地的example.py上传到了hdfs的/code目录,并且通过configuration.set()
设置副本数为2
package hdfs;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.junit.Test;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
public class HdfsClient {
@Test
public void makeDirectory() throws URISyntaxException, IOException, InterruptedException {
// 1 打开文件系统
URI uri = new URI("hdfs://hadoop102:8020");
Configuration configuration = new Configuration();
configuration.set("dfs.replication", "2");
FileSystem fs = FileSystem.get(uri, configuration, "tory");
// 2 上传文件
fs.mkdirs(new Path("/code"));
fs.copyFromLocalFile(new Path("d:/temp/example.py"), new Path("/code"));
// 3 关闭资源
fs.close();
}
}
文件下载
通过如下方法从HDFS下载文件到本地,HDFS使用循环冗余校验码来保证数据正确性,如果开启文件校验,会另外下载.CRC校验文件
/**
* 从HDFS下载文件到本地
* @param boolean delSrc 是否删除原文件,可选项,默认为false
* @param Path src 源文件路径
* @param Path src 目标文件路径
* @param boolean useRawLocalFileSystem 是否开启文件校验,可选项,默认为true表示不开启
*/
copyToLocalFile(false, new Path("/code/example.py"), new Path("d:/code.test.py"), true);
文件移动
在HDFS上进行文件移动和名称修改都可以通过rename()函数实现,第一个参数为原文件路径,第二个为新文件路径
fs.rename(new Path("/tmp/example.java"), new Path("/code/example.java"));
文件删除
通过delete()方法可以对文件进行删除,第一个为要删除的文件路径,第二个参数为是否级联删除,当删除某个不为空的文件夹是需要将其设为true
fs.delete(new Path("/tmp"), true);
除了在代码中通过Configuration对象对Hadoop集群进行设置外,还可以通过配置文件对集群进行设置,例如在resource目录下新建hdfs-site.xml,在其中设置文件副本为1份,但上述代码执行结果依旧为2份副本,这是由于参数设置存在优先级:
代码中的设置 > resource配置文件 > Hadoop自定义配置xxx-site.xml > Hadoop默认文件xxx-default.xml
因此文件副本数是2,而不是resource中的1和默认文件的3
<configuration>
<property>
<name>dfs.replicationname>
<value>1value>
property>
configuration>
如下所示,通过listFiles()
方法获取根目录下的所有文件夹,第二个参数true代表递归遍历。然后通过迭代器依次对其进行遍历,并且通过fileStatus对象获得具体的文件信息
//获取文件列表
RemoteIterator<LocatedFileStatus> listFiles = fs.listFiles(new Path("/"), true);
//逐个遍历文件
while (listFiles.hasNext()) {
LocatedFileStatus fileStatus = listFiles.next();
if (fileStatus.isFile()) {
System.out.println("这是一个文件");
}else {
System.out.println("这是一个文件夹");
}
System.out.println("文件路径" + fileStatus.getPath());
System.out.println("执行权限:" + fileStatus.getPermission());
System.out.println("所属者:" + fileStatus.getOwner());
System.out.println("所属组:" + fileStatus.getGroup());
System.out.println("文件大小" + fileStatus.getLen());
System.out.println("修改时间:" + fileStatus.getModificationTime());
System.out.println("副本数:" + fileStatus.getReplication());
System.out.println("块大小" + fileStatus.getBlockSize());
System.out.println("文件名" + fileStatus.getPath().getName());
BlockLocation[] blockLocations = fileStatus.getBlockLocations();
System.out.println("文件副本块位置:" + Arrays.toString(blockLocations));
}