hadoop之HDFS简介

一、HDFS概述和特点

1、HDFS概述

HDFS(Hadoop Distributed File System,Hadoop分布式文件系统),它是一个文件系统,用于存储文件,通过目录树来定位文件;其次,它是分布式的,由很多服务器联合起来实现其功能,集群中的服务器有各自的角色。

2、HDFS优缺点

优点:

HDFS的使用场景:适合一次写入,多次读取的场景。
(1)高容错性,自动保存多个副本,即使多个副本不可用,仍可以通过可用副本恢复;
(2)适合处理大数据,适合处理GB,TB甚至PB级别的数据;
(3)成本可控,可构建在廉价的机器上,通过多副本确保可靠性。

缺点:

HDFS修改文件的效率很差,但可以在文件后面追加写入。
(1)不适合低延时数据访问,比如毫秒级的存储数据,是做不到的;
(2)无法高效的对大量小文件进行存储,因为NameNode的内存总是有限的,每个文件的信息需要占用NameNode的150字节的内存,128G内存大概能存9亿个文件的信息(128x1024x1024x1024/150);
(3)不支持并发写入,只能支持一个写,不允许多个线程同时写,不支持文件随机修改;
(4)仅支持数据的追加,不支持数据的修改,适合一次写入,多次读取的操作;

HDFS块(Block)大小:

HDFS块太小时,会增加寻址时间;块太大时,磁盘传输数据的时间会明显大于寻址时间,导致程序无法及时处理这块的数据,因为需要等数据传输完。一般我们的寻址时间在传输时间的百分之1左右较为合适。而数据的传输时间取决于网络带宽和磁盘读取速度的最小值,而网络带宽可以可以调整的,所以取决于磁盘的读取速度。一般的机械硬盘在100M/S左右的传输速率,可以将块大小设置为128M,固态硬盘在200M-300M/S左右的传输速率,可以将块大小设置为256M,这样刚好可以将磁盘的利用率最大化处理,简单处理就是根据磁盘的性能设置块大小,如果磁盘老化或者使用时间过长,可以考虑将128M的块大小改成64M。
按照上面的块大小设置,磁盘的读取时间大概都为1秒左右,这样寻址时间为10ms左右,如果寻址时间超过10ms,则表示小文件过多,需要将小文件合并处理。

3、HDFS组成架构

一般由NameNode(NN)、DataNode(DN)、Client(客户端)和 SecondaryNameNode(2NN)四部分组成。
Client客户端是外界可以对 HDFS 操作的一个统称,当我们在 Shell 上直接发送命令给HDFS创建目录,或者上传文件时,Shell就是客户端;当我们使用 NameNode 的web端对HDFS执行操作时,web端也是一个客户端;当我们使用程序跟HDFS进行交互时,程序端也是客户端。
NameNode 就是相当于 HDFS 的管理者,任何跟 HDFS 的交互都要先经过它,里面存储着每个文件的文件信息,管理着 HDFS 的各个DataNode节点,各数据的副本策略,数据块的映射信息和处理客户端的读写请求等。
DataNode 就是相当于 HDFS 的执行者,每台服务器上面会起一个 DataNode,一个 NameNode 对应多个DataNode,DataNode 执行的就是文件实际的读写操作,并定期向 NameNode 汇报自己管理的数据块是否可用,超过一定时间没有汇报某个数据块的信息,则 HDFS 认为该数据块不可用,就会从其他可用节点恢复该数据块的信息。
SecondaryNameNode 就是相当于 NameNode 的秘书或者助手。为了兼顾 HDFS 的处理速度和可靠性,我们操作HDFS里面文件的记录都会在 NameNode 的内存中保持,并以追加的方式记录在 NameNode 的日志文件中,但日志记录并不是无限增长的,需每隔一段时间,将日志整合,并固化成一般的记录存储在硬盘中,SecondaryNameNode 就是每隔一段时间,或者每隔一定数量的操作,就将 NameNode 的日志取过来整合,并固化成一般的日志文件再交给 NameNode 进行存储。
发生故障时,SecondaryNameNode 能协助恢复一部分的数据,但最新的无法恢复,所以一般实际应用应该使用更加可靠的双 NameNode 模式,一旦一个NameNode发生故障,另一个立马切换接管。
client客户端还需处理文件的切片,就是将文件切片成HDFS规定的块大小,再一个一个上传。client需要与 NameNode 交互,获取文件的位置信息;需要与DataNode交互,执行读取或写入数据操作;Client还提供一些命令来管理HDFS,比如NameNode的格式化;Client也通过一些命令来访问HDFS,比如增删查文件等操作。

二、HDFS的Shell操作

1、基本语法

hadoop fs 具体命令
# 或者
hdfs dfs 具体命令

使用 hadoop fs 和 hdfs dfs 再接具体命令,效果都是一样的,两者是等同的,具体看个人的使用习惯。

2、命令大全

hadoop fs or hdfs dfs 再接以下命令
[-appendToFile <localsrc> ... <dst>]
[-cat [-ignoreCrc] <src> ...]
[-chgrp [-R] GROUP PATH...]
[-chmod [-R] <MODE[,MODE]... | OCTALMODE> PATH...]
[-chown [-R] [OWNER][:[GROUP]] PATH...]
[-copyFromLocal [-f] [-p] <localsrc> ... <dst>]
[-copyToLocal [-p] [-ignoreCrc] [-crc] <src> ... <localdst>]
[-count [-q] <path> ...]
[-cp [-f] [-p] <src> ... <dst>]
[-df [-h] [<path> ...]]
[-du [-s] [-h] <path> ...]
[-get [-p] [-ignoreCrc] [-crc] <src> ... <localdst>]
[-getmerge [-nl] <src> <localdst>]
[-help [cmd ...]]
[-ls [-d] [-h] [-R] [<path> ...]]
[-mkdir [-p] <path> ...]
[-moveFromLocal <localsrc> ... <dst>]
[-moveToLocal <src> <localdst>]
[-mv <src> ... <dst>]
[-put [-f] [-p] <localsrc> ... <dst>]
[-rm [-f] [-r|-R] [-skipTrash] <src> ...]
[-rmdir [--ignore-fail-on-non-empty] <dir> ...]
[-setrep [-R] [-w] <rep> <path> ...]
[-stat [format] <path> ...]
[-tail [-f] <file>]
[-test -[defsz] <path>]
[-text [-ignoreCrc] <src> ...]

具体使用方法,可以单个命令查询帮助信息,如下:

hadoop fs -help rm
hdfs dfs -help tail

注意:HDFS的命令是都是命令前需要加‘-’的,这点需要注意,区别于linux中正常的命令。

3、常用命令介绍

(1)上传文件
1. -moveFromLocal (本地移动文件到HDFS系统,会删除本地)
2. -copyFromLocal (复制本地文件到HDFS系统,本地会保留)
3. -put (跟copyFromLocal一样,习惯常用)
4. -appendToFile (追加文件)

# 将本地A文件移动到HDFS上,不改变名称
hadoop fs -moveFromLocal localfileA.txt /hdfsfile
# 将本地B文件复制到HDFS上,并修改名称为hdfsB文件
hadoop fs -copyFromLocal localfileB.txt /hdfsfile/hdfsfileB.txt
# 将本地C文件复制到HDFS上,并修改名称为hdfsC文件
hadoop fs -put localfileC.txt /hdfsfile/hdfsfileC.txt
# 将本地C文件的内容追加到hdfsB文件上面
hadoop fs -appendToFile localfileC.txt /hdfsfile/hdfsfileB.txt

(2)下载文件
1. -copyToLocal (HDFS拷贝文件下来本地)
2. -get (跟copyToLocal一样,习惯常用)

# 将hdfs上面的文件下载到本地
hadoop fs -copyToLocal /hdfsfile/localfileA.txt ./
hadoop fs -get /hdfsfile/hdfsfileB.txt ./

(3)其他常用操作
1. -ls (显示HDFS目录信息)
2. -cat (显示HDFS文件内容)
3. -chgrp -chmod -chown (更改HDFS组,权限,用户和组,跟linux使用一样)
4. -mkdir (创建HDFS目录)
5. -cp (复制HDFS文件到HDFS另一目录)
6. -mv (移动HDFS文件到HDFS另一目录)
7. -tail (显示HDFS文件最后1kb的内容,比cat常用)
8. -rm (删除HDFS中的文件或目录)
9. -rm -r (递归删除HDFS中的文件或目录)
10. -du -s -h (统计文件夹的大小信息,汇总统计)
11. -du -h (统计文件夹的大小信息,分开单独统计)
12. -setrep (设置HDFS文件的副本数量)

hadoop fs -ls /hdfsfile
hadoop fs -cat /hdfsfile/hdfsfileB.txt
hadoop fs -tail /hdfsfile/hdfsfileB.txt
hadoop fs -mkdir /hdfsfile2
hadoop fs -cp hdfsfile/hdfsfileC.txt /hdfsfile2
hadoop fs -mv hdfsfile/hdfsfileB.txt /hdfsfile2
hadoop fs -rm /hdfsfile2/hdfsfileB.txt
hadoop fs -rm -r /hdfsfile2
hadoop fs -du -s -h /hdfsfile
hadoop fs -du -h /hdfsfile
hadoop fs -setrep 5 /hdfsfile/hdfsfileC.txt

三、HDFS的API操作

在 windows 环境下开发HDFS的操作,首先需要在windows环境下解压hadoop的安装包,并将hadoop的路径添加到环境变量上。这里使用IDEA开发环境,首先需要在Maven工程下导入相应的依赖,相应的依赖如下:

<dependencies>
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-client</artifactId>
        <version>3.1.3</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.30</version>
    </dependency>
</dependencies>

在项目的src/main/resources目录下,新建一个文件,命名为“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

之后可以创建如下类对文件进行操作:

public class HdfsClient {
    @Test
    public void clientDemo() throws IOException, URISyntaxException, InterruptedException {
        // 1 获取文件系统的配置
        Configuration configuration = new Configuration();
        // 这里获取到配置后,可以直接获取配置信息,或者修改配置的信息,这里是将副本策略改为2,即只保留2个副本
        configuration.set("dfs.replication", "2");
        // 2 根据文件系统的配置获取文件系统的句柄,这里客户端访问HDFS时,都要有用户身份的,不加用户会被拒绝访问
        FileSystem fs = FileSystem.get(new URI("hdfs://hadoop102:8020"), configuration,"mrlin");
        // 3 使用句柄可以直接操作HDFS,创建目录
        fs.mkdirs(new Path("/newpath/newpathA/"));
        // 4 上传文件
    	fs.copyFromLocalFile(new Path("d:/localfileA.txt"), new Path("/newpath/newpathA"));
        // 5 下载文件
	    // boolean delSrc 指是否将原文件删除
	    // Path src 指要下载的文件路径
	    // Path dst 指将文件下载到的路径
	    // boolean useRawLocalFileSystem 是否开启文件校验
	    fs.copyToLocalFile(false, new Path("/newpath/newpathA/localfileA.txt"), new Path("d:/localfileB.txt"), true);
	    // 6 修改文件名称
	    fs.rename(new Path("/newpath/newpathA/localfileA.txt"), new Path("/newpath/newpathA/hdfsfileA.txt"));
	    // 7 文件和目录删除
		fs.delete(new Path("/newpath/newpathA/"), true);
        // 8 关闭资源
        fs.close();
    }
}

HDFS文件详情查看:

@Test
public void testListFiles() throws IOException, InterruptedException, URISyntaxException {
	// 1获取文件系统
	Configuration configuration = new Configuration();
	FileSystem fs = FileSystem.get(new URI("hdfs://hadoop102:8020"), configuration, "mrlin");
	// 2 获取文件详情
	RemoteIterator<LocatedFileStatus> listFiles = fs.listFiles(new Path("/"), true);
	while (listFiles.hasNext()) {
		LocatedFileStatus fileStatus = listFiles.next();
		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));
	}
	// 3 关闭资源
	fs.close();
}

HDFS文件和文件夹判断:

@Test
public void testListStatus() throws IOException, InterruptedException, URISyntaxException{
    // 1 获取文件配置信息
    Configuration configuration = new Configuration();
    FileSystem fs = FileSystem.get(new URI("hdfs://hadoop102:8020"), configuration, "mrlin");
    // 2 判断是文件还是文件夹
    FileStatus[] listStatus = fs.listStatus(new Path("/"));
    for (FileStatus fileStatus : listStatus) {
        // 如果是文件
        if (fileStatus.isFile()) {
            System.out.println("f:"+fileStatus.getPath().getName());
        }else {
            System.out.println("d:"+fileStatus.getPath().getName());
        }
    }
    // 3 关闭资源
    fs.close();
}

HDFS参数优先级:
将hdfs-site.xml拷贝到项目的resources资源目录下,这里是设置副本的策略数为1,同时上面的程序中也设置副本的策略数为2,实际运行的时候副本的策略数为2。

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>

<configuration>
	<property>
		<name>dfs.replication</name>
         <value>1</value>
	</property>
</configuration>

这里涉及到参数的优先级,或者说是读取顺序如下:
(1)客户端代码中设置的值 >(2)ClassPath下的用户自定义配置文件 >(3)然后是服务器的自定义配置(xxx-site.xml) >(4)服务器的默认配置(xxx-default.xml)
其实反过来就是读取顺序的原因,hadoop系统启动的时候,会先读取默认的配置,然后再读取用户自定义的配置,如果有相同的值设置,则后读取的会覆盖前面读取的值,最后就是以后读取的值为准。程序运行时,会再读取ClassPath下的配置文件,再在程序运行时,动态的修改配置值,最后就会以程序中修改的值为最终值,表现出来就是上面的优先级顺序。

四、HDFS的读写操作

1、HDFS写操作

客户端跟NameNode交互:
(1)客户端向NameNode发送请求,请求上传文件,NameNode会对客户端的请求进行检查,检查客户端用户是否有权限上传,检查客户端需要上传的目录是否存在等;
(2)NameNode向客户端发送应答,如客户端无权限或目录不存在,返回报错信息;若正常则返回可以上传;
(3)客户端向NameNode请求上传第一个Block(客户端会将需要发送的文件按块大小进行切分)
(4)NameNode向客户端返回对应的副本策略的节点信息,这里比如发送了3个DataNode节点信息,分别为DataNode1(dn1)、DataNode1(dn2) 和 DataNode1(dn3)。
客户端跟DataNode交互:
(5)客户端向最近的dn1节点请求上传数据,建立Block传输通道,dn1收到请求会继续向dn2,dn3请求上传数据建立传输通道。如果由客户端直接向每个DataNode节点交互的话,则3个副本就需要客户端发送3次,这里的网络传输等问题都可能造成数据的丢失和延迟,最好的办法其实就是上传到最近的节点,再由该节点传输给其他节点。
(6)dn3,dn2收到dn1的请求后,发送应答给dn1,再由dn1向客户端发送应答。
(7)客户端开始往dn1上传第一个Block(先从磁盘读取数据放到一个本地内存缓存),然后以Packet为单位,dn1收到一个Packet就会传给dn2,dn2传给dn3;dn1每传一个Packet会放入一个应答队列等待应答。每一个Packet共64k,包含516字节的校验信息(chunk512byte+chunksum4byte)。重复直至一个Block传输完成。
(8)当一个Block传输完成之后,客户端再次请求NameNode上传第二个Block到服务器。(重复执行3-7步直至完成)。

节点距离计算
上面第(4)步,NameNode 在向客户端发送节点的时候,或者客户端在向DataNode传输数据的时候,是怎判断哪个节点是最近的呢。
首先我们知道不管是Shell客户端,程序打包后也是要上传到服务器运行,还是Web端,其实都是在hadoop102这个节点上面运行,所以其实这个客户端就是在hadoop102这个节点,如果hadoop102上面也起了一个DataNode的话,那客户端到DataNode的距离就是0了。
节点的距离计算,就是两个节点到共同父节点的距离之和,称之为两个节点之间的距离。
如下,甲是库房,A和B是不同机架,数字1,2,3为机架的不同层:
甲-A-1 甲-A-1 都是本机,距离之和为0+0=0
甲-A-1 甲-A-2 共同父节点为A,1到A的距离为1,2到A的距离了为1,距离之和为2
甲-A-1 甲-B-2 共同父节点为甲,1到甲的距离为2,2到甲的距离为2,距离之和为4
甲-A-1 甲-2 共同父节点为甲,1到甲的距离为2,2到甲的距离为1,距离之和为3

2、HDFS读操作

(1)客户端向NameNode请求下载文件,NameNode通过查询该文件的元数据,找到文件块所在的DataNode地址和块地址。
(2)NameNode向客户端发送文件的元数据信息,包含DataNode地址和块地址等
(3)客户端挑选一台DataNode(就近原则)服务器,发送读取数据的请求。
(4)DataNode开始向客户端传输数据给客户端(从磁盘里面读取数据输入流,以Packet为单位来做校验)。
(5)客户端以Packet为单位接收,先在本地缓存,然后写入目标文件。

五、NameNode和2NN

NameNode存储的是节点的元数据。该数据存储在FsImage中,系统上电后,会将其读入到内存中,每个节点的元数据大概150byte。后续对节点数据的更新操作,NameNode会将其记录到Edits中,并读入到内存中,Edits数据只是追加,不做修改。所以FsImage+Edits就是对文件的所有操作过程。
2NN就是专门将Edits数据的操作按时合并到FsImage中。

1、NameNode和2NN的启动流程

第一阶段:NameNode启动
(1)第一次启动NameNode时,需要格式化,格式化后会创建镜像文件(Fsimage)和编辑日志(Edits)文件。如果不是第一次启动,则会直接加载编辑日志和镜像文件到内存。
(2)客户端对NameNode请求文件增删改的操作
(3)NameNode 会先在日志进行操作,更新滚动日志,先追加到Edits文件,再读取到内存,防止突然断电后的状态不一致。
(4)NameNode在内存中对元数据进行增删改的操作

第二阶段:Secondary NameNode工作
(1)Secondary NameNode询问 NameNode 是否需要CheckPoint,也就是合并日志。直接带回NameNode是否CheckPoint的结果;
(2)当需要执行CheckPoint时,Secondary NameNode请求执行CheckPoint;
(3)NameNode结束正在写的Edits日志,再创建新的Edits日志文件,命名为Edits_002;
(4)将滚动前的编辑日志Edits和镜像文件Fsimage拷贝到Secondary NameNode;
(5)Secondary NameNode加载编辑日志和镜像文件到内存,并将其合并;
(6)生成新的镜像文件fsimage.chkpoint;
(7)拷贝fsimage.chkpoint到NameNode;
(8)NameNode将fsimage.chkpoint重新命名成fsimage。

2、CheckPoint时间设置

检测点的设置由 hdfs-default.xml 配置文件中的 dfs.namenode.checkpoint.period 进行设置,单位是秒。默认的2NN每隔1个小时进行fsimage和Edits的合并。
hdfs-default.xml

<property>
  <name>dfs.namenode.checkpoint.period</name>
  <value>3600s</value>
</property>

同时,检查点的次数检查由hdfs-default.xml 配置文件中的 dfs.namenode.checkpoint.check.period 进行设置,默认是每隔1分钟检查Edits的操作次数,默认是每隔1百万次操作会进行fsimage和Edits的合并。
以上哪个条件先达到,都会进行合并操作。
hdfs-default.xml

<property>
  <name>dfs.namenode.checkpoint.txns</name>
  <value>1000000</value>
<description>操作动作次数</description>
</property>

<property>
  <name>dfs.namenode.checkpoint.check.period</name>
  <value>60s</value>
<description> 1分钟检查一次操作次数</description>
</property>

3、Fsimage和Edits概念

NameNode被格式化之后,会在/opt/module/hadoop-3.1.3/data/tmp/dfs/name/current目录中产生如下文件:
fsimage 0000000000000000000
fsimage 0000000000000000000.md5
seen txid
VERSION
(1)Fsimage文件: HDFS文件系统元数据的一个永久性的检查点,其中包含HDFS文件系统的所有目录和文件inode的序列化信息
(2)Edits文件:存放HDES文件系统的所有更新操作的路径,文件系统客户端执,行的所有写操作首先会被记录到Edits文件中。
(3) seen txid文件保存的是一个数字,就是最后一个edits 的数字
(4)每次NameNode启动的时候都会将Fsimage文件读入内存,加载Edits里面的更新操作,保证内存中的元数据信息是最新的、同步的,可以看成NameNode启动的时候就将Fsimage和Edits文件进行了合并。

六、DataNode的工作机制

DataNode中一个数据块以文件的形式存储在磁盘上,包括两个文件,一是数据本身,二是元数据包括数据块的长度,块数据的校验和,以及时间戳等。

1、DataNode工作流程

(1)DataNode上电后会主动向NameNode注册汇报信息
(2)NameNode返回应答信息,说明注册成功成功
(3)此后DataNode会周期性(6小时)的检查完自己所有块信息后,向NameNode汇报自己的信息
(4)DataNode会每隔3秒向NameNode传递一次心跳,证明该节点处于连接状态,心跳会返回NameNode给DataNode的命令,例如复制块到另一个节点,或者删除某一个块等信息
(5)DataNode超过10分钟+30秒不向NameNode传递心跳时,NameNode认为该节点不可用,会将该节点上面存储的数据按照副本数,重新分配到其他节点存储。
DataNode不可用超时计算公式:
TimeOut = 2 * dfs.namenode.heartbeat.recheck-interval + 10 * dfs.heartbeat.interval
在配置文件 hdfs-site.xml 中:

<property>
    <name>dfs.namenode.heartbeat.recheck-interval</name>
    <value>300000</value>
</property>

<property>
    <name>dfs.heartbeat.interval</name>
    <value>3</value>
</property>

dfs.namenode.heartbeat.recheck-interval 的单位是毫秒,默认是5分钟,dfs.heartbeat.interval 的单位是秒,默认是3秒。

2、数据完整性

上面说到元数据中包括块数据的校验和(CheckSum),当读取数据时,会同步计算块数据的校验和,跟数据块本身的元数据中的校验和做对比,发现不一致,则认为数据损坏,标记为不可用。hadoop默认的数据校验默认采用CRC校验算法。
数据校验发生在上传数据时,HDFS会对数据进行CRC计算,再跟数据本身传输过来的CRC进行校验,发现不一致,会让客户端重新发送;
数据校验发生在下载数据时,客户端也会对数据进行CRC计算,再跟数据本身传输过来的CRC进行校验,发现不一致,会让DataNode重新发送;
DataNode也会在其文件创建后周期性的对数据进行校验,发现数据缺失或者损坏,就会通知NameNode让NameNode针对该数据再准备副本。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值