目录
2.3.1.3.4 zkfc(FailoverController)
2.3.2 压力过大,内存受限(联帮机制:Federation)
一、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内容就分享到这里!欢约大家点赞+收藏,有疑问也欢迎大家评论留言!