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 挂掉。
--------------------------------------分割线 --------------------------------------
Ubuntu 13.04上搭建Hadoop环境
Ubuntu 12.10 +Hadoop 1.2.1版本集群配置
Hadoop: The Definitive Guide【PDF版】
--------------------------------------分割线 --------------------------------------
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”。