目录
编辑
一、HDFS基础
1.1 概述
1.2 HDFS的设计目标
1.2.1 硬件故障
1.2.2 流式数据访问
1.2.3 超大数据集
1.2.4 简单的一致性模型
1.2.5 移动计算而不是移动数据
1.2.6 跨异构硬件和软件平台的可移植性
1.3 基础概念
1.3.1 块(Block)
1.3.2 复制(Replica)
1.3.3 名字节点(Namenode)
1.3.4 Datanode
1.3.5 FileSystem
二、 HDFS架构
2.1 总体架构
2.1.1 角色功能
2.1.1.1 NameNode
2.1.1.2 DataNode
2.1.1.3 Client
2.1.2 元数据持久化
2.1.3 安全模式
2.1.4 SNN(SecondaryNameNode)
2.2 主从架构分析
2.2.1 单节点主从架构分析
2.2.1.1 主从分析
2.2.1.2 主从架构问题
2.3 解决方案
2.3.1 单点故障(HA集群方案)
2.3.1.1 总体架构
2.3.1.2 架构说明
2.3.1.3 架构角色说明
2.3.1.3.1 NameNode active
2.3.1.3.2 NameNode standby
2.3.1.3.3 JournalNode
2.3.1.3.4 zkfc(FailoverController)
2.3.1.3.5 ZooKeeper
2.3.2 压力过大,内存受限(联帮机制:Federation)
2.3.2.1 Federation 产生背景
2.3.2.1.1 单组Namenode架构
2.3.2.1.2 单组Namenode局限性
2.3.2.1.3 为什么要引入Federation?
2.3.2.2 Federation 介绍
2.3.2.2.1 Federation架构
2.3.2.3 Federation主要优点
2.3.2.3.1 Namespace的可扩展性
2.3.2.3.2 Performance(性能)
2.3.2.3.3 Isolation(隔离)
2.3.2.4 Federation 主要缺点
1)交叉访问问题
2)管理性问题
2.3.2.5 Federation局限性
三、HDFS读写流程
3.1 写流程
3.2 读流程
一、HDFS基础
1.1 概述
Hadoop分布式文件系统(HDFS)是指被设计成适合运行在通用硬件(commodity hardware)上的分布式文件系统(Distributed File System)。HDFS在最开始是作为Apache Nutch搜索引擎项目的基础架构而开发的。HDFS是Apache Hadoop Core项目的一部分。
1.2 HDFS的设计目标
1.2.1 硬件故障
硬件故障对于HDFS来说应该是常态而非例外。HDFS包含数百或数千台服务器(计算机),每台都存储文件系统的一部分数据。事实上,HDFS存在大量组件并且每个组件具有非平凡的故障概率,这意味着某些组件始终不起作用。因此,检测故障并从中快速自动恢复是HDFS的设计目标。
1.2.2 流式数据访问
在HDFS上运行的应用程序不是通常在通用文件系统上运行的通用应用程序,需要对其数据集进行流式访问。HDFS用于批处理而不用于用户的交互式使用,相对于数据访问的低延迟更注重数据访问的高吞吐量。
可移植操作系统接口(Portable Operating System Interface of UNIX, POSIX)标准设置的一些硬性约束对HDFS来说是不需要的,因此HDFS会调整一些POSIX特性来提高数据吞吐率,事实证明是有效的。
1.2.3 超大数据集
在HDFS上运行的应用程序具有大型数据集。HDFS上的一个文件大小一般在吉字节(GB)到太字节(TB)。因此,HDFS需要设计成支持大文件存储,以提供整体较高的数据传输带宽,能在一个集群里扩展到数百上千个节点。一个HDFS实例需要支撑千万计的文件。
1.2.4 简单的一致性模型
HDFS应用需要“一次写入多次读取”访问模型。假设一个文件经过创建、写入和关闭之后就不会再改变了。这一假设简化了数据一致性问题,并可实现高吞吐量的数据访问。MapReduce应用或网络爬虫应用都非常适合这个模型。将来还需要扩充这个模型,以便支持文件的附加写操作。
1.2.5 移动计算而不是移动数据
当应用程序在其操作的数据附近执行时,计算效率更高。当数据集很大时更是如此,这可以最大限度地减少网络拥塞并提高系统的整体吞吐量。HDFS为应用程序提供了接口,使其自身更靠近数据所在的位置。
1.2.6 跨异构硬件和软件平台的可移植性
HDFS的设计考虑到了异构硬件和软件平台间的可移植性,方便了HDFS作为大规模数据应用平台的推广。
从Hadoop这些年的发展来看,HDFS依靠上述特性,成为不断演进变革的大数据体系的坚实基石。
1.3 基础概念
1.3.1 块(Block)
Block是HDFS文件系统处理的最小单位,一个文件可以按照Block大小划分为多个Block,不同于Linux文件系统中的数据块,HDFS文件通常是超大文件,因此Block大小一般设置得比较大,默认为128MB。
1.3.2 复制(Replica)
HDFS通过冗余存储来保证数据的完整性,即一个Block会存放在N个Datanode中,HDFS客户端向Namenode申请新Block时,Namenode会根据Block分配策略为该Block分配相应的Datanode replica,这些Datanode组成一个流水线(pipeline),数据依次串行写入,直至Block写入完成。
1.3.3 名字节点(Namenode)
Namenode是HDFS文件系统的管理节点,主要负责维护文件系统的命名空间(Namespace)或文件目录树(Tree)和文件数据块映射(BlockMap),以及对外提供文件服务。
HDFS文件系统遵循POXIS协议标准,与Linux文件系统类似,采用基于Tree的数据结构,以INode作为节点,实现一个目录下多个子目录和文件。INode是一个抽象类,表示File/Directory的层次关系,对于一个文件来说,INodeFile除了包含基本的文件属性信息,也包含对应的Block信息。
数据块映射信息则由BlockMap负责管理,在Datanode的心跳上报中,将向Namenode汇报负责存储的Block列表情况,BlockMap负责维护BlockID到Datanode的映射,以方便文件检索时快速找到Block对应的HDFS位置。
HDFS每一步操作都以FSEditLog的信息记录下来,一旦Namenode发生宕机重启,可以从每一个FSEditLog还原出HDFS操作以恢复整个文件目录树,如果HDFS集群发生过很多变更操作,整个过程将相当漫长。
因此HDFS会定期将Namenode的元数据以FSImage的形式写入文件中,这一操作相当于为HDFS元数据打了一个快照,在恢复时,仅恢复FSImage之后的FSEditLog即可。
由于Namenode在内存中需要存放大量的信息,且恢复过程中集群不可用,HDFS提供HA(主/备Namenode实现故障迁移Failover)以及Federation(多组Namenode提供元数据服务,以挂载表的形式对外提供统一的命名空间)特性以提高稳定性和减少元数据压力。
1.3.4 Datanode
Datanode是HDFS文件系统的数据节点,提供基于Block的本地文件读写服务。定期向Namenode发送心跳。Block在本地文件系统中由数据文件及元数据文件组成,前者为数据本身,后者则记录Block长度和校验和(checksum)等信息。扫描或读取数据文件时,HDFS即使运行在廉价的硬件上,也能通过多副本的能力保证数据一致性。
1.3.5 FileSystem
HDFS客户端实现了标准的Hadoop FileSystem接口,向上层应用程序提供了各种各样的文件操作接口,在内部使用了DFSClient等对象并封装了较为复杂的交互逻辑,这些逻辑对客户端都是透明的。
二、 HDFS架构
2.1 总体架构
HDFS是一个典型的主/备(Master/Slave)架构的分布式系统,由一个名字节点Namenode(Master) +多个数据节点Datanode(Slave)组成。其中Namenode提供元数据服务,Datanode提供数据流服务,用户通过HDFS客户端与Namenode和Datanode交互访问文件系统。
如上图所示:HDFS把文件的数据划分为若干个块(Block),每个Block存放在一组Datanode上,Namenode负责维护文件到Block的命名空间映射以及每个Block到Datanode的数据块映射。
2.1.1 角色功能
2.1.1.1 NameNode
1)完全基于内存存储文件元数据、目录结构、文件block的映射
2)需要持久化方案保证数据可靠性
3)提供副本放置策略
2.1.1.2 DataNode
1)基于本地磁盘存储block(文件的形式)
2)并保存block的校验和数据保证block的可靠性
3)与NameNode保持心跳,汇报block列表状态
2.1.1.3 Client
1)和NameNode交互文件元数据和NameNode交互文件元数据
2)和DataNode交互文件block数据
2.1.2 元数据持久化
1)任何对文件系统元数据产生修改的操作,Namenode都会使用一种称为EditLog的事务日志记录下来
2)使用FsImage存储内存所有的元数据状态
3)使用本地磁盘保存EditLog和FsImage
4)EditLog具有完整性,数据丢失少,但恢复速度慢,并有体积膨胀风险
5)FsImage具有恢复速度快,体积与内存数据相当,但不能实时保存,数据丢失多
6)NameNode使用了FsImage+EditLog整合的方案:
滚动将增量的EditLog更新到FsImage,以保证更近时点的FsImage和更小的EditLog体积
2.1.3 安全模式
1)HDFS搭建时会格式化,格式化操作会产生一个空的FsImage
2)当Namenode启动时,它从硬盘中读取Editlog和FsImage
3)将所有Editlog中的事务作用在内存中的FsImage上
4)并将这个新版本的FsImage从内存中保存到本地磁盘上
5)然后删除旧的Editlog,因为这个旧的Editlog的事务都已经作用在FsImage上了
6)Namenode启动后会进入一个称为安全模式的特殊状态。
7)处于安全模式的Namenode是不会进行数据块的复制的。
8)Namenode从所有的 Datanode接收心跳信号和块状态报告。
9)每当Namenode检测确认某个数据块的副本数目达到这个最小值,那么该数据块就会被认为是副本安全(safely replicated)的。
10)在一定百分比(这个参数可配置)的数据块被Namenode检测确认是安全之后(加上一个额外的30秒等待时间),Namenode将退出安全模式状态。
11)接下来它会确定还有哪些数据块的副本没有达到指定数目,并将这些数据块复制到其他Datanode上。
2.1.4 SNN(SecondaryNameNode)
1)在非Ha模式下,SNN一般是独立的节点,周期完成对NN的EditLog向FsImage合并,减少EditLog大小,减少NN启动时间
2)根据配置文件设置的时间间隔fs.checkpoint.period 默认3600秒
3)根据配置文件设置edits log大小fs.checkpoint.size规定edits文件的最大值默认是64MB
Primary NameNode 和Secondary NameNode的数据合并流程如下图:
2.2 主从架构分析
2.2.1 单节点主从架构分析
2.2.1.1 主从分析
1)主从集群:结构相对简单,主与从协作
2)主:单点,数据一致好掌握
2.2.1.2 主从架构问题
1)单点故障,集群整体不可用
2)压力过大,内存受限
2.3 解决方案
2.3.1 单点故障(HA集群方案)
2.3.1.1 总体架构
2.3.1.2 架构说明
HA使用active NameNode和standbyNameNode两个节点解决单点问题,两个NameNode节点通过JournalNode集群共享状态,通过ZKFC(**FailoverController**)选举active,监控NameNode的状态,实现自动备源,DataNode会同时向两个NameNode节点发送心跳。
2.3.1.3 架构角色说明
2.3.1.3.1 NameNode active
1)接受Client的rpc请求并处理,自己写一份editlog,同时向JournalNode集群发送一份editlog
2)同时接受DataNode的块报告block report, 块位置更新block location updates和心跳heartbeat
2.3.1.3.2 NameNode standby
NameNode standby是NameNode active的一个热备,一旦切换active状态,可以及时对外提供服务
1)同样的会接受JournalNode上面的editlog,并执行更新,与NameNode active的元数据保持同样的状态
2)同时接受DataNode的块报告block report, 块位置更新block location updates和心跳heartbeat
2.3.1.3.3 JournalNode
用于同步active NameNode和standby NameNode之间的数据,本身由一组JournalNode节点组成的集群,一般是奇数,保证高可用。
2.3.1.3.4 zkfc(FailoverController)
主要用来监控NameNode节点的健康状态,zkfc会向zookeper集群发送心跳,让自己被选举,如果自己被选举主时,会通过rpc调用NameNode,让NameNode变成active状态。
2.3.1.3.5 ZooKeeper
ZooKeeper 分布式应用程序服务的组件,接收zkfc心跳和NameNode注册信息,选举出zkfc和NameNode的主。
2.3.2 压力过大,内存受限(联帮机制:Federation)
2.3.2.1 Federation 产生背景
2.3.2.1.1 单组Namenode架构
HDFS主要有两大模块:
1、Namespace(命名空间):由目录、文件和块组成,它支持所有命名空间相关的文件操作,如创建、删除、修改,查看所有文件和目录。
2、Block Storage Service(块存储服务):包括Block管理和存储两部分。
1)Block管理: 通过控制注册以及阶段性的心跳,来保证Datanode的正常运行;处理Block的报告信息和维护块的位置信息;支持Block相关的操作,如创建、删除、修改、获取Block的位置信息;管理Block的冗余信息、创建副本、删除多余的副本等。
2)存储: Datanode提供本地文件系统上Block的存储、读写、访问等。
通常情况下,单组Namenode能够满足集群大部分需求,单点故障问题可以通过启用HA解决,单组Namenode包含一主一备两个Namenode,通过Zookeeper保障及控制Failover,而Zookeeper本身具有高可用特性,好像完全不用担心单点故障造成集群不可用的问题,一切看起来似乎非常完美。然而,随着集群规模不断的增长,似乎又不是那么完美了。
2.3.2.1.2 单组Namenode局限性
单组Namenode只允许整个集群有一个活动的Namenode,管理所有的命名空间。随着集群规模的增长,在1000个节点以上的大型Hadoop集群中,单组Namenode的局限性越发的明显,主要表现在以下几个方面:
1)扩展性:Namenode内存使用和元数据量正相关。180GB堆内存配置下,元数据量红线约为7亿,而随着集群规模和业务的发展,即使经过小文件合并与数据压缩,仍然无法阻止元数据量逐渐接近红线。
2)可用性:随着元数据量越来越接近7亿,CMS GC频率也越来越高,期间也曾发生过一次在CMS GC过程中由于大文件“get Block location”并发过高导致的promotion fail。
3)性能:随着集群规模增长,Namenode响应的RPC QPS也在逐渐提高。越来越高并发的读写,与Namenode的粗粒度元数据锁,使Namenode RPC响应延迟和平均RPC队列长度都在慢慢提高。
4)隔离性:由于Namenode没有隔离性设计,单一对Namenode负载过高的应用,会影响到整个集群的服务能力
既然单组Namenode存在上述局限性,那么为什么要通过Federation的方式横向拓展Namenode,纵向拓展Namenode为什么不行?不选择纵向拓展Namenode的原因主要体现在以下三个方面:
1)启动时间长:Namenode启动需要将元数据加载到内存中,具有128 GB Java Heap的Namenode启动一次大概需要40分钟到1个小时,那512GB呢?
2)调试困难:对大JVM Heap进行调试比较困难,优化Namenode的内存使用性价比比较低。
3)集群易宕机:Namenode在Full GC时,如果发生错误将会导致整个集群宕机。
2.3.2.1.3 为什么要引入Federation?
1)采用Federation的最主要的原因是简单,Federation能够快速的解决大部分单Namenode的问题。
2)Federation是简单鲁棒的设计,由于联邦中各个Namenode之间是相互独立的。Federation整个核心设实现大概用了3.5个月。大部分改变是在Datanode、Config和Tools,而Namenode本身的改动非常少,这样Namenode的原先的鲁棒性不会受到影响。比分布式的Namenode简单,虽然这种事先的扩展性比起真正的分布式的Namenode要小些,但是可以迅速满足需求。
3)Federation良好的向后兼容性,已有的单Namenode的部署配置不需要进行太大的改变就可以继续工作。
2.3.2.2 Federation 介绍
2.3.2.2.1 Federation架构
为了水平扩展名称服务,Federation使用多组独立的Namenodes/Namespaces。所有的Namenodes是联邦的,也就是说,他们之间相互独立且不需要互相协调,各自分工,管理自己的区域。Datanode被用作通用的数据块存储设备,每个DataNode要向集群中所有的Namenode注册,且周期性的向所有Namenode发送心跳和块报告,并执行来自所有Namenode的命令。
Federation架构与单组Namenode架构相比,主要是Namespace被拆分成了多个独立的部分,分别由独立的Namenode进行管理。
Block Pool(块池)
1)Block Pool允许一个命名空间在不通知其他命名空间的情况下为一个新的block创建Block ID。同时一个Namenode失效不会影响其下Datanode为其他Namenode服务。
2)每个Block Pool内部自治,也就是说各自管理各自的block,不会与其他Block Pool交流。一个Namenode挂掉了,不会影响其他NameNode。
3)当Datanode与Namenode建立联系并开始会话后自动建立Block Pool。每个block都有一个唯一的标识,这个标识我们称之为扩展块ID,在HDFS集群之间都是惟一的,为以后集群归并创造了条件。
4)Datanode中的数据结构都通过块池ID索引,即Datanode中的BlockMap,storage等都通过BPID索引。
5)某个Namenode上的NameSpace和它对应的Block Pool一起被称为NameSpace Volume。它是管理的基本单位。当一个NN/NS被删除后,其所有Datanode上对应的Block Pool也会被删除。当集群升级时,每个NameSpace Volume作为一个基本单元进行升级。
ClusterID
增加一个新的ClusterID来标识在集群中所有的节点。当一个Namenode被格式化的时候,这个标识被指定或自动生成,这个ID会用于格式化集群中的其它Namenode。
2.3.2.3 Federation主要优点
2.3.2.3.1 Namespace的可扩展性
HDFS的水平扩展,但是命名空间不能扩展,通过在集群中增加Namenode来扩展Namespace,以达到大规模部署或者解决有很多小文件的情况。
2.3.2.3.2 Performance(性能)
在之前的框架中,单个Namenode文件系统的吞吐量是有限制的,增加更多的Namenode能增大文件系统读写操作的吞吐量。
2.3.2.3.3 Isolation(隔离)
一个单一的Namenode不能对多用户环境进行隔离,一个实验性的应用程序会加大Namenode的负载,减慢关键的生产应用程序,在多个Namenode情况下,不同类型的程序和用户可以通过不同的Namespace来进行隔离。
2.3.2.4 Federation 主要缺点
1)交叉访问问题
由于Namespace被拆分成多个,且互相独立,一个文件路径只允许存在一个Namespace中。如果应用程序要访问多个文件路径,那么不可避免的会产生交叉访问Namespace的情况。比如MR、Spark任务,都会存在此类问题。
2)管理性问题
启用Federation后,HDFS很多管理命令都会失效,比如“hdfs dfsadmin、hdfs fsck”等,除此之外,“hdfs dfs cp/mv”命令同样失效,如果要在不同Namespace间拷贝或移动数据,需要使用distcp命令,指定绝对路径。
2.3.2.5 Federation局限性
在解决NameNode扩展能力方面,社区虽然提供了Federation,但这个方案有很强的局限性:
1)HDFS路径Scheme需要变为ViewFs,ViewFs路径和其他Scheme路径互不兼容,比如DistributedFileSystem无法处理ViewFs为Scheme的路径,也就是说如果启用,则需要将Hive meta、ETL脚本、MR/Spark作业中的所有HDFS路径均的scheme改为viewfs。
2)如果将fs.defaultFS的配置从hdfs://ns1/变为viewfs://ns/,将导致旧代码异常,通过脚本对用户上万个源码文件的分析,常用的HDFS路径风格多样,包括hdfs:///user、hdfs://ns1/user、/user等,如果fs.defaultFS有所更改,hdfs:///user将会由于缺失nameservice变为非法HDFS路径。
3)ViewFs路径的挂载方式与Linux有所区别:
如果一个路径声明了挂载,那么其同级目录都需要进行挂载,比如/user/path_one挂载到了hdfs://ns1/user/path_one上,那么/user/path_two也需要在配置中声明其挂载到哪个具体的路径上。
如果一个路径声明了挂载,那么其子路径不能再声明挂载,比如/user/path_one挂载到了hdfs://ns1/user/path_one上,那么其子路径也自动并且必须挂载到hdfs://ns1/user/path_one上。
4)一次路径请求不能跨多个挂载点:
由于HDFS客户端原有的机制,一个DFSClient只对应一个nameservice,所以一次路径处理不能转为多个nameservice的多次RPC。
对于跨挂载点的读操作,只根据挂载配置返回假结果。
对于跨挂载点的rename(move路径)操作,会抛出异常。
5)Federation架构中,NameNode相互独立,NameNode元数据、DataNode中块文件都没有进行共享,如果要进行拆分,需要使用DistCp,将数据完整的拷贝一份,存储成本较高;数据先被读出再写入三备份的过程,也导致了拷贝效率的低效。
6)Federation是改造了客户端的解决方案,重度依赖客户端行为。方案中NameNode相互独立,对Federation没有感知。另外HDFS为Scheme的路径,不受Federation挂载点影响,也就是说如果对路径进行了namespace拆分后,如果因为代码中的路径或客户端配置没有及时更新,导致流程数据写入老数据路径,那么请求依然是合法但不符合预期的。
三、HDFS读写流程
3.1 写流程
HDFS客户端通过调用DistributedFileSystem# create来实现远程调用Namenode提供的创建文件操作,Namenode在指定的路径下创建一个空的文件并为该客户端创建一个租约(在续约期内,将只能由这一个客户端写数据至该文件),随后将这个操作记录至EditLog(编辑日志)。NameNode触发副本放置策略,返回一个有序的DataNode列表,客户端将使用这些信息,创建一个标准的Hadoop FSDataOutputStream输出流对象。
HDFS客户端开始向HdfsData-OutputStream写入数据,由于当前没有可写的Block,DFSOutputStream根据副本数向Namenode申请若干Datanode组成一条流水线来完成数据的写入。
客户端的数据以字节(byte)流的形式写入chunk(以chunk为单位计算checksum(校验和))。若干个chunk组成packet,数据以packet的形式从客户端发送到第一个Datanode,再由第一个Datanode发送数据到第二个Datanode并完成本地写入,以此类推,直到最后一个Datanode写入本地成功,可以从缓存中移除数据包(packet)。
客户端执行关闭文件后,HDFS客户端将会在缓存中的数据被发送完成后远程调用Namenode执行文件来关闭操作。
Datanode在定期的心跳上报中,以增量的信息汇报最新完成写入的Block,Namenode则会更新相应的数据块映射以及在新增Block或关闭文件时根据Block映射副本信息判断数据是否可视为完全持久化(满足最小备份因子)。
1)Client和NameNode连接创建文件元数据
2)NameNode判定元数据是否有效
3)NameNode触发副本放置策略,返回一个有序的DataNode列表
4)Client和DataNode建立Pipeline连接
5)Client将块切分成packet(64KB),并使用chunk(512B)+chucksum(4B)填充
6)Client将packet放入发送队列dataqueue中,并向第一个DataNode发送
7)第一个DataNode收到packet后本地保存并发送给第二个DataNode
8)第二个DataNode收到packet后本地保存并发送给第三个DataNode
9)这一个过程中,上游节点同时发送下一个packet
3.2 读流程
HDFS客户端远程调用Namenode,查询元数据信息,获得这个文件的数据块位置列表,返回封装DFSIntputStream的HdfsDataInputStream输入流对象。
客户端选择一台可用Datanode服务器,请求建立输入流。
Datanode向输入流中写原始数据和以packet为单位的checksum。
客户端接收数据。如遇到异常,跳转至步骤2,直到数据全部读出,而后客户端关闭输入流。当客户端读取时,可能遇到Datanode或Block异常,导致当前读取失败。正由于HDFS的多副本保证,DFSIntputStream将会切换至下一个Datanode进行读取。与HDFS写入类似,通过checksum来保证读取数据的完整性和准确性。
1)为了降低整体的带宽消耗和读取延时,HDFS会尽量让读取程序读取离它最近的副本。
2)如果在读取程序的同一个机架上有一个副本,那么就读取该副本。
3)如果一个HDFS集群跨越多个数据中心,那么客户端也将首先读本地数据中心的副本。
好了,今天的HDFS内容就分享到这里!欢约大家点赞+收藏,有疑问也欢迎大家评论留言!