大数据系列3:Hdfs的HA实现
在之前的文章:大数据系列:一文初识Hdfs ,
大数据系列2:Hdfs的读写操作 中Hdfs的组成、读写有简单的介绍。
在里面介绍Secondary NameNode
和Hdfs读写的流程。
并且在文章结尾也说了,Secondary NameNode
并不是我常说的HA,(High Availability)
。
本文承接之前的内容,对Hdfs的HA实现做个简单的介绍。
NameNode的重要性
先来看看Hdfs读写的流程图:
可以看到无论是读还是写,我们都必须和存储元数据的NameNode
进行交互。
一旦NameNode
出问题,整个集群的读写基本上就凉凉了。
所以,在设计的时候本就有做一些容错的措施。
容错的措施
如果NameNode
出了问题,整个文件Hdfs将不可用。
设想一个场景:
如果以为某种原因我们运行NameNode
的机器被删除。
此时所有Hdfs的文件都会丢失,因为我们虽然在DataNode
中存储了文件的Block
信息,但是我们无法通过这些Block
重新构建文件。
所以让NameNode
有一定抗拒错误的能力是很重要的。
而Hadoop
本身提供了两种机制。
备份元数据
第一种机制:
将Hdfs元数据中持久化状态的文件做多个备份。
Hadoop
可以进行相关的配置,把这些持久的状态备份到多个文件系统。这个过程是同步且原子的 。
一般都是配置为本份到本地磁盘,同时远程NFS 挂载
Secondary NameNode
第二种机制:
运行一个Secondary NameNode
,负责定期合并名称namespace iamge
和edit log
,以防止edit log
变得过大。
合并流程如下,详细过程之前有介绍过,此处不累赘:
存在问题
虽然有两个方式让Hdfs有一定的容错能力,但是还是存在一些问题。
Secondary NameNode
拥有一个合并的namespace iamge
像的副本,在NameNode
失败时可以使用该iamge
。
但是Secondary NameNode
中的状态是滞后于NameNode
的,如果NameNode
是宕机的情况下,数据丢失必不可少。
此时通常的做法是将NFS上的NameNode
元数据文件复制到Secondary NameNode
,并将其作为新的NameNode
运行。
因为NFS中的与
NameNode
的元数据是同步的,所以可以通过NFS中的数据恢复。
在多个文件系统上复制NameNode
元数据和使用Secondary NameNode
创建checkpoints
的组合可以防止数据丢失。
然而这种组合的机制并不能提供Hdfs
的高可用性。
主要原因是这种方式恢复太耗时间了。
NameNode
是唯一的存储了文件与Blcok
映射关系的元数据存储库
,一旦它罢工,所有的Client
的作业包括Mapreduce
、读、写、列出文件无法进行。
在新的NameNode
上线之前,整个集群处于停止服务的状态
如果NameNode
要重新上线服务需要经过以下下的步骤:
- 加载命名空间到内存
- 重放
edit log
- 从
DataNode
接收足够的Block
报告并离开安全模式。
在具有许多文件和Block
的大型集群上,NameNode
的冷启动所需的时间可能是30分钟或更长。
这么拉垮可不太能接受,无论是日常运维还是计划停机之类的操作。
所以,归根结底就是NameNode
仍然是存在单点故障(SPOF,Single Point Of Failure
)
下面就看看怎么解决这个问题。
HDFS high availability (HA)
一般情况下会有两个场景和HA息息相关。
- 在出现计划外事件(如服务器宕机)的情况下,在重新启动
NameNode
之前,集群将不可用。 - 有计划的维护事件,例如
NameNode
机器上的软件或硬件升级,将导致集群停机。
为了解决上述的问题,Hadoop
提供了HDFS high availability (HA)
的支持。
通过active-standby
的方式配置两个NameNode
(3.0后可以支持超过两个),在Active/Passive
动配置中使用热备份。
在机器崩溃的情况下,实现快速故障转移到新的NameNode
,
或者出于计划维护的目的,允许管理员发起优雅的故障转移。
为了实现这个目标,在架构层面需要做一些改变:
NameNode
间必须要通过一些高可用的共享内存共享edit log
,一旦standby NameNode
接管工作的时候,它通过读取共享edit log
直至结尾以保持它的状态与active NameNode
一致,然后继续读取由activeNameNode
写入的新条目。DataNodes
必须向两个NameNode
发送Block
报告,因为NameNode
映射关系是存储在DataNode
的内存而不是磁盘。- 客户端必须使用一种机制来处理
NameNode
失效问题,让用户对于NameNode
的切换是透明的。(就是不需要用户自己去切换NameNode
) standby NameNode
应该承担之前Secondary NameNode
的角色,定期为active NameNode
的命名空间创建检查点。
在典型的HA集群中,会配置两个或更多的NameNodes
,但是在任何时候,都只能有一个NameNode
处于活动状态,其他NameNode
处于备用状态。
Active状态的NameNode
负责集群中的所有Client的操作,
Standby状态的NameNode
仅需要保存足够的状态的即可。
有两种实现方式
Quorum Journal Manager (QJM)
QJM
是一个专用的HDFS
实现,设计的唯一目的是提供高度可用的编辑日志,并且是大多数HDFS
安装的推荐选择。
QJM
作为一组journalnodes(JNs)
独立守护进程运行,每次编辑都必须写入大部分JNs
节点。
为了使备节点与主节点保持状态同步,两个节点都与JNs
进程通信。
Active NameNode
执行任何关于名称空间修改时,它会持久地记录修改记录到大部分的JNs
。
通常,有三个journal节点,因此系统可以容忍其中一个节点的丢失。
尽管这种方式类似ZooKeeper工作方式,但是必须说明的是QJM实现没有使用ZooKeeper
同时还要注意,HDFS的HA确实使用了ZooKeeper来选择activeNameNode
,后续会介绍
standby NameNode
能够从JNs
中读取edits
,并不断监视它们对edit log
的修改。
当standby
NameNode````看到这edits时,它将它们应用到自己的名称空间。
standby NameNode
必须持有集群中NameNode
位置的最新信息。
所以,DataNodes
需要配置所有NameNode
的位置,并向所有NameNode
发送Block
位置信息和心跳。
因为standby NameNode
在内存中有最新的状态:最新的edit log
和最新的NameNode
映射
所以Active NameNode
发生故障时,standby NameNode
可以很快接管(在几十秒内)。
实际观察到的故障转移时间要长一些(大约一分钟左右),因为系统需要保守地判断active
NameNode
是否发生了故障。
NFS
与QJM
方共享不同,edit log
记录到JNs不同,
此处的共享内存一般使用过一个支持NFS
(Network File System
:网络文件系统)的NAS
(Network Attached Storage
:网络附属存储)创建应共享文件,通常会挂载在每隔一个NameNode
的机器上,NameNode
可以对其进行读、写操作。
但是,目前只支持一个共享edit
目录。
因此,系统的可用性受到共享edit
目录的可用性的限制,
为了消除所有单点故障,共享编辑目录需要有冗余。
具体来说,就是到存储的多条网络路径,以及存储本身的冗余(磁盘、网络和电源)。
所以,建议使用高质量的专用NAS设备作为共享存储服务器,而不是简单的Linux
服务器。
故障转移和规避
在HA
集群中,standby NameNode
还执行名称空间状态检查点操作,
因此在HA集群中没有必要运行 Secondary NameNode, CheckpointNode, or BackupNode
。
同时无论是QJM
还是NFS
中,
在同一时刻只有一个NameNode
处于Active
是至关重要的。
否则,名称空间状态将很快在两者之间出现分歧(脑裂),可能导致数据丢失或其他不正确的结果。
为了避免这个情况,同一时间只允许一个NameNode
对 JournalNodes 或 NFS的共享内存写入。
在故障转移期间,即将成为Active状态的NameNode
将接管向共享eidt文件写入数据的角色,
这将有效地阻止其他NameNode
继续处于Active状态,从而允许新的Active状态安全地进行故障转移。
但是要如何实现呢?
故障转移
从active NameNode
到standby
NameNode```` 的过渡由系统中称为故障转移控制器(failover controller)的新实体进行管理。
可以理解为
active NameNode
挂了standby NameNode
上位的过程
有各种各样的故障转移控制器,但是默认的实现是使用ZooKeeper来确保只有一个NameNode
是active
的。
每个NameNode
运行一个轻量级的故障转移控制器进程,
它的任务是监视它的NameNode
是否出现故障(使用一个简单的心跳机制),
并在NameNode
出现故障时触发故障转移。
故障转移也可以由管理员手动启动,
例如,在例行维护的情况下。
这被称为优雅的故障转移( graceful failover
),因为故障转移控制器为两个NameNode
安排了有序的角色转换。
但是,
在意料之外的故障转移的情况下,不可能确保失败的NameNode
已经停止运行。
例如,网络或网络分区速度较慢的情况下,
即使以前activeNameNode
仍然在运行
也可能会触发故障转移。
为了避免这种情况,HA实现了确保以前active NameNode
不会造成任不良影响的方法称为规避(fencing
)。
规避
JNs一次只允许一个NameNode
写入编辑日志;
但是,以前活跃的NameNode
仍然可能向客户端提供过时的读请求,
因此可以设置一个SSH 规避命令来杀死NameNode
进程。
对于使用NFS文件的共享edit log
,因为很难控制一次只允许一个NameNode
写入,
需要更强有力地规避方法,一般可以选择如下方法:
- 撤销
NameNode
对共享存储目录的访问(通常使用特定于供应商的NFS命令) - 通过远程管理命令屏蔽相应网络端口(port)
- 做绝一点可以通过一个叫做“一枪爆头”方式进行规避(
STONITH,short the other node in the head
),通过一个特定的供电单元对相应的NameNode
进行断电操作。
这就是推荐使用QJM的主要原因。
有了上述只是储备,我们很容易就可以把这个架构图画出来,以QJM为例
那么转移过程大致如下:
客户端故障转移
对于客户端而言,我们要让用户对于故障转移是无感的。
可以通过配置文件实现一个故障转移
在配置文件中通过配置几个参数实现故障转移:
dfs.nameservices
:Hdfs逻辑名称
<property>
<name>dfs.nameservices</name>
<value>mycluster</value>
</property>
dfs.ha.NameNodes.[nameservice ID]
:nameservice中每个NameNode
的唯一标识符
<property>
<name>dfs.ha.NameNodes.mycluster</name>
<value>nn1,nn2, nn3</value>
</property>
dfs.NameNode.rpc-address.[nameservice ID].[name node ID]
:每个NameNode
监听的完全限定RPC地址
<property>
<name>dfs.NameNode.rpc-address.mycluster.nn1</name>
<value>machine1.example.com:8020</value>
</property>
<property>
<name>dfs.NameNode.rpc-address.mycluster.nn2</name>
<value>machine2.example.com:8020</value>
</property>
<property>
<name>dfs.NameNode.rpc-address.mycluster.nn3</name>
<value>machine3.example.com:8020</value>
</property>
更多的配置可以查看
NameNode
HA With QJM
NameNode
HA With NFS
关于HA暂时介绍到这里,后续有空再进行详细的介绍。
之后会写一下关于Yarn的文章,感兴趣可以关注【兔八哥杂谈】