在 Hadoop 2.0.0 之前,一个集群只有一个Namenode,这将面临单点故障问题。如果 Namenode 机器挂掉了,整个集群就用不了了。只有重启 Namenode ,才能恢复集群。另外正常计划维护集群的时候,还必须先停用整个集群,这样没办法达到 7 * 24小时可用状态。Hadoop 2.0 及之后版本增加了 Namenode 高可用机制,下面详细介绍。
非高可用部署,可参考我之前的文章:大数据Hadoop原理介绍+安装+实战操作(HDFS+YARN+MapReduce)
我们知道
NameNode上存储的是 HDFS 上所有的元数据信息,因此最关键的问题在于 NameNode 挂了一个,备份的要及时顶上,这就意味着我们要把所有的元数据都同步到备份节点。好,接下来我们考虑如何同步呢?每次 HDFS 写入一个文件,都要同步写 NameNode 和其备份节点吗?如果备份节点挂了就会写失败?显然不能这样,只能是异步来同步元数据。如果 NameNode 刚好宕机却没有将元数据异步写入到备份节点呢?那这部分信息岂不是丢失了?这个问题就自然要引入第三方的存储了,在 HA 方案中叫做“共享存储”。每次写文件时,需要将日志同步写入共享存储,这个步骤成功才能认定写文件成功。然后备份节点定期从共享存储同步日志,以便进行主备切换。

NameNode 主备切换主要由 ZKFailoverController、HealthMonitor 和 ActiveStandbyElector 这 3 个组件来协同实现:
ZKFailoverController 作为 NameNode 机器上一个独立的进程启动 (在 hdfs 启动脚本之中的进程名为 zkfc),启动的时候会创建 HealthMonitor 和 ActiveStandbyElector 这两个主要的内部组件,ZKFailoverController 在创建 HealthMonitor 和 ActiveStandbyElector 的同时,也会向 HealthMonitor 和 ActiveStandbyElector 注册相应的回调方法。
HealthMonitor 主要负责检测 NameNode 的健康状态,如果检测到 NameNode 的状态发生变化,会回调 ZKFailoverController 的相应方法进行自动的主备选举。
ActiveStandbyElector 主要负责完成自动的主备选举,内部封装了 Zookeeper 的处理逻辑,一旦 Zookeeper 主备选举完成,会回调 ZKFailoverController 的相应方法来进行 NameNode 的主备状态切换。

HealthMonitor 初始化完成之后会启动内部的线程来定时调用对应 NameNode 的 HAServiceProtocol RPC 接口的方法,对 NameNode 的健康状态进行检测。ZKFailoverController 注册的相应方法进行处理。ActiveStandbyElector 来进行自动的主备选举。ZKFailoverController 调用对应 NameNode 的 HAServiceProtocol RPC 接口的方法将 NameNode 转换为 Active 状态或 Standby 状态。过 去几年中 Hadoop 社区涌现过很多的 NameNode 共享存储方案,比如 shared NAS+NFS、BookKeeper、BackupNode 和 QJM(Quorum Journal Manager) 等等。目前社区已经把由 Clouderea 公司实现的基于 QJM 的方案合并到 HDFS 的 trunk 之中并且作为默认的共享存储实现,本部分只针对基于 QJM 的共享存储方案的内部实现原理进行分析。为了理解 QJM 的设计和实现,首先要对 NameNode 的元数据存储结构有所了解。
Paxos 算法,采用多个称为 JournalNode 的节点组成的 JournalNode 集群来存储 EditLog。
FSEditLog:这个类封装了对 EditLog 的所有操作,是 NameNode 对 EditLog 的所有操作的入口。JournalSet: 这个类封装了对本地磁盘和 JournalNode 集群上的 EditLog 的操作,内部包含了两类JournalManager,一类为 FileJournalManager,用于实现对本地磁盘上 EditLog 的操作。一类为QuorumJournalManager,用于实现对 JournalNode 集群上共享目录的 EditLog 的操作。FSEditLog 只会调用 JournalSet 的相关方法,而不会直接使用 FileJournalManager 和 QuorumJournalManager。FileJournalManager: 封装了对本地磁盘上的 EditLog 文件的操作,不仅 NameNode 在向本地磁盘上写入 EditLog 的时候使用 FileJournalManager,JournalNode 在向本地磁盘写入 EditLog 的时候也复用了 FileJournalManager 的代码和逻辑。QuorumJournalManager:封装了对 JournalNode 集群上的 EditLog 的操作,它会根据 JournalNode 集群的 URI 创建负责与 JournalNode 集群通信的类 AsyncLoggerSet, QuorumJournalManager 通过 AsyncLoggerSet 来实现对 JournalNode 集群上的 EditLog 的写操作,对于读操作,QuorumJournalManager 则是通过 Http 接口从 JournalNode 上的 JournalNodeHttpServer 读取 EditLog 的数据。AsyncLoggerSet:内部包含了与 JournalNode 集群进行通信的 AsyncLogger 列表,每一个 AsyncLogger 对应于一个 JournalNode 节点,另外 AsyncLoggerSet 也包含了用于等待大多数 JournalNode 返回结果的工具类方法给 QuorumJournalManager 使用。AsyncLogger:具体的实现类是 IPCLoggerChannel,IPCLoggerChannel 在执行方法调用的时候,会把调用提交到一个单线程的线程池之中,由线程池线程来负责向对应的 JournalNode 的 JournalNodeRpcServer 发送 RPC 请求。JournalNodeRpcServer:运行在 JournalNode 节点进程中的 RPC 服务,接收 NameNode 端的 AsyncLogger 的 RPC 请求。JournalNodeHttpServer:运行在 JournalNode 节点进程中的 Http 服务,用于接收处于 Standby 状态的 NameNode 和其它 JournalNode 的同步 EditLog 文件流的请求。Active NameNode 和 StandbyNameNode 使用 JouranlNode 集群来进行数据同步的过程如下图 所示,Active NameNode 首先把 EditLog 提交到 JournalNode 集群,然后 Standby NameNode 再从 JournalNode 集群定时同步 EditLog:

处 于 Standby 状态的 NameNode 转换为 Active 状态的时候,有可能上一个 Active NameNode 发生了异常退出,那么 JournalNode 集群中各个 JournalNode 上的 EditLog 就可能会处于不一致的状态,所以首先要做的事情就是让 JournalNode 集群中各个节点上的 EditLog 恢复为一致。另外如前所述,当前处于 Standby 状态的 NameNode 的内存中的文件系统镜像有很大的可能是落后于旧的 Active NameNode 的,所以在 JournalNode 集群中各个节点上的 EditLog 达成一致之后,接下来要做的事情就是从 JournalNode 集群上补齐落后的 EditLog。只有在这两步完成之后,当前新的 Active NameNode 才能安全地对外提供服务。


对比一下就会看到,yarn集群的高可用架构比hdfs namenode的要简单太多了,没有zkfc,没有QJM集群,只需要一个zookeeper集群来负责选举出active的resourcemanager就好了。
为什么差别这么大?
HDFS的NameNode要保持高可用,必须要保证数据同步,从而需要一个共享存储QJM来存放edits日志,然后同步到standby的节点上去;ResourceManager来说,并不需要持久化啥数据,也就是无状态的,就像容器一样,直接删除,再创建一个完全没问题,所以差别来说,就是因为需要保存一些数据,这就是有状态和无状态之分。如果在开始部署 Hadoop 集群的时候就启用 NameNode 的高可用的话,那么相对会比较容易。但是如果在采用传统的单 NameNode 的架构运行了一段时间之后,升级为 NameNode 的高可用架构的话,就要特别注意在升级的时候需要按照以下的步骤进行操作:
Zookeeper 进行初始化,创建 Zookeeper 上的/hadoop-ha/${dfs.nameservices} 节点。创建节点是为随后通过 Zookeeper 进行主备选举做好准备,在进行主备选举的时候会在这个节点下面创建子节点。这一步通过在原有的 NameNode 上执行命令 hdfs zkfc -formatZK 来完成。JournalNode,这通过脚本命令 hadoop-daemon.sh start journalnode 来完成。JouranlNode 集群的共享存储目录进行格式化,并且将原有的 NameNode 本地磁盘上最近一次 checkpoint 操作生成 FSImage 文件之后的 EditLog 拷贝到 JournalNode 集群上的共享目录之中,这通过在原有的 NameNode 上执行命令 hdfs namenode -initializeSharedEdits 来完成。hadoop-daemon.sh start namenode 完成。hadoop-daemon.sh start namenode 完成。zkfc(ZKFailoverController) 进程,谁通过 Zookeeper 选主成功,谁就是主 NameNode,另一个为备 NameNode。这通过脚本命令hadoop-daemon.sh start zkfc 完成。| 主机名 | NameNode | DataNode | Zookeeper | ZKFC | JournalNode | ResourceManager | NodeManager |
|---|---|---|---|---|---|---|---|
| local-168-182-110 | * | * | * | * | * | ||
| local-168-182-111 | * | * | * | * | |||
| local-168-182-112 | * | * | * | * | |||
| local-168-182-113 | * | * | * | * | * |
也可以参考我之前的文章:分布式开源协调服务——Zookeeper
下载地址:https://zookeeper.apache.org/releases.html
cd /opt/bigdata/
wget https://dlcdn.apache.org/zookeeper/zookeeper-3.8.0/apache-zookeeper-3.8.0-bin.tar.gz --no-check-certificate
tar -xf apache-zookeeper-3.8.0-bin.tar.gz
vi /etc/profile
export ZOOKEEPER_HOME=/opt/bigdata/apache-zookeeper-3.8.0-bin/
export PATH=$ZOOKEEPER_HOME/bin:$PATH
# 加载生效
source /etc/profile
cd $ZOOKEEPER_HOME
cp conf/zoo_sample.cfg conf/zoo.cfg
mkdir $ZOOKEEPER_HOME/data
cat >conf/zoo.cfg<<EOF
# tickTime:Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个 tickTime 时间就会发送一个心跳。tickTime以毫秒为单位。session最小有效时间为tickTime*2
tickTime=2000
# Zookeeper保存数据的目录,默认情况下,Zookeeper将写数据的日志文件也保存在这个目录里。不要使用/tmp目录
dataDir=/opt/bigdata/apache-zookeeper-3.8.0-bin/data
# 端口,默认就是2181
clientPort=2181
# 集群中的follower服务器(F)与leader服务器(L)之间初始连接时能容忍的最多心跳数(tickTime的数量),超过此数量没有回复会断开链接
initLimit=10
# 集群中的follower服务器与leader服务器之间请求和应答之间能容忍的最多心跳数(tickTime的数量)
syncLimit=5
# 最大客户端链接数量,0不限制,默认是0
maxClientCnxns=60
# zookeeper集群配置项,server.1,server.2,server.3是zk集群节点;hadoop-node1,hadoop-node2,hadoop-node3是主机名称;2888是主从通信端口;3888用来选举leader
server.1=local-168-182-110:2888:3888
server.2=local-168-182-111:2888:3888
server.3=local-168-182-112:2888:3888
EOF
echo 1 > $ZOOKEEPER_HOME/data/myid
scp -r $ZOOKEEPER_HOME local-168-182-111:/opt/bigdata/
scp -r $ZOOKEEPER_HOME local-168-182-112:/opt/bigdata/
# 也需要添加环境变量和修改myid,local-168-182-111的myid设置2,local-168-182-112的myid设置3
cd $ZOOKEEPER_HOME
# 启动
./bin/zkServer.sh start
# 查看状态
./bin/zkServer.sh status

下载地址:https://dlcdn.apache.org/hadoop/common/
mkdir -p /opt/bigdata/hadoop && cd /opt/bigdata/hadoop
wget https://dlcdn.apache.org/hadoop/common/hadoop-3.3.4/hadoop-3.3.4.tar.gz --no-check-certificate
# 解压
tar -zvxf hadoop-3.3.4.tar.gz
vi /etc/profile
export HADOOP_HOME=/opt/bigdata/hadoop/hadoop-3.3.4
export PATH=$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$PATH
# 加载生效
source /etc/profile
$HADOOP_HOME/etc/hadoop/hadoop-env.sh# 在hadoop-env.sh文件末尾追加
export JAVA_HOME=/opt/jdk1.8.0_212
export HDFS_NAMENODE_USER=root
export HDFS_DATANODE_USER=root
export HDFS_SECONDARYNAMENODE_USER=root
export YARN_RESOURCEMANAGER_USER=root
export YARN_NODEMANAGER_USER=root
$HADOOP_HOME/etc/hadoop/core-site.xml #核心模块配置# 创建存储目录
mkdir -p /opt/bigdata/hadoop/hadoop-3.3.4/data/namenode
mkdir -p /opt/bigdata/hadoop/hadoop-3.3.4/data/journalnode
<configuration>
<property>
<name>fs.defaultFSname>
<value>hdfs://myhdfsvalue>
property>
<property>
<name>hadoop.tmp.dirname>
<value>/opt/bigdata/hadoop/hadoop-3.3.4/data/namenodevalue>
property>
<property>
<name>ha.zookeeper.quorumname>
<value>local-168-182-110:2181,local-168-182-111:2181,local-168-182-112:2181value>
property>
<property>
<name>hadoop.http.staticuser.username>
<value>rootvalue>
property>
<property>
<name>hadoop.proxyuser.root.hostsname>
<value>*value>
property>
<property>
<name>hadoop.proxyuser.root.groupsname>
<value>*value>
property>
<property>
<name>hadoop.proxyuser.root.usersname>
<value>*value>
property>
<property>
<name>fs.trash.intervalname>
<value>1440value>
property>
configuration>
$HADOOP_HOME/etc/hadoop/hdfs-site.xml #hdfs文件系统模块配置<configuration>
<property>
<name>dfs.nameservicesname>
<value>myhdfsvalue>
property>
<property>
<name>dfs.ha.namenodes.myhdfsname>
<value>nn1,nn2value>
property>
<property>
<name>dfs.namenode.rpc-address.myhdfs.nn1name>
<value>local-168-182-110:8082value>
property>
<property>
<name>dfs.namenode.rpc-address.myhdfs.nn2name>
<value>local-168-182-113:8082value>
property>
<property>
<name>dfs.namenode.http-address.myhdfs.nn1name>
<value>local-168-182-110:9870value>
property>
<property>
<name>dfs.namenode.http-address.myhdfs.nn2name>
<value>local-168-182-113:9870value>
property>
<property>
<name>dfs.namenode.shared.edits.dirname>
<value>qjournal://local-168-182-110:8485;local-168-182-111:8485;local-168-182-112:8485/myhdfsvalue>
property>
<property>
<name>dfs.client.failover.proxy.provider.myhdfsname>
<value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvidervalue>
property>
<property>
<name>dfs.ha.fencing.methodsname>
<value>sshfencevalue>
property>
<property>
<name>dfs.ha.fencing.ssh.private-key-filesname>
<value>/root/.ssh/id_rsavalue>
property>
<property>
<name>dfs.journalnode.edits.dirname>
<value>/opt/bigdata/hadoop/hadoop-3.3.4/data/journalnodevalue>
property>
<property>
<name>dfs.ha.automatic-failover.enabledname>
<value>truevalue>
property>
<property>
<name>dfs.client.failover.proxy.provider.myhdfsname>
<value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvidervalue>
property>
<property>
<name>dfs.replicationname>
<value>3value>
property>
<property>
<name>dfs.permissions.enabledname>
<value>falsevalue>
property>
configuration>
修改$HADOOP_HOME/etc/hadoop/workers将下面内容覆盖文件,默认只有localhost,works配置的为
DataNode节点的主机名或IP,如果配置了works文件,并且配置ssh免密登录,可以使用 start-dfs.sh 启动 HDFS集群
local-168-182-111
local-168-182-112
local-168-182-113
$HADOOP_HOME/etc/hadoop/yarn-site.xml #yarn模块配置<configuration>
<property>
<name>yarn.resourcemanager.ha.enabledname>
<value>truevalue>
property>
<property>
<name>yarn.resourcemanager.cluster-idname>
<value>myyarnvalue>
property>
<property>
<name>yarn.resourcemanager.ha.rm-idsname>
<value>rm1,rm2value>
property>
<property>
<name>yarn.resourcemanager.hostname.rm1name>
<value>local-168-182-110value>
property>
<property>
<name>yarn.resourcemanager.hostname.rm2name>
<value>local-168-182-113value>
property>
<property>
<name>yarn.resourcemanager.webapp.address.rm1name>
<value>local-168-182-110:8088value>
property>
<property>
<name>yarn.resourcemanager.webapp.address.rm2name>
<value>local-168-182-113:8088value>
property>
<property>
<name>hadoop.zk.addressname>
<value>local-168-182-110:2181,local-168-182-111:2181,local-168-182-112:2181value>
property>
<property>
<name>yarn.resourcemanager.recovery.enabledname>
<value>truevalue>
property>
<property>
<name>yarn.resourcemanager.store.classname>
<value>org.apache.hadoop.yarn.server.resourcemanager.recovery.ZKRMStateStorevalue>
property>
<property>
<name>yarn.nodemanager.aux-servicesname>
<value>mapreduce_shufflevalue>
property>
<property>
<name>yarn.nodemanager.pmem-check-enabledname>
<value>falsevalue>
property>
<property>
<name>yarn.nodemanager.vmem-check-enabledname>
<value>falsevalue>
property>
<property>
<name>yarn.log-aggregation-enablename>
<value>truevalue>
property>
<property>
<name>yarn.log.server.urlname>
<value>http://local-168-182-110:19888/jobhistory/logsvalue>
property>
<property>
<name>yarn.log-aggregation.retain-secondsname>
<value>604880value>
property>
configuration>
$HADOOP_HOME/etc/hadoop/ mapred-site.xml #MapReduce模块配置<configuration>
<property>
<name>mapreduce.framework.namename>
<value>yarnvalue>
property>
<property>
<name>mapreduce.jobhistory.addressname>
<value>local-168-182-110:10020value>
property>
<property>
<name>mapreduce.jobhistory.webapp.addressname>
<value>local-168-182-110:19888value>
property>
<property>
<name>yarn.app.mapreduce.am.envname>
<value>HADOOP_MAPRED_HOME=${HADOOP_HOME}value>
property>
<property>
<name>mapreduce.map.envname>
<value>HADOOP_MAPRED_HOME=${HADOOP_HOME}value>
property>
<property>
<name>mapreduce.reduce.envname>
<value>HADOOP_MAPRED_HOME=${HADOOP_HOME}value>
property>
configuration>
scp -r $HADOOP_HOME local-168-182-111:/opt/bigdata/hadoop/
scp -r $HADOOP_HOME local-168-182-112:/opt/bigdata/hadoop/
scp -r $HADOOP_HOME local-168-182-113:/opt/bigdata/hadoop/
# 注意在其它节点先创建/opt/bigdata/hadoop/和环境变量
journalnode# 在local-168-182-110、local-168-182-111、local-168-182-112机器上启动
hdfs --daemon start journalnode
NameNode数据同步# 格式化(第一次配置情况下使用,已运行集群不能用),在local-168-182-110执行
hdfs namenode -format
hdfs namenode -initializeSharedEdits
local-168-182-110上的NameNode节点hdfs --daemon start namenode
local-168-182-113节点同步镜像数据hdfs namenode -bootstrapStandby
local-168-182-113节点上启动NameNodehdfs --daemon start namenode
# 在local-168-182-110上执行
hdfs zkfc -formatZK
# 在local-168-182-110,local-168-182-113上执行,ZKFC远程杀死假死SNN使用的killall namenode命令属于psmisc软件中的。建议所有节点都安装psmisc。
yum install -y psmisc
~/.bash_profile,记得source 加载# 或者在start-dfs.sh,stop-dfs.sh(在hadoop安装目录的sbin里)两个文件顶部添加以下参数
export HDFS_NAMENODE_USER=root
export HDFS_DATANODE_USER=root
export HDFS_JOURNALNODE_USER=root
export HDFS_SECONDARYNAMENODE_USER=root
export YARN_RESOURCEMANAGER_USER=root
export YARN_NODEMANAGER_USER=root
export HDFS_ZKFC_USER=root
hdfs# 在local-168-182-110节点上执行
start-dfs.sh
jps

web地址:
http://local-168-182-110:9870/
http://local-168-182-113:9870/


yarnstart-yarn.sh
jps

web地址:
http://local-168-182-110:8088/cluster/cluster
http://local-168-182-113:8088/cluster/cluster


mapred --daemon start historyserver

hdfs haadmin -getServiceState nn1
hdfs haadmin -getServiceState nn2
# 设置nn1为Standby,nn2为Active
# 当HDFS的HA配置中开启了自动故障转移时,需加上--forcemanual参数(谨慎使用此参数)
hdfs haadmin -transitionToStandby --forcemanual nn1
hdfs haadmin -transitionToActive --forcemanual nn2
# 查看
#hdfs haadmin -getServiceState nn1
#hdfs haadmin -getServiceState nn2
# 查看所有节点状态
hdfs haadmin -getAllServiceState
# 设置nn1为Active,nn1为Standby
# 当HDFS的HA配置中开启了自动故障转移时,需加上--forcemanual 参数(谨慎使用此参数)
hdfs haadmin -transitionToActive --forcemanual nn1
hdfs haadmin -transitionToStandby --forcemanual nn2
# 查看
#hdfs haadmin -getServiceState nn1
#hdfs haadmin -getServiceState nn2
# 查看所有NameNode节点状态
hdfs haadmin -getAllServiceState

在active的NameNode节点上,kill掉NameNode进程:
jps
jps|grep NameNode|awk '{print $1}'|xargs kill -9
jps
# 再查看节点状态
hdfs haadmin -getServiceState nn1
hdfs haadmin -getServiceState nn2
# 查看所有NameNode节点状态
hdfs haadmin -getAllServiceState

# 启动namenode
hdfs --daemon start namenode
jps
# 查看节点状态
hdfs haadmin -getServiceState nn1
hdfs haadmin -getServiceState nn2
# 查看所有NameNode节点状态
hdfs haadmin -getAllServiceState

yarn rmadmin -getServiceState rm1
yarn rmadmin -getServiceState rm2
# 设置rm1为Standby,设置rm2为Active
# 当YARN的HA配置中开启了自动故障转移时,需加上-forcemanual 参数(谨慎使用此参数)
yarn rmadmin -transitionToStandby -forcemanual rm1
yarn rmadmin -transitionToActive -forcemanual rm2
# 查看
#yarn rmadmin -getServiceState rm1
#yarn rmadmin -getServiceState rm2
yarn rmadmin -getAllServiceState
# 设置rm1为Active,设置rm2为Standby
# 当YARN的HA配置中开启了自动故障转移时,需加上-forcemanual 参数
yarn rmadmin -transitionToActive -forcemanual rm1
yarn rmadmin -transitionToStandby -forcemanual rm2
# 查看所有ResourceManager节点状态
yarn rmadmin -getAllServiceState

在active的ResourceManager节点上,kill掉ResourceManager进程:
yarn rmadmin -getAllServiceState
jps
jps|grep ResourceManager|awk '{print $1}'|xargs kill -9
jps
# 再查看节点状态
#yarn rmadmin -getServiceState rm1
#yarn rmadmin -getServiceState rm2
# 查看所有ResourceManager节点状态
yarn rmadmin -getAllServiceState

yarn --daemon start resourcemanager
jps
# 查看所有ResourceManager节点状态
yarn rmadmin -getAllServiceState

Hadoop 3.3.4 HA(高可用)原理与实现就先到这里了,有疑问的小伙伴欢迎给我留言哦,后面会持续更新关于大数据方面的文章,请小伙伴耐心等待~