HA高可用集群
HA介绍
什么是HA
HA: High Availability,高可用集群,指的是集群7*24小时不间断服务。
为什么需要HA
在HDFS中,有NameNode、DataNode和SecondaryNameNode角色的分布,客户端所有的操作都是要与NameNode交互的,同时整个集群的命名空间信息也都保存在NameNode节点。但是,现在的集群配置中只有一个NameNode,于是就有一个问题: 单点故障
那么,什么是单点故障呢?现在集群中只有一个NameNode,那么假如这个NameNode意外宕机、升级硬件等,导致NameNode不可用了,整个集群是不是也就不可用了?这就是单点故障的问题。
为了解决这样的问题,就需要高可用集群了。
高可用的备份方式
-
主从模式(冷备)
准备两台服务器, 准备相同的程序。 一台服务器对外提供服务, 称为主节点(Active节点); 另外一台服务器平时不对外提供服务, 主要负责和Active节点之间进行数据的同步, 称为备份节点(Standby节点). 当主节点出现故障, Standby节点可以自动提升为Active节点, 对外提供服务。
ZooKeeper实现的集群高可用, 采用的就是这种模式。 -
双主互备(热备)(了解)
准备两台服务器, 准备相同的程序. 同时对外提供服务(此时, 这两台服务器彼此为对方的备份), 这样, 当一台节点宕机的时候, 另外一台节点还可以继续提供服务.
-
集群多备(了解)
基本上等同于双主互备, 区别就在于同时对外提供服务的节点数量更多, 备份数量更多
高可用的实现
我们在这里采用的是主从模式的备份方式,也就是准备两个NameNode,一个对外提供服务,称为Active节点;另外一个不对外提供服务,只是实时的同步Active节点的数据,称为Standby的节点。
为了提供快速的故障转移,Standby节点还必须具有集群中块位置的最新信息。为了实现这一点,DataNodes被配置了两个NameNodes的位置,并向两者发送块位置信息和心跳信号。也就是说,DataNode同时向两个NameNode心跳反馈。
高可用架构图
JournalNode
-
JournalNode的功能
Hadoop2.x版本之后, Clouera提出了QJM/QuromJournal Manager, 这是一个基于Paxos算法实现的HA的实现方案
-
基本的原理就是使用2N+1台JN存储EditLog, 每次写入数据的时候, 有半数以上的JN返回成功的信息, 就表示本次的操作已经同步到了JN
-
在HA中, SecondaryNameNode这个角色已经不存在了, 保证Standby节点的元数据信息与Active节点的元数据信息一致, 需要通过若干个JN
-
当有任何的操作发生在Active节点上的时候, JN会记录这些操作到半数以上的节点中. Standby节点检测JN中的log日志文件发生了变化, 会读取JN中的数据到自己的内存中, 维护最新的目录树结构与元数据信息
-
当发生故障的时候, Active节点挂掉, 此时Standby节点在成为新的Active节点之前, 会将读取到的EditLog文件在自己的内存中进行推演, 得到最新的目录树结构. 此时再升为Active节点, 可以无缝的继续对外提供服务.
-
-
防止脑裂的发生
对于HA群集的正确操作至关重要,一次只能有一个NameNode处于Active状态。否则,名称空间状态将在两者之间迅速分散,从而有数据丢失或其他不正确结果的风险。为了确保该属性并防止所谓的“裂脑情况”,JournalNode将一次仅允许单个NameNode成为作者。在故障转移期间,变为活动状态的NameNode将仅承担写入JournalNodes的角色,这将有效地防止另一个NameNode继续处于活动状态,从而使新的Active可以安全地进行故障转移。
- 怎么理解脑裂?
就是Active节点处于网络震荡状态,假死状态,Standby就转为Active。等网络震荡过后,就有两个Active了,这就是脑裂。
- 怎么理解脑裂?
-
JournalNode集群正常工作的条件
- 至少3个Journalnode节点
- 运行个数建议奇数个(3,5,7等)
- 满足(n+1)/2个以上,才能正常服务。即能容忍(n-1)/2个故障。
-
JournalNode的缺点
在这种模式下,即使活动节点发生故障,系统也不会自动触发从活动NameNode到备用NameNode的故障转移,必须需要人为的操作才行。要是有一个能监视Active节点的服务功能就好了。
这个时候,我们就可以使用zookeeper集群服务,来帮助我们进行自动容灾了。
自动容灾原理
如果想进行HA的自动故障转移,那么需要为HDFS部署两个新组件:ZooKeeper quorum和ZKFailoverController进程(缩写为ZKFC)
Zookeeper quorum
Apache ZooKeeper是一项高可用性服务,用于维护少量的协调数据,将数据中的更改通知客户端并监视客户端的故障。HDFS自动故障转移的实现依赖ZooKeeper进行以下操作:
故障检测
集群中的每个NameNode计算机都在ZooKeeper中维护一个持久性会话。如果计算机崩溃,则ZooKeeper会话将终止,通知另一个NameNode应触发故障转移。活动的NameNode选举(HA的第一次启动)
ZooKeeper提供了一种简单的机制来专门选举一个节点为活动的节点。如果当前活动的NameNode崩溃,则另一个节点可能会在ZooKeeper中采取特殊的排他锁,指示它应成为下一个活动的NameNode。
ZKFC
ZKFailoverController(ZKFC)是一个新组件,它是一个ZooKeeper客户端,它监视和管理namenode的状态。运行namenode的每台机器都会运行一个ZKFC,该ZKFC负责以下内容:
运行状况监视
ZKFC使用运行状况检查命令定期ping其本地NameNode。只要NameNode以健康状态及时响应,ZKFC就会认为该节点是健康的。如果节点崩溃,冻结或以其他方式进入不正常状态,则运行状况监视器将其标记为不正常。ZooKeeper会话管理
当本地NameNode运行状况良好时,ZKFC会在ZooKeeper中保持打开的会话。如果本地NameNode处于活动状态,则它还将持有一个特殊的“锁定” znode。该锁使用ZooKeeper对“临时”节点的支持。如果会话到期,则锁定节点将被自动删除。基于ZooKeeper的选举
如果本地NameNode运行状况良好,并且ZKFC看到当前没有其他节点持有锁znode,则它本身将尝试获取该锁。如果成功,则它“赢得了选举”,并负责运行故障转移以使其本地NameNode处于活动状态。故障转移过程类似于上述的手动故障转移:首先,如有必要,将先前的活动节点隔离,然后将本地NameNode转换为活动状态。
自动容灾的过程描述
ZKFC(是一个进程,和NN在同一个物理节点上)有两只手,分别拽着NN和Zookeeper。(监控NameNode健康状态,并向Zookeeper注册NameNode);集群一启动,2个NN谁是Active?谁又是Standby呢?
2个ZKFC先判断自己的NN是否健康,如果健康,2个ZKFC会向zoopkeeper集群抢着创建一个节点,结果就是只有1个会最终创建成功,从而决定active地位和standby位置。如果ZKFC1抢到了节点,ZKFC2没有抢到,ZKFC2也会监控watch这个节点。如果ZKFC1的Active NN异常退出,ZKFC1最先知道,就访问ZK,ZK就会把曾经创建的节点删掉。删除节点就是一个事件,谁监控这个节点,就会调用callback回调,ZKFC2就会把自己的地位上升到active,但在此之前要先确认ZKFC1的节点是否真的挂掉?这就引入了第三只手的概念。ZKFC2通过ssh远程连接NN1尝试对方降级,判断对方是否挂了。确认真的不健康,才会真的 上升地位之active。所以ZKFC2的步骤是:
1.创建新节点。
2.第三只手把对方降级。
3.把自己升级
那如果NN都没毛病,ZKFC挂掉了呢?Zoopkeeper有一个客户端session机制,集群启动之后,2个ZKFC除了监控自己的NN,还要和Zoopkeeper建立一个tcp长连接,并各自获取自己的session。只要一方的session失效,Zoopkeeper 就会删除该方创建的节点,同时另一方创建节点,上升地位。
HA的配置
守护进程布局
qianfeng01: NameNode、DataNode、JournalNode、QuorumPeerMain、ZKFC
qianfeng02: NameNode、DataNode、JournalNode、QuorumPeerMain、ZKFC
qianfeng03: DataNode、JournalNode、QuorumPeerMain
复制代码
现在,先停止HDFS的进程,修改如下的配置文件吧!
hdfs-site.xml
<!-- 注意: 高可用的集群,没有SecondaryNameNode的存在,因此在这个文件中之前存在的SecondaryNameNode的配置需要删除 -->
<configuration>
<!-- 配置NameNode的逻辑名称 -->
<!-- 注意: 后面的很多参数配置都是需要使用到这个名称的 -->
<property>
<name>dfs.nameservices</name>
<value>supercluster</value>
</property>
<!-- 配置两个NameNode的唯一标识符 -->
<property>
<name>dfs.ha.namenodes.supercluster</name>
<value>nn1,nn2</value>
</property>
<!-- 针对每一个NameNode,配置自己的RPC通信地址和端口 -->
<property>
<name>dfs.namenode.rpc-address.supercluster.nn1</name>
<value>qianfeng01:9820</value>
</property>
<property>
<name>dfs.namenode.rpc-address.supercluster.nn2</name>
<value>qianfeng02:9820</value>
</property>
<!-- 针对每一个NameNode,配置WebUI的地址和端口 -->
<property>
<name>dfs.namenode.http-address.supercluster.nn1</name>
<value>qianfeng01:9870</value>
</property>
<property>
<name>dfs.namenode.http-address.supercluster.nn2</name>
<value>qianfeng02:9870</value>
</property>
<!-- 定义journalnode进程的数据存储的父路径, 目录在上面已经定义好了的:journalData -->
<property>
<name>dfs.journalnode.edits.dir</name>
<value>/usr/local/hadoop-3.3.1/tmp</value>
</property>
<!-- 配置journalnode的服务器地址和存储目录(数目为奇数个) -->
<!-- 服务器地址使用分号“;”作为分隔符-->
<property>
<name>dfs.namenode.shared.edits.dir</name>
<value>qjournal://qianfeng01:8485;qianfeng02:8485;qianfeng03:8485/journalData</value>
</property>
<!-- 指定客户端连接Active的namenode节点的java类型 -->
<property>
<name>dfs.client.failover.proxy.provider.supercluster</name>
<value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value>
</property>
<!--为了保证系统的正确性,在任何时间只有一个NameNode处于Active状态,需要配置一个防护机制 -->
<property>
<name>dfs.ha.fencing.methods</name>
<value>sshfence</value>
</property>
<!--为了使该防护选项起作用,它必须能够在不提供密码的情况下SSH到目标节点。因此,还必须配置以下属性-->
<property>
<name>dfs.ha.fencing.ssh.private-key-files</name>
<value>/root/.ssh/id_rsa</value>
</property>
<!-- 免密登陆超时时间,超过此时间未连接上,则登陆失败,此配置可选-->
<property>
<name>dfs.ha.fencing.ssh.connect-timeout</name>
<value>30000</value>
</property>
<!-- 支持自动容灾属性 -->
<property>
<name>dfs.ha.automatic-failover.enabled</name>
<value>true</value>
</property>
<!-- 块的副本数量 -->
<property>
<name>dfs.replication</name>
<value>3</value>
</property>
</configuration>
复制代码
core-site.xml
<configuration>
<!--注意:使用到的是在hdfs-site.xml中配置的逻辑名称 -->
<property>
<name>fs.defaultFS</name>
<value>hdfs://supercluster</value>
</property>
<!-- hdfs的数据保存的路径,被其他属性所依赖的一个基础路径 -->
<property>
<name>hadoop.tmp.dir</name>
<value>/usr/local/hadoop-3.3.1/tmp</value>
</property>
<!-- ZooKeeper服务的地址 -->
<property>
<name>ha.zookeeper.quorum</name>
<value>qianfeng01:2181,qianfeng02:2181,qianfeng03:2181</value>
</property>
</configuration>
复制代码
hadoop-env.sh
# 添加两行
export HDFS_JOURNALNODE_USER=root
export HDFS_ZKFC_USER=root
复制代码
分发配置文件到其他节点
[root@qianfeng01 ~]# cd $HADOOP_HOME/etc
[root@qianfeng01 hadoop]# scp -r hadoop qianfeng02:$PWD
[root@qianfeng01 hadoop]# scp -r hadoop qianfeng03:$PWD
复制代码
启动集群
现在,集群已经搭建成为了高可用的集群了。在启动集群之前,我们需要先明确一件事情: 集群现在的状态有两种:
- 这个集群我之前使用过,NameNode已经存储有数据了(fsimage和edits已生成)
- 这个集群是我新搭建的,我直接搭建集群的时候就搭建的高可用的集群,之前从来没有启动过
如果你是第一种情况,请跳转到 启动: 普通集群转HA
如果你是第二种情况,请跳转到 启动: 直接搭建HA
个人建议:最好是直接搭建HA这样可以避免不必要的麻烦
启动: 普通集群转HA
# 1. 启动集群的JournalNode服务。
# 注意事项: 如果之前集群还在运行,需要先将其停止!使用命令 stop-dfs.sh
[root@qianfeng01 ~]# hdfs --daemon start journalnode
[root@qianfeng02 ~]# hdfs --daemon start journalnode
[root@qianfeng03 ~]# hdfs --daemon start journalnode
# 2. 启动以前节点上的namenode进程
[root@qianfeng01 ~]# hdfs --daemon start namenode
# 3. 在新的namenode节点上拉取镜像文件
[root@qianfeng02 ~]# hdfs namenode -bootstrapStandby
# 4. 同步日志到journalnode集群上,再启动集群
# 先关namenode
[root@qianfeng01 ~]# hdfs --daemon stop namenode
# 再同步日志
[root@qianfeng01 ~]# hdfs namenode -initializeSharedEdits
# 5. 格式化zkfc
# 5.1. 前提QuorumPeerMain服务必须处于开启状态,客户端zkfc才能格式化成功
[root@qianfeng01 ~]# zkServer.sh start
[root@qianfeng02 ~]# zkServer.sh start
[root@qianfeng03 ~]# zkServer.sh start
# 5.2. 选择其中一个namenode节点进行格式化zkfc
[root@qianfeng01 ~]# hdfs zkfc -formatZK
# 6. 你就可以快乐的开启HA集群进行测试了
[root@qianfeng01 ~]# start-all.sh
# 查看NameNode的状态
[root@qianfeng01 ~]# hdfs haadmin -getServiceState nn1
# 注意: 以后开HA集群时,要先开zookeeper服务,再开HDFS。
复制代码
启动: 直接搭建HA
# 1. 启动三个节点上的journalnode服务
[root@qianfeng01 ~]# hdfs --daemon start journalnode
[root@qianfeng02 ~]# hdfs --daemon start journalnode
[root@qianfeng03 ~]# hdfs --daemon start journalnode
# 2. 格式化namenode
# - 先删除所有节点的${hadoop.tmp.dir}/tmp/的数据(可选,这一步表示弃用fsimage.)
# - 选择其中一个namenode进行格式化
[root@qianfeng01 ~]# hdfs namenode -format
# - 并启动namenode进程
[root@qianfeng01 ~]# hdfs --daemon start namenode
# 3. 在另一台namenode上拉取已格式化的那台机器的镜像文件(数据的一致性)
[root@qianfeng02 ~]# hdfs namenode -bootstrapStandby
# 4. 然后关闭已经启动的namenode
[root@qianfeng01 ~]# hdfs --daemon stop namenode
# 5. 格式化zkfc
# 5.1. 前提QuorumPeerMain服务必须处于开启状态,客户端zkfc才能格式化成功
[root@qianfeng01 ~]# zkServer.sh start
[root@qianfeng02 ~]# zkServer.sh start
[root@qianfeng03 ~]# zkServer.sh start
# 5.2. 选择其中一个namenode节点进行格式化zkfc
[root@qianfeng01 ~]# hdfs zkfc -formatZK
# 6. 你就可以快乐的开启HA集群进行测试了
[root@qianfeng01 ~]# start-all.sh
# 注意:以后开HA集群时,要先开zookeeper服务,再开HDFS。
复制代码
自动容灾测试
由于CentOS7的minimal版本缺少容灾切换ActiveNameNode节点时所需要的组件,因此需要手动安装一下:
yum install -y psmisc
复制代码
- 首先查看当前活跃的Active节点是谁
- Kill掉活跃节点上的NameNode进程,模拟宕机
- 观察另外一个节点,是否已经变成Active的状态
API操作
对于HA集群来说,如果想要使用JavaAPI进行集群的访问,需要在Configuration中设置好每一个属性,也就是再上方配置集群时进行的各种配置。如果觉得麻烦,最简单的方法就是将core-site.xml、hdfs-site.xml拷贝到项目的Resources目录下。
也可以观看大数据相关视频:
千锋大数据Hadoop全新增强版-先导片