学习目标:高可用、单机元数据内存受限、源码设计
- HDFS是如何实现有状态的高可用架构——HA解决单节点故障
- HDFS是如何从架构上解决单机内存受限问题——元数据内存受限问题
- HDFS能支撑起亿级流量的核心源码的设计
一、HDFS架构演进
1、Hadoop的三个版本:对应的三个HDFS版本
Hadoop1、2、3
HDFS 1、2、3
Hadoop1重点解决的两上问题:
- 海量数据如何存储
- 海量数据如何进行计算
2、HDFS1.0的架构:
HDFS1是一个主从式架构,主节点只有一个NameNode,从节点有多个叫DataNode
NameNode:
1、管理元数据信息(文件目录树):文件与Block块,Block块与DataNode主机的关系
2、NameNode为了快速响应用户的操作请求,所以所元数据加载到了内存里面
DataNode:
1、存储数据,把上传的数据划分成为固定大小的文件块(Hadoop1默认是64MB)
2、为了保证数据的安全,每个文件块默认都有三个副本
3、HDFS2:解决上一架构的缺陷:单节点故障、内存受限
3.1 单点故障:主备自动切换
解决方案图:ANN与SNN自动切换图:(也是在HDFS2解决的问题)
同一时间只有一个ANN在服务,与SNN进行切换时需要实现自动切换,即ZooKeeper,在每个NN上安装一个ZKFC来监控每个NN的健康状况,下图中的绿色方块即ZK实现的一个锁机制,即只有ANN可以进,如果ANN出问题,则将此锁给SNN,并将SNN转换为ANN,实现了自动切换(在实际生产中,有些企业是不用JournalNode,而是直接改源码使用ZooKeeper来代替JournalNode进行存储解决单点故障问题)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CksXVn9m-1608650765428)(/Users/ryan/大数据/大数据架构师/img/image-20201222215138780.png)]
JournalNode是在MR2也就是Yarn中新加的,journalNode的作用是存放EditLog,同步元数据
在MR1中editlog是和fsimage存放在一起的然后SecondNamenode做定期合并,Yarn在这上面就不用SecondNamanode了
配置文件是;hdfs-site.xml文件负责
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ruOjbAck-1608650765431)(/Users/ryan/大数据/大数据架构师/img/1353331-20191008160147206-1234947374.png)]
Active Namenode与StandBy Namenode之间的就是JournalNode,作用相当于NFS共享文件系统.Active Namenode往里写editlog数据,StandBy再从里面读取数据进行同步.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uB6lYhh0-1608650765434)(/Users/ryan/大数据/大数据架构师/img/1353331-20191008155625658-149426410.png)]
两个NameNode为了数据同步,会通过一组称作JournalNodes的独立进程进行相互通信。当active状态的NameNode的命名空间有任何修改时,会告知大部分的JournalNodes进程。
standby状态的NameNode有能力读取JNs中的变更信息,并且一直监控edit log的变化,把变化应用于自己的命名空间。standby可以确保在集群出错时,命名空间状态已经完全同步了。
一般200个节点以内,JournalNode3个就够了,如果是2000个以内5个即可。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UlLoSqMe-1608650765436)(/Users/ryan/大数据/大数据架构师/img/1353331-20191008160409794-1622483165.png)]
如何做到两个NameNode?如何保证两个之间元数据一致性问题?
-
共享目录方案:没人在用,万一共享目录出问题,数据就出问题了
-
QJM(Quorum Journal Manager):当你写一条数据到一个JournalNode,它就会自动同步到其它另外两个JournalNode上
3.1.1 QJM原理:
奇数点结点(集群能否正常提供服务的依据:只有超过二分之一的节点是存活的,集群才是健康的)组成。每个JournalNode对外有一个简易的RPC接口,以供NameNode读写EditLog到JN本地磁盘。当写EditLog时,NameNode会同时向所有JournalNode并行写文件,只要有N/2+1结点写成功则认为此次写操作成功,遵循Paxos协议。其内部实现框架如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ULokzCh8-1608650765439)(/Users/ryan/大数据/大数据架构师/img/1508123397521_3709_1508123422520.png)]
-
FSEditLog类:管理EditLog文件与对大量log记录命名空间的个性,所有EditLog操作的入口
-
JournalSet: 维护Journal的集合,集成本地磁盘和JournalNode集群上EditLog的相关操作
-
FileJournalManager: 实现本地磁盘上 EditLog 操作
-
QuorumJournalManager: 实现JournalNode 集群EditLog操作
-
AsyncLoggerSet: 实现JournalNode 集群 EditLog 的写操作集合
-
AsyncLogger:发起RPC请求到JN,执行具体的日志同步功能
-
JournalNodeRpcServer:运行在 JournalNode 节点进程中的 RPC 服务,接收 NameNode 端的 AsyncLogger 的 RPC 请求。
-
JournalNodeHttpServer:运行在 JournalNode 节点进程中的 Http 服务,用于接收处于 Standby 状态的 NameNode 和其它 JournalNode 的同步 EditLog 文件流的请求。
-
3.1.2 QJM写过程分析(超级重要:这个是解决可靠性也服务性能的关键——分段加锁与双缓冲方案)
上面提到EditLog,NameNode会把EditLog同时写到本地和JournalNode。写本地由配置中参数dfs.namenode.name.dir控制,写JN由参数dfs.namenode.shared.edits.dir控制,在写EditLog时会由两个不同的输出流来控制日志的写过程,分别为:EditLogFileOutputStream(本地输出流)和QuorumOutputStream(JN输出流)。写EditLog也不是直接写到磁盘中,为保证高吞吐,NameNode会分别为EditLogFileOutputStream和QuorumOutputStream定义两个同等大小的Buffer,大小大概是512KB,一个写Buffer(buffCurrent),一个同步Buffer(buffReady),这样可以一边写一边同步,所以EditLog是一个异步写过程,同时也是一个批量同步的过程,避免每写一笔就同步一次日志。
这个是怎么实现边写边同步的呢,这中间其实是有一个缓冲区交换的过程,即bufferCurrent和buffReady在达到条件时会触发交换,如bufferCurrent在达到阈值同时bufferReady的数据又同步完时,bufferReady数据会清空,同时会将bufferCurrent指针指向bufferReady以满足继续写,另外会将bufferReady指针指向bufferCurrent以提供继续同步EditLog。上面过程用流程图就是表示如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bFBUUdaL-1608650765442)(/Users/ryan/大数据/大数据架构师/img/1508123437957_9118_1508123462816.png)]
3.1.3 两个问题(数据可靠性与一致性如何保证):
1)、既然EditLog是异步写的,怎么保证缓存中的数据不丢呢?
这里虽然是异步,但实际所有日志都需要通过logSync同步成功后才会给client返回成功码,假设某一时刻NameNode不可用了,其内存中的数据其实是未同步成功的,所以client会认为这部分数据未写成功。
2)、EditLog怎么在多个JN上保持一致的呢?
2.1).隔离双写:
在ActiveNameNode每次同步EditLog到JN时,先要保证不会有两个NN同时向JN同步日志。这个隔离是怎么做的。这里面涉及一个很重要的概念Epoch(时期) Numbers,很多分布式系统都会用到。Epoch有如下几个特性:
- 当NN成为活动结点时,其会被赋予一个EpochNumber
- 每个EpochNumber是惟一的,不会有相同的EpochNumber出现
- EpochNumber有严格顺序保证,每次NN切换后其EpochNumber都会自增1,后面生成的EpochNumber都会大于前面的EpochNumber
QJM是怎么保证上面特性的呢,主要有以下几点:具体步骤
-
第一步,在对EditLog作任何修改前,QuorumJournalManager(NameNode上)必须被赋予一个EpochNumber
-
第二步, QJM把自己的EpochNumber通过newEpoch(N)的方式发送给所有JN结点
-
第三步, 当JN收到newEpoch请求后,会把QJM的EpochNumber保存到一个lastPromisedEpoch变量中并持久化到本地磁盘
-
第四步, ANN同步日志到JN的任何RPC请求(如logEdits(),startLogSegment()等),都必须包含ANN的EpochNumber
-
第五步,JN在收到RPC请求后,会将之与lastPromisedEpoch对比,如果请求的EpochNumber小于lastPromisedEpoch,将会拒绝同步请求,反之,会接受同步请求并将请求的EpochNumber保存在lastPromisedEpoch
-
这样就能保证主备NN发生切换时,就算同时向JN同步日志,也能保证日志不会写乱,因为发生切换后,原ANN的EpochNumber肯定是小于新ANN的EpochNumber,所以原ANN向JN的发起的所有同步请求都会拒绝,实现隔离功能,防止了脑裂。
2.2). 恢复in-process日志
-
-
为什么要这步呢,如果在写过程中写失败了,可能各个JN上的EditLog的长度都不一样,需要在开始写之前将不一致的部分恢复。恢复机制如下:
1 ANN先向所有JN发送getJournalState请求;
2 JN会向ANN返回一个Epoch(lastPromisedEpoch);
3 ANN收到大多数JN的Epoch后,选择最大的一个并加1作为当前新的Epoch,然后向JN发送新的newEpoch请求,把新的Epoch下发给JN;
4 JN收到新的Epoch后,和lastPromisedEpoch对比,若更大则更新到本地并返回给ANN自己本地一个最新EditLogSegment起始事务Id,若小则返回NN错误;
5 ANN收到多数JN成功响应后认为Epoch生成成功,开始准备日志恢复;
6 ANN会选择一个最大的EditLogSegment事务ID作为恢复依据,然后向JN发送prepareRecovery; RPC请求,对应Paxos协议2p阶段的Phase1a,若多数JN响应prepareRecovery成功,则可认为Phase1a阶段成功;
7 ANN选择进行同步的数据源,向JN发送acceptRecovery RPC请求,并将数据源作为参数传给JN。
8 JN收到acceptRecovery请求后,会从JournalNodeHttpServer下载EditLogSegment并替换到本地保存的EditLogSegment,对应Paxos协议2p阶段的Phase1b,完成后返回ANN请求成功状态。
9 ANN收到多数JN的响应成功请求后,向JN发送finalizeLogSegment请求,表示数据恢复完成,这样之后所有JN上的日志就能保持一致。
数据恢复后,ANN上会将本地处于in-process状态的日志更名为finalized状态的日志,形式如edits*[start-txid]*[stop-txid]。2.3).日志同步
这个步骤上面有介绍到关于日志从ANN同步到JN的过程,具体如下:
1 执行logSync过程,将ANN上的日志数据放到缓存队列中
2 将缓存中数据同步到JN,JN有相应线程来处理logEdits请求
3 JN收到数据后,先确认EpochNumber是否合法,再验证日志事务ID是否正常,将日志刷到磁盘,返回ANN成功码
4 ANN收到JN成功请求后返回client写成功标识,若失败则抛出异常通过上面一些步骤,日志能保证成功同步到JN,同时保证JN日志的一致性,进而备NN上同步日志时也能保证数据是完整和一致的。
2.4). StandbyNameNode的工作机制
-
这个读过程是面向备NN(StandbyNN)的,SNN定期检查JournalNode上EditLog的变化,然后将EditLog拉回本地。SNN上有一个线程StandbyCheckpointer,会定期将SNN上FSImage和EditLog合并,并将合并完的FSImage文件传回主NN(ANN)上,就是所说的Checkpointing过程。下面我们来看下Checkpointing是怎么进行的。
在2.x版本中,已经将原来的由SecondaryNameNode主导的Checkpointing替换成由SNN主导的Checkpointing。下面是一个CheckPoint的流向图:
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UpKKvWjp-1608650765445)(/Users/ryan/大数据/大数据架构师/img/1508123569245_7882_1508123594226.png)]
【 图.Checkpointing流向图 】
总的来说,就是在SNN上先检查前置条件,前置条件包括两个方面:距离上次Checkpointing的时间间隔和EditLog中事务条数限制。前置条件任何一个满足都会触发Checkpointing,然后SNN会将最新的NameSpace数据即SNN内存中当前状态的元数据保存到一个临时的fsimage文件( fsimage.ckpt)然后比对从JN上拉到的最新EditLog的事务ID,将fsimage.ckpt_中没有,EditLog中有的所有元数据修改记录合并一起并重命名成新的fsimage文件,同时生成一个md5文件。将最新的fsimage再通过HTTP请求传回ANN。通过定期合并fsimage有什么好处呢,主要有以下几个方面:
-
可以避免EditLog越来越大,合并成新fsimage后可以将老的EditLog删除
-
可以避免主NN(ANN)压力过大,合并是在SNN上进行的
-
可以保证fsimage保存的是一份最新的元数据,故障恢复时避免数据丢失
3.2 内存受限:HDFS联邦
HDFS为了快速响应客户请求,它会把元数据状态存储在内存中,以实现快速响应。
一般用到联邦是在节点超过1000台以上,即将NN设置为多组,类似于C盘,D盘,E盘方案,可以通过联邦实现多组ANN同时使用,当客户进行访问时就可以指定元数据存储的位置,和DN没有任何关系。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gu98kVTY-1608650765448)(/Users/ryan/大数据/大数据架构师/img/image-20201222215431315.png)]
联邦即引入了NameSpace,原先单NameNode只有Block块组成,使用联邦后由namespace(命名空间)和Block Storage(块的存储)两层,由目录、文件、块组成。采用联邦后解决了内存受限问题,但还是存在单节点故障,所以每组下面都会有一个ANN和SNN组成。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yp0B67CR-1608650765451)(/Users/ryan/大数据/大数据架构师/img/70.png)]
当用户存储文件时需要存到某组NN中时,采用"文件名hash"的方法,这些文件可能会被放到不同的namespace中,为了方便管理多个命名空间,HDFS Federation采用了经典的Client Side Mount Table
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W7G1UVcy-1608650765454)(/Users/ryan/大数据/大数据架构师/img/image-20201222225236409.png)]
4、HDFS3架构:支持多NN
HA方案支持多个Namenode,引入纠删码技术。
二、亿级流量支撑——分段加锁与双缓冲方案
思考:
NN管理着元数据,用户所有的操作请求都要操作NN,大一点的平台一天需要运行几十万,上百万的任务。一个任务就会有很多的请求,这些所有的请求都会在NN这儿(更新目录树),对于NN来说这就是亿级的流量,NN是如何支撑亿级流量的呢?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hJdZGLU0-1608650765458)(/Users/ryan/大数据/大数据架构师/img/image-20201222231538451.png)]
为了保证数据的安全,必须将内存中的数据写到磁盘上,记录日志,为了解决磁盘IO瓶颈,引入:分段加锁和双缓冲的方案
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9vBdpxE8-1608650765461)(/Users/ryan/大数据/大数据架构师/img/image-20201222231816464.png)]
使用内存缓冲:CurrentBuffer响应客户并发请求,到一定闽值时进行内存交换,交给SyncBuffer进行溢写磁盘,与Kafka的原理一样。