Hadoop基础-HDFS基本原理

概述:Hadoop是Apache软件基金会所开发的并行计算框架与分布式文件系统。其核心主要包括三个模块:Hadoop Common,HDFS与MapReduce。

1.Hadoop Common


2.HDFS

概述:hdfs是hadoop分布式文件系统(Hadoop Distributed File System)的缩写,为分布式计算存储提供底层支持。采用java语言编写,可以部署到多种普通的廉价机器上面,以集群处理数量积累达到大型主机处理性能。Hdfs架构原理采用master/slave架构。一个HDFS集群包含一个单独的Name Node 和多个DataNode。NameNode管理的是元数据,而DataNode存储的是实际的数据。

2.1 核心概念

  • 1.Block

物理磁盘中有块的概念,磁盘的物理block是磁盘操作的最小单位,一般为512Byte,文件系统的block是抽象在物理block之上的一般是物理block的数倍。通常为数kb,相对于单机系统而言,HDFS的block要大的多,默认的是128M,在hdfs中的文件会被拆分成block大小的chunk,chunk作为独立的单元存储,当存储比block小的文件时,只会占用实际的大小,如1m的文件占用的是1m的存储空间而不是128M。

设置一个大的block主要是为了减少定位磁盘在整个查找数据中的时间占比。

这种block的好处,这种拆分可以存储比整个磁盘容量还要打的文件,因为构成这个文件的block会分布在整个集群上面,理论上要给文件的block拆分到集群中所有的机器的磁盘上面。Block的抽象简化了存储系统,对于Block无需关注他的权限,所有者等内容,这些信息只需要在文件级别上进行控制,同时block也作为容错和高可用的机制中的副本单元,容错的发生是以block为单位进行复制。

  • 2.NameNode 和DateNode

相关的已经在概述中进行了说明。

同时nameNode 存放文件系统树和所有文件目录的元数据,元数据持久化主要是两个方式:

  • namespace image
  • edit log

主要说明一点在HDFS中NameNode很容易成为集群的单点故障,从而造成整个集群的不可用,为了解决这个问题,主要有以下两个方法:

  • a. 备份持久化元数据:将文件系统的元数据同时写到多个文件系统,例如同时将这些元数据保存到本地系统,这些备份的操作都是原子的,同步的。
  • b.Second NameNode : Second节点定期合并主Namenode的namespace image和edit log, 避免edit log过大,通过创建检查点checkpoint来合并。它会维护一个合并后的namespace image副本, 可用于在Namenode完全崩溃时恢复数据。

对于DataNode来说主要负责存储和提取Block,读写请求可能来自nameNode也可以直接来与客户端,同时DateNode会周期性的向NameNode来提交上传自己的Block相关的信息。

  • 3.Block Cache

对于DataNode经常从磁盘中读取的Block,会在内存中对该Block进行缓存,但是一个Block只会缓存一个数据节点上。

  • 4.HDFS Federation

这种NameNode和DataNode主从的结构,NameNode的内存大小会制约文件的数量,当开启了HDFS Federation模型时,可以横向的对NameNode进行扩展,使用多个节点来分别管理namespace下面的一部分,例如一个nameNode管理/user目录下面的文件,另外一个nameNode管理/share目录下面的文件,这些所有的nameNode同时来维护一个BlockPool,保存Block中节点的映射信息。

  • 5.Hadoop的HA方案

在hdfs集群中,namenode仍然是单点故障的,元数据同时写入到另外的文件系统和Seconde Namenode定期的进行checkpoint有利于保护数据丢失,但是并不能提高可用性。应为当NameNode挂掉之后,常规的做法都是使用元数据备份来重启要给新的那么Node,启动一个新的nameNode耗时会比较久,一般在几十分钟甚至到数个小时。

造成重启耗时的原因大致有:
1) 元数据镜像文件载入到内存耗时较长。
2) 需要重放edit log
3) 需要收到来自DataNode的状态报告并且满足条件后才能离开安全模式提供写服务。

1) 主备需共享edit log存储。
主NameNode和待命的NameNode共享一份edit log,当主备切换时,Standby通过回放edit log同步数据。
共享存储通常有2种选择

NFS:传统的网络文件系统
QJM:quorum journal manager
QJM是专门为HDFS的HA实现而设计的,用来提供高可用的edit log。QJM运行一组journal node,edit log必须写到大部分的journal nodes。通常使用3个节点,因此允许一个节点失败,类似ZooKeeper。注意QJM没有使用ZK,虽然HDFS HA的确使用了ZK来选举主Namenode。一般推荐使用QJM。

2)DataNode需要同时往主备发送Block Report
因为Block映射数据存储在内存中(不是在磁盘上),为了在Active NameNode挂掉之后,新的NameNode能够快速启动,不需要等待来自Datanode的Block Report,DataNode需要同时向主备两个NameNode发送Block Report。

3)客户端需要配置failover模式(对用户透明)
Namenode的切换对客户端来说是无感知的,通过客户端库来实现。客户端在配置文件中使用的HDFS URI是逻辑路径,映射到一对Namenode地址。客户端会不断尝试每一个Namenode地址直到成功。

4)Standby替代Secondary NameNode
如果没有启用HA,HDFS独立运行一个守护进程作为Secondary Namenode。定期checkpoint,合并镜像文件和edit日志。

如果当主Namenode失败时,备份Namenode正在关机(停止 Standby),运维人员依然可以从头启动备份Namenode,这样比没有HA的时候更省事,算是一种改进,因为重启整个过程已经标准化到Hadoop内部,无需运维进行复杂的切换操作。

NameNode的切换通过代failover controller来实现。failover controller有多种实现,默认实现使用ZooKeeper来保证只有一个Namenode处于active状态。

2.2 HDFS数据的读写

1.读数据

image-20210820105303943

1)客户端传递一个文件Path给FileSystem的open方法

2)DFS采用RPC远程获取文件最开始的几个block的datanode地址。Namenode会根据网络拓扑结构决定返回哪些节点(前提是节点有block副本),如果客户端本身是Datanode并且节点上刚好有block副本,直接从本地读取。

3)客户端使用open方法返回的FSDataInputStream对象读取数据(调用read方法)

4)DFSInputStream(FSDataInputStream实现了改类)连接持有第一个block的、最近的节点,反复调用read方法读取数据

5)第一个block读取完毕之后,寻找下一个block的最佳datanode,读取数据。如果有必要,DFSInputStream会联系Namenode获取下一批Block 的节点信息(存放于内存,不持久化),这些寻址过程对客户端都是不可见的。

6)数据读取完毕,客户端调用close方法关闭流对象

在读数据过程中,如果与Datanode的通信发生错误,DFSInputStream对象会尝试从下一个最佳节点读取数据,并且记住该失败节点, 后续Block的读取不会再连接该节点
读取一个Block之后,DFSInputStram会进行检验和验证,如果Block损坏,尝试从其他节点读取数据,并且将损坏的block汇报给Namenode。
客户端连接哪个datanode获取数据,是由namenode来指导的,这样可以支持大量并发的客户端请求,namenode尽可能将流量均匀分布到整个集群。
Block的位置信息是存储在namenode的内存中,因此相应位置请求非常高效,不会成为瓶颈。

3.写文件

image-20210820105425780

1)客户端调用DistributedFileSystem的create方法

2)DistributedFileSystem远程RPC调用Namenode在文件系统的命名空间中创建一个新文件,此时该文件没有关联到任何block。 这个过程中,Namenode会做很多校验工作,例如是否已经存在同名文件,是否有权限,如果验证通过,返回一个FSDataOutputStream对象。 如果验证不通过,抛出异常到客户端。

3)客户端写入数据的时候,DFSOutputStream分解为packets(数据包),并写入到一个数据队列中,该队列由DataStreamer消费。

4)DateStreamer负责请求Namenode分配新的block存放的数据节点。这些节点存放同一个Block的副本,构成一个管道。 DataStreamer将packet写入到管道的第一个节点,第一个节点存放好packet之后,转发给下一个节点,下一个节点存放 之后继续往下传递。

5)DFSOutputStream同时维护一个ack queue队列,等待来自datanode确认消息。当管道上的所有datanode都确认之后,packet从ack队列中移除。

6)数据写入完毕,客户端close输出流。将所有的packet刷新到管道中,然后安心等待来自datanode的确认消息。全部得到确认之后告知Namenode文件是完整的。 Namenode此时已经知道文件的所有Block信息(因为DataStreamer是请求Namenode分配block的),只需等待达到最小副本数要求,然后返回成功信息给客户端。


3.MapReduce

计算一定时间类的最大温度:

Map程序:

image-20210719134756064

Reduce程序:

image-20210719134844464

image-20210719134927858

通过构造JobConf对象来控制实现我们的mapreduce任务,在hadoop集群上执行这个任务时,需要将代码打成要给jar包,在JobConf中传递该类的Class类型,通过调用FiileInpuFormat类的静态函数addInputPath()来定义输入数据的路径,可以是单个文件也可以是目录,将会输入目录下的所有文件。通过调用setOutputPath()来设置reduce函数输出文件的目录,必须是不存在的目录,否则会报错。setMapperClass 和setReducerClass是指定map和reduce类的类型,

image-20210719141703198

一个reduce任务的完整流程如图,虚线框表示节点,虚线箭头表示节点内部的数据传输,实线箭头表示节点之间的数据传输。reduce任务的数量不是输入数据的大小决定的,而是特别指定的。如果有多个reduce任务的时候,map任务会将结果进行分区,map的结果键值对要输入到一个reduce节上的会存放在同一个分区中。同时分区由用户定义的分区函数来控制。

image-20210719142203781