版本记录:
在Hadoop 1.x 中,Namenode是集群的单点故障,一旦Namenode出现故障,整个集群将不可用,重启或者开启一个新的Namenode才能够从中恢复。值得一提的是,Secondary Namenode并没有提供故障转移的能力。集群的可用性受到影响表现在:
Hadoop HA(High Available)通过同时配置两个处于Active/Passive模式的Namenode来解决上述问题,分别叫Active Namenode和Standby Namenode. Standby Namenode作为热备份,从而允许在机器发生故障时能够快速进行故障转移,同时在日常维护的时候使用优雅的方式进行Namenode切换。Namenode只能配置一主一备,不能多于两个Namenode。
主Namenode处理所有的操作请求(读写),而Standby只是作为slave,维护尽可能同步的状态,使得故障时能够快速切换到Standby。为了使Standby Namenode与Active Namenode数据保持同步,两个Namenode都与一组Journal Node进行通信。当主Namenode进行任务的namespace操作时,都会确保持久会修改日志到Journal Node节点中的大部分。Standby Namenode持续监控这些edit,当监测到变化时,将这些修改应用到自己的namespace。
当进行故障转移时,Standby在成为Active Namenode之前,会确保自己已经读取了Journal Node中的所有edit日志,从而保持数据状态与故障发生前一致。
为了确保故障转移能够快速完成,Standby Namenode需要维护最新的Block位置信息,即每个Block副本存放在集群中的哪些节点上。为了达到这一点,Datanode同时配置主备两个Namenode,并同时发送Block报告和心跳到两台Namenode。
确保任何时刻只有一个Namenode处于Active状态非常重要,否则可能出现数据丢失或者数据损坏。当两台Namenode都认为自己的Active Namenode时,会同时尝试写入数据(不会再去检测和同步数据)。为了防止这种脑裂现象,Journal Nodes只允许一个Namenode写入数据,内部通过维护epoch数来控制,从而安全地进行故障转移。
有两种方式可以进行edit log共享:
如图所示,NFS作为主备Namenode的共享存储。这种方案可能会出现脑裂(split-brain),即两个节点都认为自己是主Namenode并尝试向edit log写入数据,这可能会导致数据损坏。通过配置fencin脚本来解决这个问题,fencing脚本用于:
使用这种方案,管理员就可以手工触发Namenode切换,然后进行升级维护。但这种方式存在以下问题:
因此需要另一种方式来处理这些问题:
QJM(Quorum Journal Manager)是Hadoop专门为Namenode共享存储开发的组件。其集群运行一组Journal Node,每个Journal 节点暴露一个简单的RPC接口,允许Namenode读取和写入数据,数据存放在Journal节点的本地磁盘。当Namenode写入edit log时,它向集群的所有Journal Node发送写入请求,当多数节点回复确认成功写入之后,edit log就认为是成功写入。例如有3个Journal Node,Namenode如果收到来自2个节点的确认消息,则认为写入成功。
而在故障自动转移的处理上,引入了监控Namenode状态的ZookeeperFailController(ZKFC)。ZKFC一般运行在Namenode的宿主机器上,与Zookeeper集群协作完成故障的自动转移。整个集群架构图如下:
Namenode使用QJM 客户端提供的RPC接口与Namenode进行交互。写入edit log时采用基于仲裁的方式,即数据必须写入JournalNode集群的大部分节点。
在Journal Node节点上(服务端)
服务端Journal运行轻量级的守护进程,暴露RPC接口供客户端调用。实际的edit log数据保存在Journal Node本地磁盘,该路径在配置中使用dfs.journalnode.edits.dir
属性指定。
Journal Node通过epoch数来解决脑裂的问题,称为JournalNode fencing。具体工作原理如下:
1)当Namenode变成Active状态时,被分配一个整型的epoch数,这个epoch数是独一无二的,并且比之前所有Namenode持有的epoch number都高。
2)当Namenode向Journal Node发送消息的时候,同时也带上了epoch。当Journal Node收到消息时,将收到的epoch数与存储在本地的promised epoch比较,如果收到的epoch比自己的大,则使用收到的epoch更新自己本地的epoch数。如果收到的比本地的epoch小,则拒绝请求。
3)edit log必须写入大部分节点才算成功,也就是其epoch要比大多数节点的epoch高。
这种方式解决了NFS方式的3个问题:
前面提到,为了支持故障转移,Hadoop引入两个新的组件:Zookeeper Quorum和ZKFailoverController process(简称ZKFC)。
Zookeeper的任务包括:
在每个Namenode守护进程的机器上,同时也会运行一个ZKFC,用于完成以下任务:
如果ZKFC所在机器的Namenode健康状态良好,并且用于选举的znode锁未被其他节点持有,则ZKFC会尝试获取锁,成功获取这个排它锁就代表获得选举,获得选举之后负责故障转移,如果有必要,会fencing掉之前的namenode使其不可用,然后将自己的namenode切换为Active状态。
为了允许HA集群,需要以下资源:
1)Namenode机器:运行Active Namenode和Standby Namenode的机器配置应保持一样,也与不使用HA情况下的配置一样。
2)JournalNode机器:运行JournalNode的机器,这些守护进程比较轻量级,所以可以将其部署在Namenode或者YARN ResourceManager。至少需要部署3个Journalnode节点,以便容忍一个节点故障。通常配置成奇数,例如总数为N,则可以容忍(N-1)/2
台机器发生故障后集群仍然可以正常工作。
需要注意的是,Standby Namenode同时完成了原来Secondary namenode的checkpoint功能,因此不需要独立再部署Secondary namenode。
Nameservices: 服务的逻辑名称
<property>
<name>dfs.nameservicesname>
<value>myclustervalue>
property>
Namenode配置:
dfs.ha.namenodes.[nameservices]: nameserviecs对应的namenode:
<property>
<name>dfs.ha.namenodes.myclustername>
<value>nn1,nn2value>
property>
Namenode RPC地址:
<property>
<name>dfs.namenode.rpc-address.mycluster.nn1name>
<value>machine1.example.com:8020value>
property>
<property>
<name>dfs.namenode.rpc-address.mycluster.nn2name>
<value>machine2.example.com:8020value>
property>
Namenode HTTP Server配置:
<property>
<name>dfs.namenode.http-address.mycluster.nn1name>
<value>machine1.example.com:50070value>
property>
<property>
<name>dfs.namenode.http-address.mycluster.nn2name>
<value>machine2.example.com:50070value>
property>
edit log保存目录,也就是Journal Node集群地址,分号隔开:
<property>
<name>dfs.namenode.shared.edits.dirname>
<value>qjournal://node1.example.com:8485;node2.example.com:8485;node3.example.com:8485/myclustervalue>
property>
客户端故障转移代理类,目前只提供了一种实现:
<property>
<name>dfs.client.failover.proxy.provider.myclustername>
<value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvidervalue>
property>
edit日志保存路径:
<property>
<name>dfs.journalnode.edits.dirname>
<value>/path/to/journal/node/local/datavalue>
property>
fencing方法配置:
<property>
<name>dfs.ha.fencing.methodsname>
<value>sshfencevalue>
property>
<property>
<name>dfs.ha.fencing.ssh.private-key-filesname>
<value>/home/exampleuser/.ssh/id_rsavalue>
property>
虽然在使用QJM作为共享存储时,不会出现同时写入的脑裂现象。但是旧的namenode依然可以接受读请求,这可能会导致数据过时,直到原有namenode尝试写入journal node时才关机。因此也推荐配置一种合适的fencing方法。
配置完成之后,使用如下命令启动JQM集群:
hadoop-daemon.sh start journalnode
配置并启动Zookeeper集群,与常规的而配置方式完成一样,主要包括数据保存位置、节点id、时间配置等,在zoo.cfg中配置。这里不列出详细步骤。使用之前,需要格式化zk的文件:
hdfs zkfc -formatZK
格式化Namenode:
hdfs namenode -format
启动两个namenode:
//master
hadoop-daemon.sh start namenode27
//备用namenode上
hdfs namenode -bootstrapStandby
其他组件的启动方式与常规方式一样。
(完)