1、概述
Hadoop2.X中的HDFS(Vsersion2.0)相比于Hadoop1.X增加了两个重要功能,HA和Federation。HA解决了Hadoop1.X Namenode中一直存在的单点故障问题,HA策略通过热备的方式为主NameNode提供一个备用者,并且这个备用者的状态一直和主Namenode的元数据保持一致,一旦主NameNode挂了,备用NameNode可以立马转换变换为主NameNode,从而提供不间断的服务。另外,Federation特性,主要是允许一个 HDFS 集群中存在多个 NameNode 同时对外提供服务,这些 NameNode 分管一部分目录(水平切分),彼此之间相互隔离,但共享底层的 DataNode 存储资源。本文档主要是总结我自己个人利用为QJM(Quorum Journal Manager)为公司测试集群(hadoop2.2.0)部署HA策略的过程以及自己在部署过程中遇到的一些问题。
2、HDFSHA基本架构
先来看个手动挡切换的HA架构图:
在一个典型的 HDFS HA 场景中,通常由两个NameNode 组成,一个处于Active状态,另一个处于Standby状态。Active NameNode 对外提供服务,比如处理来自客户端的 RPC 请求,而 Standby NameNode 则不对外提供服务,仅同步 Active NameNode的状态,以便能够在它失败时快速进行切换。
为了能够实时同步 Active 和 Standby 两个 NameNode 的元数据信息(实际上editlog),需提供一个共享存储系统,可以是 NFSQJ(Quorum Journal Manager)或者 Bookeeper,Active NameNode 将数据写入共享存储系统,我们可以在Active NameNode的50070端口上看到相应的NameNode Journal Status信息:
同时Standby NameNode监听该系统(QJM管理下的Journalnode进程对应的存储路径),一旦发现有新数据写入,则读取这些数据,并加载到自己内存中,以保证自己内存状态与 Active NameNode 保持基本一致,那么在紧急情况下 standby NameNode便可快速切为Active NameNode。另外,在Hadoop1.X中的Secondary NameNode或者自己通过nfs热备的NameNode信息在Hadoop2.X中已经不再需要了,他们被Standby NameNode取代了。 在Yarn的官网中,我还看到一段关于JournalNode错误兼容性信息:
大概意思是主备NameNode 之间通过一组JournalNode 同步元数据信息(我的通俗理解就是QJM类似一个数据池,池里边装着多个JournalNode进程存储editlog,Active NameNode往池里边的JournalNode进程写editlog,StandBy NameNode向池里边的JournalNode取数据同步),一条数据只要成功写入多数 JournalNode 即认为写入成功。启动的JournalNode的个数必须为奇数个。如果你的HA策略中启动了N个JournalNode进程那么整个QJM最多允许(N-1)/2个进程死掉,这样才能保证editLog成功完整地被写入。比如 3个 JournalNode 时,最多允许 1 个 JournalNode挂掉,5 个 JournalNode 时,最多允许 2 个 JournalNode 挂掉。
3、 HDFS HA部署
3.1、部署和测试环境
HDFS HA的部署和验证是在公司的测试集群中完成,其中测试集群中数据节点一共有4个主机名分别为hadoop-slave1、hadoop-slave02、hadoop-slave03、hadoop-slave04,master节点的主机名为hadoop-master。因为JournalNode和Zookeeper进程是非常轻量级的,可以其他服务共用节点。现在的部署情况是:
hadoop-master:作为Active NameNode
haoop-slave01: 作为StandBy NameNode
hadoop-slave02: 作为DataNode,并且启动一个JournalNode、启动一个Zookeeper
hadoop-slave03: 作为DataNode,并且启动一个JournalNode、启动一个Zookeeper
hadoop-slave04: 作为DataNode,并且启动一个JournalNode、启动一个Zookeeper
其他软件:
Apache Hadoop 2.2.0、JDK1.6
3.2、修改配置文件
主要配置${HADOOP_HOME}/etc/hadoop/下的hdfs-site.xml。下面是一些配置参数以及说明:
(1) dfs.nameservices
HDFS的命名服务逻辑名称,可以自己定义。在已经配置HA策略的HDFS会用到这个逻辑名称,同时该名称也会被基于HDFS的系统用,例如HBASE等。另外,如果需要启动HDFS Federation的话,可以通过该参数指定多个服务逻辑名称,用“,”作为分隔符。
我的配置如下:
<property>
<name>dfs.nameservices</name>
<value>mycluster</value>
<description>Logical name forthis new nameservice</description>
</property>
(2) dfs.ha.namenodes.[$nameserviceID]
命名服务下面包含的NameNode列表,可为每个NameNode 指定一个自定义的 ID 名称,比如命名服务 testCluster 下有两个 NameNode,分别命名为 nn1 和 nn2(到目前为止一个命名服务下最多包含2个NameNode),我的配置如下:
<property> <name>dfs.ha.namenodes.testCluster</name> <value>nn1,nn2</value> <description>Unique identifiers for each NameNode in the nameservice </description></property>
(3) dfs.namenode.rpc-address.[$nameserviceID].[$name node ID]
这个参数很容易理解,主要是为每个NameNode设置RPC地址,我的配置如下:
<property>
<name>dfs.namenode.rpc-address.testCluster.nn1</name>
<value>hadoop-master:8020</value>
</property>
<property>
<name>dfs.namenode.rpc-address.testCluster.nn2</name>
<value>hadoop-slave01:8020</value>
</property>
(4) dfs.namenode.http-address.[$nameserviceID].[$name node ID]
这个参数主要是为NameNode设置对外的HTTP地址, 通过此配置的指定你可以执行在浏览器中管理HDFS界面等操作。我的配置如下:
<property>
<name>dfs.namenode.http-address.testCluster.nn1</name>
<value>hadoop-master:50070</value>
</property>
<property>
<name>dfs.namenode.http-address.testCluster.nn2</name>
<value>hadoop-slave01:50070</value>
</property>
(5) dfs.namenode.shared.edits.dir
设置一组JournalNode的URL地址,ActiveNameNode会将Edit Log写入这些JournalNode所配置的本地目录(可以用nfs等共享文件系统,由参数dfs.journalnode.edits.dir控制)中,而StandByNameNode通过DataNode的心跳通知去读取这些Edit Log,并且作用在内存中的目录树中,其配置格式为:qjournal://host1:port1;host2:port2;host3:port3/journalId,我的配置如下:
<property>
<name>dfs.namenode.shared.edits.dir</name>
<value>qjournal://hadoop-slave02:8485;hadoop-slave03:8485;hadoop-slave04:8485/testcluster</value>
<description>journalNodeList</description>
</property>
(6) dfs.journalnode.edits.dir
这个就是刚刚提到的JournalNode所在节点上的一个目录,用于存放 editlog 和其他状态信息,该参数只能设置一个目录,你可以对磁盘做 RIAD 提高数据可靠性。
<property>
<name>dfs.journalnode.edits.dir</name>
<value>/home/hadoop/hadoop-2.2.0/journal/node/local/data</value>
</property>
(7) dfs.client.failover.proxy.provider.[$nameserviceID]
该参数设置HDFS客户端与ActiveName进行交互的JAVA实现类,HDFS客户端通过该参数来寻找到集群中的Active NameNode,此类默认实现ConfiguredFailoverProxyProvider,我的配置如下:
<property>
<name>dfs.client.failover.proxy.provider.testcluster</name>
<value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value>
</property>
(8) dfs.ha.fencing.methods
这个参数比较重要,主要用于在主备节点切换时实现隔离机制的,在官方网站中做了相当详细的配置说明,其大概意思为:主备架构解决单点故障问题时,必须要认真解决的是脑裂问题,即出现两个 master 同时对外提供服务,导致系统处于不一致状态,可能导致数据丢失等潜在问题。在 HDFS HA中,JournalNode 只允许一个 NameNode 写数据,不会出现两个 Active NameNode 的问题,但是,当主备切换时,之前的 Active NameNode 可能仍在处理客户端的 RPC 请求,为此,需要增加隔离机制(fencing)将之前的 Active NameNode 杀死。HDFS 允许用户配置多个隔离机制,当发生主备切换时,将顺次执行这些隔离机制,直到一个返回成功。Hadoop 2.0 内部打包了两种类型的隔离机制,分别是 shell 和 sshfence。
1)sshfence方式
sshfence 通过 ssh 登录到前一个 ActiveNameNode 并将其杀死。为了让该机制成功执行,需配置免密码 ssh 登陆(注意:这个为主备节点配置双向的RSA免密码登陆),这可通过参数 dfs.ha.fencing.ssh.private-key-files 指定一个私钥文件。我的配置如下:
<property>
<name>dfs.ha.fencing.methods</name>
<value>sshfence</value>
</property>
<property>
<name>dfs.ha.fencing.ssh.private-key-files</name>
<value>/home/hadoop/.ssh/id_rsa</value>
</property>
另外,在设置一个超时时间,一旦 ssh 超过该时间,则认为执行失败。我的配置如下:
<property>
<name>dfs.ha.fencing.ssh.connect-timeout</name>
<value>30000</value>
</property>
2) shell方式(我没有采用这种方式)
执行自定义的Shell脚本命令隔离旧的ActiveNameNode。相比于sshfence方式,个人认为这种方式有个好处就是,你在shell脚本里边可以将之前的 Active NameNode 直接kill掉,然后立马启动NameNode,此时刚刚启动的NameNode就是立马处于一个StandBy状态,立马就可以进入HA状态,如果采用sshfence方式还要手动自己重启刚刚被kill掉的NameNode从而才能进入HA(这些的前提都是,采用手动HA方式,之前的Acitve NameNode不是宕机而仅仅是NameNode进程挂掉)。配置可以为:
<property>
<name>dfs.ha.fencing.methods</name>
<value>shell(/path/to/my/script.sh arg1 arg2 ...)</value>
</property>
注意, Hadoop 中所有参数将以环境变量的形似提供给该 shell,但所有的“.”被替换成了“_”,比如“dfs.namenode.rpc-address.ns1.nn1”变为“dfs_namenode_rpc-address”。
3.3、启动各种服务
HDFS集群进程启动的大概顺序为:启动所有的JournalNodeà启动nn1和nn2à启动所有DataNode。具体详细步骤如下:
(1) 启动所有JournalNode
在所有的配置有JournalNode的服务节点上,以我的配置就是在hadoop-slave02、hadoop-slave03和hadoop-slave04上分别执行:
$HADOOP_HOME/sbin/hdfs-daemon.sh startjournalnode
(2) 初始化JournalNode
此步骤要注意的是,如果你是将非HA HDFS的集群转化成为HA HDFS那么这一步骤就需要,如果都是HA HDFS就不需要执行此步骤。该步骤的主要作用是将非HA HDFS中NameNode的edit log去初始化JourNalnodes。具体操作在nn1上执行:
$HADOOP_HOME/bin/hdfs namenode -initializeSharedEdits [-force | -nonInteractive]。
此命令默认是交互式的,需要用户输入各种YOR N,如果嫌麻烦就直接执行:
$HADOOP_HOME/bin/hdfs namenode–initializeSharedEdits –force
(3) 启动nn1和nn2
子步骤1:
进入nn1,如果是新集群则format(注意,如果不是新集群千万不要format):
$HADOOP_HOME/bin/hadoop namenode -format
子步骤2:进入nn1,接着启动nn1:
hadoop-daemon.sh start namenode
子步骤3:进入nn2,执行下面命令让nn2从nn1上将最新的FSimage信息拉回来:
注意:如果是nn2的NameNode已经是被format掉了或者是将非HA HDFS的集群转化成为HA HDFS则不需要执行这一个步骤。
$HADOOP_HOME/bin/hdfs namenode -bootstrapStandby -force
子步骤4:进入nn2,然后启动nn2:
hadoop-daemon.sh start namenode
子步骤5:启动所有的DataNode
在各个DataNode节点执行:
hadoop-daemon.sh start datanode
或者直接在nn1节点直接执行:
hadoop-daemons.sh start namenode
各个服务到现在为止已经启动完毕,主备节点都还处于StandBy状态。我们可以看到主备节点的信息:
在这里说说一个遇到的“错误”问题,我在分别启动nn1和nn2之后,还没有将其中一个切换为Acive NameNode时,在nn1和nn2的日志上都报了以下这个“错误”:
其实这个错误信息完全可以不用管,出现这个问题原因上面信息已经很明显了,只要接下来将其中一个切换成Acive NameNode就ok了。
3.4、手动切换Active NameNode
nn1和nn2启动后都处于StandBy状态,此时都不能够对外提供服务,现在需要将nn1切换为Active NameNode,进入nn1节点输入:
$HADOOP_HOME/bin/hdfs haadmin-transitionToActive nn1
切换后我们再看看50070页面,nn1已经被切换为Active了:
在来看看之前还没有切换Acive NameNode的“错误”信息已经消失了,下面分别是nn1和nn2的日志信息,非常正常:
另外,如果你现在想将nn2转化为Acive NameNode,则在进入nn2所在节点,输入命令:
$HADOOP_HOME/bin/hdfs haadmin-failover --forcefence --forceactive nn1 nn2
看看nn2上的日志:
在这里说说我在切换时遇到过的几个小问题:
- 在住备节点上一定要配置双向的RSA免密码登陆,不然再切换时会出错,sshfence方式不能找到旧的Active NameNode,直接被reject掉。
- 第二,在切换的过程中我遇到了这个错误:
这个是权限问题,解决方法是直接在core-site.xml文件添加下面权限控制选项:
<property>
<name>hadoop.http.filter.initializers</name>
<value>org.apache.hadoop.security.AuthenticationFilterInitializer</value>
</property>
<property>
<name>hadoop.http.authentication.type</name>
<value>simple</value>
</property>
<property>
<name>hadoop.http.authentication.token.validity</name>
<value>36000</value>
</property>
<property>
<name>hadoop.http.authentication.signature.secret.file</name>
<value>/home/hadoop/hadoop-http-auth-signature-secret</value>
</property>
<property>
<name>hadoop.http.authentication.cookie.domain</name>
<value></value>
</property>
<property>
<name>hadoop.http.authentication.simple.anonymous.allowed</name>
<value>true</value>
</property>
然后建立/home/hadoop/hadoop-http-auth-signature-secret文件,并且在文件写入访问用户,我写入的是hadoop,然后将这个文件scp到各个节点,问题解决。
- 如果你将Active NameNode从nn1转到nn2后,在各个DataNode日志出现一个“错误”信息:
其实这个是我意料之中的“错误”信息,其实是没有任何问题的。因为,当你的Acive NameNode从nn1切换至nn2时,nn1就会被kill(即hadoop-msater中的NameNode进程会被kill掉),在上面切换日志我标注红方框的地方已经很清楚了。此时,各个DataNode还是会同时向Active NameNode和StandBy NameNode同时发送心跳信息的,因为nn1已经被kill掉了,所有会报这个信息,对系统没有任何影响,切换后正常使用,如果你重启nn1则不会再报信息了,新启动的nn1是处于StandBy模式的。
我们知道,StandByNameNode是不处理DataNode的RPC请求的,那么各个DataNode为什么还会同时向Active NameNode和StandBy NameNode同时发送心跳呢?这是因为这2个心跳的用途是不同的,各个DataNode向Active NameNode发送心跳主要是汇报数据块的状态信息,而向StandBy NameNode发心跳的主要目的是通知StandBy NameNode告诉它Active NameNode元数据发生了改变,要求StandBy NameNode去QJM区下载更改过的Edit Log信息。
3.5、配置自动切换模式
自动切换模式的实现需要下面两个组建的额支持:
(1) Zookeeper实例
需要质数个Zookeeper实例,在本集群我一个启用了3个Zookeeper实例,分别部署在hadoop-slave02、hadoop-slave03、hadoop-slave04中。
(2) ZKFailoverController(简称“ZKFC”)
ZKFC 是一个 Zookeeper客户端,负责监控和管理 NameNode 的状态,每台运行 NameNode的机器上也会运行一个 ZKFC 进程。
- 健康状况监控:
ZKFC 周期性地与本地的NameNode 交互,执行一些健康状况监测命令。
- Zookeeper session 管理:
如果本地 NameNode 是健康的,则会持有Zookeeper 上一个 znode,如果它是 Active 的,会持有 zookeeper 的仅有的一个特殊 znode,该 znode 类型为 ephemeral,一旦 namenode 挂掉后,会自动消失。
- 基于 zookeeper 的选举:
如果本地 NameNode 是活的,而没有其他 Namenode持有特殊的 znode,ZKFC 将尝试获取这个 znode,一旦获取成功后,则认为它“赢得了选举”,进而隔离之前的Active namenode,自己转换为新的 Active namenode。其大概结构如下图:
具体配置步骤:
步骤1:关闭集群修改hdfs-site.xml配置文件,增加自动切换选项:
<property>
<name>dfs.ha.automatic-failover.enabled</name>
<value>true</value>
</property>
步骤2:编辑core-site.xml文件,添加Zookeeper实例:
<property>
<name>ha.zookeeper.quorum</name>
<value>hadoop-slave02:2181,hadoop-slave03:2181,hadoop-slave04:2181</value>
</property>
步骤3:启动节点上的zookeeper实例:
分别进入hadoop-slave02、hadoop-slave03、hadoop-slave04节点执行:
$ZOOKEEPER_HOME/bin/zkServer.sh start
Zookeeper实例对应的进程名为:
步骤4:初始化zookeeper。
注意:这个步骤是针对第一次启动zookeeper实例用的,如果你的zookeeper实例不是第一次启动则不需要执行此命令。
$HADOOP_HOME/bin/hdfs zkfc -formatZK
步骤5:启动 JournalNode、NameNode 和 DataNode。
步骤6:启动ZKFC。
分别进入hadoop-master和hadoop-slave1即在各个 NameNode 节点上执行:
$HADOOP_HOME/sbin/hadoop-daemon.sh startzkfc
ZKFC对应的进程名为:
要注意的一点是:我们最先启动的NameNode为Active NameNode。现在为止配置完毕,验证请看下面一小节。
4、 HDFS HA机制的可用性验证
4.1手动切换模式验证
这里我使用的验证方法主要是模拟ActiveNameNode进程死掉的情况,另外Active NameNode所在节点发生宕机的情况也是一样的。现在集群中nn1为Active NameNode,nn2为StandBy NameNode,具体步骤:
步骤1:进入nn1所在节点即hadoop-master,运行kill -9 $NameNodePID将nn1杀死(此时集群中只有一个StandByNameNode)。
步骤2:往集群上传文件,或者执行hadoop fs相关命令提示连接不到(此时,集群中没有Active NameNode来处理客户端的RPC请求)。看错误信息:
步骤3:恢复集群,将StandBy NameNode转换为Active NameNode。进入nn2所在节点即hadoop-slave01执行:
$HADOOP_HOME/bin/hdfshaadmin -transitionToActive nn2
此时,nn2已经变成为Active NameNode,看50070:
步骤4:再次执行hadoop fs相关命令或者上传文件,一切正常。
步骤5:另外,不要忘记集群虽然是恢复了,但是此时已经没有了StandBy NameNode了,这是直接进入nn1所在节点启动NameNode进程,此时nn1为Standby NameNode。
当目前为止,一起验证以及恢复已经完成。各个服务的日志也恢复了正常。
4.2、自动切换模式验证
自动切换模式的验证和手动切换基本一样,还是手动kill掉Active NameNode进程,观察集群是否会自动恢复,将备用节点转换为Active NameNode。经过测试,当手动kill掉Active NameNode后,Standby NameNode成功地自动转换为Active NameNode继续服务于个个DataNode。
参考文献:
[1] http://hadoop.apache.org/docs/r2.2.0/hadoop-yarn/hadoop-yarn-site/HDFSHighAvailabilityWithQJM.html
[2] http://www.sizeofvoid.net/hadoop-2-0-namenode-ha-federation-practice-zh/
[3] http://yanbohappy.sinaapp.com/?p=205