目录

 

hadoop四大组件

HDFS(Hadoop Distributed File System)

初步认知

核心概念

Block

BlockInfoContiguous(BlockInfo)

BlockMap

Package

Chunk

package 构建

块缓存

NameSpace

NameSpaceManagement

BlockManagement

HDFS Federastion

checkPoint(检查点)

fsimage与edits.log

HDFS 架构


 

HDFS角色

Client

NameNode(active)

DataNode

SecondaryNameNode

NameNode 与secondaryNameNode工作机制

HA架构

HA角色

HA工作流程

brain-split(脑裂)



hadoop四大组件

hdfs:分布式文件管理系统

mapreduce:分布式的离线并行计算框架

yarn:作业调度与集群资源管理系统

comment:支持其他模块的工具模块

 

HDFS(Hadoop Distributed File System)

初步认知

1.一个文件系统,可以用于存储文件,通过目录树定位文件

2.由很多服务器共同实现文件存储功能

hdfs 适合一次写入,多次读出的场景,且不支持文件修改,只能追加

hdfs不适合用在:要求低时间延迟数据访问的应用,存储大量的小文件,并发写入,任意修改文件

核心概念

Block

hdfs 中的文件在物理上是分块(block)存储的。每个block 的标识是一个三元组(block id, numBytes,generationStamp),其中block id 由namenode 分配,具有唯一性。每个block文件和block meta 文件由datanode建立,每个block文件对应一个block meta 文件。比Block小的文件不会占用整个Block。一个block 会被拆分成多个package。

Block 类 实现writable 和comparable接口,可以对每个block对象进行序列化和针对block id 进行排序

block id: 唯一标识block对象

numBytes:这个数据块的大小,以字节为单位

generationStamp:数据块的时间戳

块的大小可以通过dfs.blocksize(hdfs-site.xml)来设置,hadoop2.x中默认128m

HDFS 的块比磁盘的块(512B)大,其目的是为了最小化寻址开销。如果块设置得足够大,从磁盘传输数据的时间会明显大于定位这个块开始位置所需的时间。

当然,如果block 设置的过大,在MapReduce任务中,Map或者Reduce任务的个数 如果小于集群机器数量,会使得作业运行效率降低

BlockInfoContiguous(BlockInfo)

以下这两篇博客关于BlockInfoContiguous写的比较不错,可以对应着看。



邻近信息块,用于存储block 副本数及副本位置。有2个内部关键的对象信息BlockCollection和triplets。BlockCollection保存了副本数,副本位置等的一些信息。而triplets 可用于查找DataNode上所有block。

这个数组的长度是3*replicationreplication表示数据块的备份数。这个数组中存储了该数据块所有的备份数据块对应的DataNode信息,我们现在假设备份数是3,那么这个数组的长度是3*3=9,这个数组存储的数据如下:

这个指标什么意思Hadoop_HBase_ritCount hadoop stage_HDFS

triplets包含的信息即为:

  • triplets[i]:Block所在的DataNode;
  • triplets[i+1]:该DataNode上前一个Block;
  • triplets[i+2]:该DataNode上后一个Block

BlockMap

利用blockid 快速定位BlockInfo。BlocksMap底层通过LightWeightGSet实现。LightWeightGSet本质上是利用链表解决碰撞冲突的HashTable。

Package

在DFSclient与DataNode之间通信的过程中,发送和接受数据过程都是以一个packet为基础的方式进行。package 大小由dfs.write.packet.size确定,默认64k。一个package 会包含多个chunk。

这个参数为参考值,是指真正在进行数据传输时,会以它为基准进行调整,调整的原因是一个packet有特定的结构,调整的目标是这个packet的大小刚好包含结构中的所有成员,同时也保证写到DataNode后当前block的大小不超过设定值

Chunk

它是DFSClient到DataNode数据传输中进行数据校验的粒度。

在DFSClient与DataNode之间通信的过程中,在发送数据的过程中是以packet的方式来进行的,每个packet包含了多个chunk。针对往package 中写入的每个chunk,会进行chunksum计算,同时生成chunksum bytes。

chunk校验的数据长度由io.bytes.per.checksum决定,默认512B。事实上一个chunk还包含4B的校验值,因而chunk写入packet时是516B;数据与检验值的比值为128:1,所以对于一个128M的block会有一个1M的校验文件与之对应。

package 构建

在构建一个Packet的过程中,首先将字节流数据写入一个buffer缓冲区中,也就是从偏移量为25的位置(checksumStart)开始写Packet数据Chunk的Checksum部分,从偏移量为533的位置(dataStart,根据数据与检验值的比值为128:1计算)开始写Packet数据的Chunk Data部分,直到一个Packet创建完成为止。

当写一个文件的最后一个Block的最后一个Packet时,如果一个Packet的大小未能达到最大长度,也就是上图对应的缓冲区中,Checksum与Chunk Data之间还保留了一段未被写过的缓冲区位置,在发送这个Packet之前,会检查Chunksum与Chunk Data之间的缓冲区是否为空白缓冲区(gap),如果有则将Chunk Data部分向前移动,使得Chunk Data 1与Chunk Checksum N相邻,然后才会被发送到DataNode节点

块缓存

DataNode通常直接从磁盘读取数据,但是频繁使用的Block可以在内存中缓存。默认情况下,一个Block只有一个数据节点会缓存。但是可以针对每个文件可以个性化配置。 
作业调度器可以利用缓存提升性能,例如连接join 操作中使用的一个小的查询表就是块缓存的一个应用。 
用户或者应用可以向NameNode发送缓存指令(缓存哪个文件,缓存多久), 缓存池的概念用于管理一组缓存的权限和资源。

NameSpace

 

以上博文namespace解析的很不错。此博主也是一个宝藏博主。

在分布式存储系统中,分散在不同节点中的数据可能属于同一个文件,为了组织众多的文件,把文件可以放到不同的文件夹中,文件夹可以一级一级的包含。我们把这种组织形式称为命名空间。

HDFS的命名空间包含目录、文件和块。

NameSpaceManagement

命名空间管理:是指命名空间支持对HDFS中的目录、文件和块做类似文件系统的创建、修改、删除、列表文件和目录等基本操作。

BlockManagement

在块存储服务中包含两部分工作:块管理物理存储。这是一个更通用的存储服务。其他的应用可以直接建立在Block Storage上,如HBase,Foreign Namespaces等。

块管理

1.处理Data Node向Name Node注册的请求,处理datanode的成员关系,处理来自Data Node周期性的心跳

2.处理来自块的报告信息,维护块的位置信息

3.处理与块相关的操作:块的创建、删除、修改及获取块信息

4.管理副本放置(replica placement)和块的复制及多余块的删除

物理存储

所谓物理存储就是:Data Node把块存储到本地文件系统中,对本地文件系统的读、写

 

HDFS Federastion

详见

即为namenode的一个水平扩展。namenode在内存中保存文件系统中的每个文件和每个数据块的引用关系,这意味着对于一个拥有大量文件的超大集群来说,namenode内存将成为限制系统横向扩展的瓶颈。

通过引入联邦hdfs,系统可以通过添加namenode实现扩展,其中每个namenode管理文件系统命名空间的一部分。例如,一个namenode可能管理/usr目录下所有文件,而另一个namenode可能管理/share 目录下的所有文件。

在HDFS Federation中的Namenode之间是联盟关系,他们之间相互独立且不需要相互协调。HDFS Federation中的Namenode提供了提供了命名空间管理和块管理功能。HDFS Federation中的datanode被所有的Namenode用作公共存储块的地方。

访问HDFS Federastion 集群,客户端需要使用客户端挂载数据表(client side mount table)将文件路劲映射到namenode。

通过views://URI 进行配置。例如   /user -> hdfs://nn1/containingUserDir/user。前者为ViewFileSystem中的路径,后者才是代表的真正集群路径。

namenode的heap空间扩展不可行原因:

这样纵向扩展带来的第一个问题就是启动问题,启动花费的时间太长。当前具有50GB Heap Namenode的HDFS启动一次大概需要30分钟到2小时,那512GB的需要多久?

第二个潜在的问题就是Namenode在Full GC时,如果发生错误将会导致整个集群宕机。第三个问题是对大JVM Heap进行调试比较困难。优化Namenode的内存使用性价比比较低

其架构如下:

这个指标什么意思Hadoop_HBase_ritCount hadoop stage_hdfs_02

 

 Block Pool(块池)

由属于同一个命名空间的所有数据块(信息)组成,这个块池中的数据块可以存储在集群中的所有Datanode上,而每个Datanode都可以存储集群中所有块池的数据块。

Datanode是一个物理概念,而block pool是一个重新将block划分的逻辑概念

Block pool允许一个命名空间在不通知其他命名空间的情况下为一个新的block创建Block ID。

Datanode中的数据结构都通过块池ID(BlockPoolID)索引,即datanode中的BlockMap,storage等都通过BPID索引。

在HDFS中,所有的更新、回滚都是以Namenode和BlockPool为单元发生的。即同一HDFS Federation中不同的Namenode/BlockPool之间没有什么关系。

Namespace Volume(命名空间卷)

由命名空间的元数据和一个block Pool 组成。命名空间卷之间相互独立,两两之间并不通信,甚至其中一个namenode的失效也不会影响其他namenode维护的命名空间的可用性。

checkPoint(检查点)

在NameNode启动时候,会先将fsimage中的文件系统元数据信息加载到内存,然后根据eidts中的记录将内存中的元数据同步至最新状态,将这个新版本的 FsImage 从内存中保存到本地磁盘上,然后删除旧的 Editlog,这个过程称为一个检查点

checkponit阈值存在于hdfs-default.xml文件中,分为时间参数(默认一小时)与操作次数(一分钟检查一次操作次数,默认一百万次操作次数)

//时间参数设置
<property>
 <name>dfs.namenode.checkpoint.period</name>
 <value>3600</value>
</property>
//操作次数参数设置
<property>
 <name>dfs.namenode.checkpoint.txns</name>
 <value>1000000</value>
<description>操作动作次数</description>
</property>

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

fsimage与edits.log

这个指标什么意思Hadoop_HBase_ritCount hadoop stage_HDFS_03

fsimage

内存元数据在本地磁盘的映射,用于管理维护文件系统树

其中包含 HDFS文件系统的所有目录和文件 inode 的序列化信息。 每个inode 是一个文件和目录的元数据的内部描述方式

简单的说,Fsimage就是在某一时刻,整个hdfs 的快照,就是这个时刻hdfs上所有的文件块和目录,分别的状态,位于哪些个datanode,各自的权限,各自的副本个数等。

文件名格式为fsimage_txid。fsimage文件个数通过dfs.namenode.num.checkpoints.retained来配置,默认是两个,超出部分会删除。

edits.log

存放 HDFS 文件系统的所有更新操作的路径,文件系统客户端执行的所有写操作首先会被记录到 edits 文件中

edit log文件以edits_开头,后面跟一个txid范围段,并且多个edit log之间首尾相连,正在使用的edit log名字edits_inprogress_txid

在启动HDFS时,只需要读入fsimage_0000000000000008927以及edits_inprogress_0000000000000008928就可以还原出当前hdfs的最新状况。fsimageid总会比edits.log小

fsImage文件和editsLog文件可以通过ID来互相关联。如果是非HA集群的话,这两个数据文件保存在dfs.namenode.name.dir设置的路径下,会保存fsImage文件和editsLog文件.

如果是HA集群的话,editsLog文件保存在参数dfs.journalnode.edits.dir设置的路径下,即edits文件由qjournal集群管理
 

HDFS BALANCER(均衡器)


随着时间推移,各个datanode上的块分布会越来越不均衡。不均衡的集群会降低MapReduce的本地性,导致部分datanode更为繁忙。

Hadoop的开发人员在开发Balancer程序的时候,遵循了以下几点原则:

1.    在执行数据重分布的过程中,必须保证数据不能出现丢失,不能改变数据的备份数,不能改变每一个rack中所具备的block数量。

2.    系统管理员可以通过一条命令启动数据重分布程序或者停止数据重分布程序。

3.    Block在移动的过程中,不能暂用过多的资源,如网络带宽。

4.    数据重分布程序在执行的过程中,不能影响name node的正常工作。

 

HDFS 架构

HDFS角色

Client

即为客户端,主要功能包括

1.文件切分。文件上传 HDFS 的时候,Client 将文件切分成一个一个的 Block, 然后进行存储。

2.调用hdfs api操作文件

3.与namenode进行交互,获取文件元数据

4.与datanode进行交互进行数据读写

NameNode(active)

即为master,主要功能包括

1.管理元数据:hdfs的名称空间 ,inode和block的映射、block和DataNode的映射 。inode和block的映射,固化在磁盘上。而block和DataNode的映射在DataNode启动时上报给NameNode。

2.配置副本策略 

3.处理客户端读写请求

namenode中的文件系统树及整棵树内所有的文件和目录会永久保存在本地磁盘上:fsimage与edit.log 。

DataNode

即为slave,主要功能包括:

1.存储实际的数据块

2.执行数据块的读写操作。读写请求可能来自client和namenode

3.每个datanode会去每一个Namenode注册,并且周期性的给所有的Namenode发送心跳(heartbeat)及datanode的使用报告

SecondaryNameNode

其为当前namenode(active) 的冷备份。当 NameNode 挂掉的时候,它并不能马上替换 NameNode 并提供服务。存在于内存中的edits.log中记录的操作将会丢失。主要功能为:

1.辅助namenode,分担其工作量

2.定期合并fsimage与edit.log文件,并把合并成新的fsimage文件发送给namenode

3.紧急情况下辅助恢复namenode

Secondary Namenode通常运行在另一台机器,因为合并操作需要耗费大量的CPU和内存。其数据落后于Namenode,因此当Namenode崩溃时,Secondary Namenode 只能恢复到最新的fsiamge所对应的时刻,而存在于editlog中还没合并到fsimage中的操作会丢失

由于secondaryNameNode 并不能很好解决NameNode 单节点故障问题,所以通常会配置HA,使用另外一个Namenode(stand by)来作为namenode 的热备份。

NameNode 与secondaryNameNode工作机制

这个指标什么意思Hadoop_HBase_ritCount hadoop stage_命名空间_04

 

namenode启动

1.如果是namenode第一次启动,那么namenode会创建fsimage与edits.log文件。如果不是namenode第一次启动,那么会加载fsimage与edits.log文件到内存

2.客户端对元数据进行增删改的请求

3.namenode记录操作日志并且滚动更新edits.log

4.namenode 在内存中对元数据进行增删改

secondaryNameNode 工作

1.Secondary NameNode 询问 namenode 是否需要 checkpoint(checkpoint参数见上文)。直接带回 namenode 是否检查结果。

2.Secondary NameNode 请求执行 checkpoint

3.namenode 滚动正在写的 edits 日志,该文件名为edit_inprogress_txid

4.将滚动前的编辑日志和镜像文件拷贝到 Secondary NameNode(通过http)

5.Secondary NameNode 加载编辑日志和镜像文件到内存,并合并

6.生成新的镜像文件 fsimage.chkpoint

7.拷贝 fsimage.chkpoint 到 namenode

8.namenode 将 fsimage.chkpoint 重新命名成 fsimage

 

HA架构

这个指标什么意思Hadoop_HBase_ritCount hadoop stage_HDFS_05

HA角色

ZKFailoverController

内部维护着两个两个重要的类,HealthMonitor和ActiveStandbyElector

HealthMonitor:用于namenode的健康检查

ActiveStandbyElector:当active的namenode出现异常,调用ActiveStandbyElector相关方法进行主备选举,选举成功之后,进行相应的主备切换。

HA工作流程

1.启动zookpeer,启动hadoop集群。当namenode,datanode启动时,ZKFailoverController也会作为一个独立线程启动,实际启动的是其子类dfszkfailovercontroller。通过ZKFailoverController类的main方法中创执行create()方法对ZKFailoverController对象进行初始化,在其父类的doRun方法中执行initZK()对zookeeper进行初始化,在初始化方法中会建立 ZK连接和ZK event watcher,构造主备选举的类。

ZKFailoverController对象中会存在NameNode的命名服务ID:NamenodeNameServiceId,NameNode ID,封装了网络连接的相关地址参数,包括socket地址等的对象

接下来通过initRPC()方法初始化相应的rpc服务,然后通过initHM()方法来初始化HealthMonitor,并注册回调函数,HealthMonitor监测namenode的状态,通过这个回调函数来通知ZKFailoverController,以便做相应的主备选举工作。

HealthMonitor启动时会在namenode 内部启动HADeamon,定时和DFSZKFailoverController的HealthMonitor进行心跳检测,HealthMonitor 主要检测 NameNode 的两类状态,分别是 HealthMonitor.State 和 HAServiceStatus

HealthMonitor.State 主要包括:

NITIALIZING:HealthMonitor 在初始化过程中,还没有开始进行健康状况检测; 
SERVICE_HEALTHY:NameNode 状态正常; 
SERVICE_NOT_RESPONDING:调用 NameNode 的 monitorHealth 方法调用无响应或响应超时; 
SERVICE_UNHEALTHY:NameNode 还在运行,但是 monitorHealth 方法返回状态不正常,磁盘存储资源不足; 
HEALTH_MONITOR_FAILED:HealthMonitor 自己在运行过程中发生了异常,不能继续检测 NameNode 的健康状况,会导致 ZKFailoverController 进程退出

通过监控ha.health-monitor.check-interval.ms去设置监控的间隔时间和通过参数ha.health-monitor.rpc-timeout.ms设置timeout时间,当集群变大的时候,需要适当的设置改值,让ZKFC的HealthMonitor没那么“敏感”。

回调函数(HealthCallbacks)中主要用于检查namenode健康状态,NN健康(SERVICE_HEALTHY)就加入选举,如果不健康就退出选举(SERVICE_UNHEALTHYSERVICE_NOT_RESPONDING

2.其启动后的namenode都会进入standBy,此时的HealthMonitor监控NN,发现是HEALTH的状态

其实监控部分,就是一个rpc不断的发送请求,让NN自检测然后在返回相应的数据

执行enterState会通知回调函数,进行处理。对于HEALTH状态的开始调用ActiveStandbyElector的joinElectio方法进行namenode的主备选举工作,最终调用了createLockNodeAsync方法在zookeeper上创建了一个临时节点。

Zookeeper 的写一致性会保证最终只会有一个 ActiveStandbyElector 成功创建一个路径为/hadoop-ha//ActiveStandbyElectorLock 的临时节点 (${dfs.nameservices} (为 Hadoop 的配置参数 dfs.nameservices 的值)

在zk上创建节点之后,会回调processResult方法返回成功创建zkLockPath的节点,行becomeActive()方法将此节点变成active,如果执行成功,继续监控节点状态,其他namenode节点变成standby,继续监控Lock节点状态。如果执行失败,继续进行主备选举

3.处于active状态的namenode会接收和处理来自client端与datanode的请求,并通过JournalSet把edits.log文件写入本地磁盘和journalnode本地磁盘。 namenode 会同时向所有journalnode并行写文件,只要有(n-1)/2个节点写成功则认为此次写操作成功

Active NameNode 在完成一个 EditLog Segment 的写入之后,就会向 JournalNode 集群发送 finalizeLogSegment RPC 请求,将完成写入的 EditLog Segment finalized,然后开始下一个新的 EditLog Segment。从 JournalNode 集群上同步的 EditLog 都是处于 finalized 状态的 EditLog Segment。

提交 EditLog 失败会导致 Active NameNode 关闭 JournalSet 之后退出进程,留待处于 Standby 状态的 NameNode 接管之后进行数据恢复。

写本地由配置中参数dfs.namenode.name.dir控制,写JN由参数dfs.namenode.shared.edits.dir控制

4.处于standby状态的namenode会定期检查journalnode上edit.log文件的变化,并且将edit.log文件拉回本地。当standyby的namenode检查到checkpoint的条件满足,standybynamenode会将当前状态的元数据保存到一个临时的fsimage文件(fsimage.ckpt)然后比对从JN上拉到的最新EditLog的事务ID,将fsimage.ckpt_中没有,EditLog中有的所有元数据修改记录合并一起并重命名成新的fsimage文件,同时生成一个md5文件。将最新的fsimage再通过HTTP请求传回ActiveNameNode

5.如果 Active NameNode 对应的 HealthMonitor 检测到 NameNode 的状态异常时, ZKFailoverController 会主动删除当前在 Zookeeper 上建立的临时节点/hadoop-ha//ActiveStandbyElectorLock。

处于 Standby 状态的 NameNode 的 ActiveStandbyElector 注册的监听器就会收到这个节点的 NodeDeleted 事件。收到这个事件之后,会马上再次进入到创建/hadoop-ha//ActiveStandbyElectorLock 节点的流程,如果创建成功,这个本来处于 Standby 状态的 NameNode 就选举为主 NameNode 并随后开始切换为 Active 状态。

brain-split(脑裂)

假死

所谓的“假死”是指如果 Zookeeper 客户端机器负载过高或者正在进行 JVM Full GC,那么可能会导致 Zookeeper 客户端到 Zookeeper 服务端的心跳不能正常发出,一旦这个时间持续较长,超过了配置的 Zookeeper Session Timeout 参数的话,Zookeeper 服务端就会认为客户端的 session 已经过期从而将客户端的 Session 关闭

脑裂

具体到本文所述的 NameNode,假设 NameNode1 当前为 Active 状态,NameNode2 当前为 Standby 状态。如果某一时刻 NameNode1 对应的 ZKFailoverController 进程发生了“假死”现象,那么 Zookeeper 服务端会认为 NameNode1 挂掉了,根据前面的主备切换逻辑,NameNode2 会替代 NameNode1 进入 Active 状态。但是此时 NameNode1 可能仍然处于 Active 状态正常运行,即使随后 NameNode1 对应的 ZKFailoverController 因为负载下降或者 Full GC 结束而恢复了正常,感知到自己和 Zookeeper 的 Session 已经关闭,但是由于网络的延迟以及 CPU 线程调度的不确定性,仍然有可能会在接下来的一段时间窗口内 NameNode1 认为自己还是处于 Active 状态。这样 NameNode1 和 NameNode2 都处于 Active 状态,都可以对外提供服务。

Zookeeper 社区对这种问题的解决方法叫做 fencing。

隔离双写

1.当NameNode 成为active时,都会被赋予一个EpochNumber。

每个EpochNumber是惟一的,不会有相同的EpochNumber出现。EpochNumber有严格顺序保证,每次NN切换后EpochNumber都会自增1,后面生成的EpochNumber都会大于前面的EpochNumber

2.namenode要对edits.log进行修改之前,Namenode会把EpochNumber传递给QuorumJournalManager

3. QJM把自己的EpochNumber通过newEpoch(N)的方式发送给所有JN结点

4.当JN收到newEpoch请求后,会把QJM的EpochNumber保存到一个lastPromisedEpoch变量中并持久化到本地磁盘

5. ActiveNameNode同步日志到JN的任何RPC请求(如logEdits(),startLogSegment()等),都必须包含ActiveNameNode的EpochNumber

6.JN在收到RPC请求后,会将之与lastPromisedEpoch对比,如果请求的EpochNumber小于lastPromisedEpoch,将会拒绝同步请求,反之,会接受同步请求并将请求的EpochNumber保存在lastPromisedEpoch