title: Hadoop HDFS之HA集群部署 #标题tags: hadoop #标签
date: 2021-01-01
categories: Hadoop # 分类
本文用于记录下HDFS、YARN高可用性(HA)功能以及如何使用Quorum Journal Manager(QJM)功能配置和管理HA HDFS群集。
所谓HA就是保证7*24小时不中断服务,实现高可用的最关键的策略就是消除单点故障,HA严格来说应该分成各个组件的HA机制:HDFS的HA和YARN的HA。
在Hadoop 2.0.0之前,NameNode是HDFS集群中的单点故障(SPOF)。每个群集只有一个NameNode,如果该服务不可用,则整个群集在整个NameNode重新启动或在另一台计算机上启动之前将不可用。
在典型的HA群集中,将两个单独的节点配置为NameNode。在任何时间点,其中一个NameNode处于Active状态,而另一个处于Standby状态。Active NameNode负责群集中的所有客户端操作,而Standby只是充当从属,并保持足够的状态以在必要时提供快速故障转移。
为了使Standby节点保持其状态与Active节点同步,两个节点都与称为“ JournalNodes”(JN)的一组单独的守护程序进行通信。当Active节点执行任何名称空间修改时,它会持久地将修改记录记录到这些JN的中。Standby节点能够从JN读取编辑内容,并不断监视它们以查看编辑日志的更改。当“备用节点”看到编辑内容时,会将其应用到自己的名称空间。发生故障转移时,Standby服务器将确保在将自身升级为活动状态之前,已从JournalNode读取所有编辑内容。这样可确保在发生故障转移之前,名称空间状态已完全同步。
为了提供快速故障转移,Standby节点还必须具有有关集群中块位置的最新信息。为了实现这一点,DataNode被配置了两个NameNode的位置,并向两者发送块位置信息和心跳。
对于HA群集的正确操作至关重要,一次只能有一个NameNode处于活动状态。否则会产生脑裂现象,从而有数据丢失或其他不正确结果的风险。为了确保此属性并防止所谓的“裂脑情况”,JournalNode将仅一次允许一个NameNode成为“作者”。在故障转移期间,将变为Active状态的NameNode将仅承担写入JournalNodes的角色,这将有效地防止另一个NameNode继续处于活动状态,从而使新的Active节点可以安全地进行故障转移。
参考:
环境准备
为了部署高可用性群集,应该满足以下条件:
- NameNode节点:运行Active NameName和Standby NameNode的服务器应具有彼此等效的硬件。
- JournalNode节点:JournalNode守护程序相对较轻,因此可以合理地将JournalNode节点与其他Hadoop守护程序(例如NameNode,JobTracker或YARN ResourceManager)进行复用。注意:必须至少有3个JournalNode守护程序,因为必须将编辑日志修改写入大多数JN。这样可以避免JournalNode节点的单点故障,你也可以允许三个以上的JournalNode节点,但节点数必须为奇数,如3、5、7… ,当与N个JournalNode一起运行时,系统最多可以容忍(N-1)/ 2个故障,并继续正常运行。
在高可用性群集中,Standby节点还执行SecondaryNameNode的工作(编辑日志合并),因此不必在高可用性群集中运行SecondaryNameNode节点。
OS | hostname | IP | roles |
---|---|---|---|
Centos 7.5 | hadoop01 | 192.168.20.2 | Zookeeper、DataNode、NodeManager、NameNode、JournalNode |
Centos 7.5 | hadoop02 | 192.168.20.3 | Zookeeper、 DataNode、NodeManager、NameNode、JournalNode |
Centos 7.5 | hadoop03 | 192.168.20.4 | Zookeeper、DataNode、NodeManager、ResourceManager、JournalNode |
Centos 7.5 | hadoop04 | 192.168.20.5 | DataNode、NodeManager、ResourceManager、historyserver |
接下的所有操作,只要没特别说明,则只需在 hadoop01 这一个机器上执行即可。
环境配置
在正式进行集群配置前,需要先对系统进行初步配置。
配置ssh免密登录
切记,先配置主机解析记录,再配置免密登录。
# 配置主机名解析记录
$ cat >> /etc/hosts <<EOF
192.168.20.2 hadoop01
192.168.20.3 hadoop02
192.168.20.4 hadoop03
192.168.20.5 hadoop04
EOF
# 修改后的hosts文件发送至其他节点
$ for i in $(seq 1 4);do rsync -az /etc/hosts hadoop0${i}:/etc/;done
# 下面是配置免密登录,需要在resourcemanager和NameNode两个角色的机器上执行
# 如果可以,最好在集群中所有节点都配置免密登录到集群中的其他节点
# 为了方便后续群起集群,我们必须在resourcemanager和NameNode两个角色的机器上配置免密登录到其他节点
$ ssh-keygen -t rsa # 执行后一路回车
$ for i in $(seq 1 4);do ssh-copy-id hadoop0${i} ;done # 执行后根据提示,该输入yes输入yes,该输入密码就输入密码
一定要确保所有NameNode和resourcemanager节点可以免密登录到其他节点,否则后续自动故障转移会出问题。
修改系统限制
$ mv /etc/security/limits.conf{,.bak}
cat > /etc/security/limits.conf << EOF
* - nofile 655360
* - memlock unlimited
* - stack 655360
* - nproc unlimited
EOF
cat > /etc/sysctl.conf << EOF
kernel.sysrq = 0
kernel.core_uses_pid = 1
fs.file-max=655360
kernel.msgmnb = 65536
kernel.msgmax = 65536
kernel.shmmax = 68719476736
kernel.shmall = 4294967296
kernel.pid_max = 655360
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 0
net.ipv4.tcp_max_tw_buckets = 10000
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_timestamps = 0
net.ipv4.tcp_sack = 1
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_ecn = 0
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 3
net.ipv4.tcp_max_orphans = 655360
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_mem = 65536 131072 262144
net.ipv4.udp_mem = 65536 131072 262144
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 16384 16777216
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.route.gc_timeout = 100
# 禁止icmp重定向报文
net.ipv4.conf.all.accept_redirects = 0
# 禁止icmp源路由
net.ipv4.conf.all.accept_source_route = 0
net.core.somaxconn = 65535
net.core.rmem_default = 8388608
net.core.wmem_default = 8388608
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.netdev_max_backlog = 262144
vm.swappiness = 10
vm.overcommit_memory = 1
vm.max_map_count = 262144
EOF
sysctl -p
#将修改后的文件发送至其他节点
$ for i in $(seq 1 4);do rsync -az /etc/security/limits.conf hadoop0${i}:/etc/security/;done
$ for i in $(seq 1 4);do rsync -az /etc/sysctl.conf hadoop0${i}:/etc/;done
# 其余节点需执行下面命令刷新内核参数
$ sysctl -p
配置jdk
自行去oracle官网下载java包 jdk-8u261-linux-x64.tar.gz ,然后上传至服务器。
配置jdk环境的操作,是需要在所有节点进行的。
$ mkdir /apps/usr -p && systemctl stop firewalld && systemctl disable firewalld && setenforce 0
$ tar zxf jdk-8u261-linux-x64.tar.gz -C /apps/usr/
$ ln -sf /apps/usr/jdk1.8.0_261 /apps/usr/jdk
$ cat >> /etc/profile << EOF
export JAVA_HOME=/apps/usr/jdk
export CLASSPATH=\$JAVA_HOME/lib
export PATH=\$JAVA_HOME/bin:\$PATH
EOF
$ source /etc/profile
$ java -version # 查看版本信息
java version "1.8.0_261"
Java(TM) SE Runtime Environment (build 1.8.0_261-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.261-b12, mixed mode)
配置ntp时间同步
在hadoop01配置时间服务器,其他客户端制定定时任务即可。
$ yum -y install ntp
$ vim /etc/ntp.conf # 修改配置文件
# 找到(17行左右):
#restrict 192.168.1.0 mask 255.255.255.0 nomodify notrap
# 改为(网段是你自己的网段,表示允许哪个网段的客户端来同步时间):
restrict 192.168.20.0 mask 255.255.255.0 nomodify notrap
# 找到
server 0.centos.pool.ntp.org iburst
server 1.centos.pool.ntp.org iburst
server 2.centos.pool.ntp.org iburst
server 3.centos.pool.ntp.org iburst
# 将其注释掉,表示不使用互联网上的时间同步服务器
#server 0.centos.pool.ntp.org iburst
#server 1.centos.pool.ntp.org iburst
#server 2.centos.pool.ntp.org iburst
#server 3.centos.pool.ntp.org iburst
# 末尾追加
server 127.127.1.0
fudge 127.127.1.0 stratum 5
# 在上级时钟源失效时,NTP会使用127.127.1.0的本地时钟,将local时间作为ntp服务器时间提供给ntp客户端。
# NTP把本地主机的时钟也看作外部时钟源来处理,分配的地址是127.127.1.0
# 让硬件时间和系统时间一起同步
$ echo 'SYNC_HWCLOCK=yes' >> /etc/sysconfig/ntpd
# 重启ntp服务器生效
$ systemctl restart ntpd && systemctl enable ntpd
# 配置其他客户端定时同步时间(哪些机器要同步上面时间服务器的时间,就进行以下配置)
$ yum -y install ntp
$ crontab -e # 写入以下定时任务
*/5 * * * * /usr/sbin/ntpdate 192.168.20.2 &> /dev/null
部署hadoop
没有特别说明的,以下操作在hadoop01上进行即可。
$ wget https://mirrors.tuna.tsinghua.edu.cn/apache/hadoop/common/current2/hadoop-2.10.1.tar.gz
$ tar zxf hadoop-2.10.1.tar.gz -C /apps/usr/
# 配置hadoop环境变量
$ cat >> /etc/profile << EOF
export HADOOP_HOME=/apps/usr/hadoop-2.10.1
export PATH=\${HADOOP_HOME}/bin:\${HADOOP_HOME}/sbin:\${PATH}
EOF
$ source /etc/profile
$ cd /apps/usr/hadoop-2.10.1/
# 修改hadoop的jdk环境变量
$ sed -i 's/export JAVA_HOME=.*/export JAVA_HOME=\/apps\/usr\/jdk/g' etc/hadoop/hadoop-env.sh
$ vim etc/hadoop/hdfs-site.xml # 修改此文件
<configuration> # 找到此行,写入以下配置
<!--默认副本数为3-->
<property>
<name>dfs.replication</name>
<value>3</value>
</property>
<!--自定义hdfs集群名称-->
<property>
<name>dfs.nameservices</name>
<value>mycluster</value>
</property>
<!--自定义参加集群的NameNode名称,mycluster名称需和上面定义的一致-->
<property>
<name>dfs.ha.namenodes.mycluster</name>
<value>nn1,nn2</value>
</property>
<!--为上面定义的nn1、nn2指定监听的RPC端口-->
<property>
<name>dfs.namenode.rpc-address.mycluster.nn1</name>
<value>hadoop01:9000</value>
</property>
<property>
<name>dfs.namenode.rpc-address.mycluster.nn2</name>
<value>hadoop02:9000</value>
</property>
<!--设置两个namenode的HTTP服务器要监听的地址-->
<property>
<name>dfs.namenode.http-address.mycluster.nn1</name>
<value>hadoop01:50070</value>
</property>
<property>
<name>dfs.namenode.http-address.mycluster.nn2</name>
<value>hadoop02:50070</value>
</property>
<!--指定Jn的监听地址,也就是指定namenode共享数据的存放到哪里-->
<property>
<name>dfs.namenode.shared.edits.dir</name>
<value>qjournal://hadoop01:8485;hadoop02:8485;hadoop03:8485/mycluster</value>
</property>
<!--指定HDFS客户端用来联系Active NameNode的Java类-->
<property>
<name>dfs.client.failover.proxy.provider.mycluster</name>
<value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value>
</property>
<!--指定namenode隔离机制,在故障转移期间用来保护激活的NameNode-->
<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>
<!--关闭权限检查,如果上面指定的用户私钥就是hdfs的启动用户,那么此项不建议配置-->
<property>
<name>dfs.permissions.enable</name>
<value>false</value>
</property>
</configuration>
$ vim etc/hadoop/core-site.xml # 修改此文件
<configuration> # 找到此行,写入以下配置
<!--指定HDFS中NameNode的地址为hdfs-site中定义的集群名称-->
<property>
<name>fs.defaultFS</name>
<value>hdfs://mycluster</value>
</property>
<!--指定Jn的edits相关数据的存放路径-->
<property>
<name>dfs.journalnode.edits.dir</name>
<value>/apps/usr/hadoop-2.10.1/data/tmp/jn/</value>
</property>
<!--指定Hadoop运行时产生文件的存储目录-->
<property>
<name>hadoop.tmp.dir</name>
<value>/apps/usr/hadoop-2.10.1/data/tmp</value>
</property>
</configuration>
# 修改slavaes文件,以便日后可以群起集群,而不是一个个节点去启动
# 下面slaves写的是主机名,每行主机名后不可以有空格,并且必须保证hosts文件里写了正确的解析记录
$ cat > etc/hadoop/slaves << EOF
hadoop01
hadoop02
hadoop03
hadoop04
EOF
# 发送修改好的hadoop目录到其他节点
$ for i in 2 3 4;do rsync -az /apps/usr/hadoop-2.10.1 hadoop0${i}:/apps/usr/;done
# 其他节点执行以下命令,以便配置hadoop的环境变量
$ cat >> /etc/profile << EOF
export HADOOP_HOME=/apps/usr/hadoop-2.10.1
export PATH=\${HADOOP_HOME}/bin:\${HADOOP_HOME}/sbin:\${PATH}
EOF
$ source /etc/profile
启动hdfs集群
注:新集群启动并格式化NameNode后,会产生一个空的fsimage和edits文件,但由于是高可用集群,JournalNode的作用就是来保存及同步两个NameNode的edits文件的,所以在启动及格式化namenode之前,需要先将JournalNode启动,以免出现问题。
启动JournalNode
# 所有JournalNode节点上执行此指令,启动Journalnode
$ hadoop-daemon.sh start journalnode
$ ss -lnptu | grep 8485 # 确定所有JournalNode节点已启动
tcp LISTEN 0 128 *:8485 *:* users:(("java",pid=22753,fd=256))
格式化nn1并启动
在格式化之前,请确保防火墙已关闭或者已放行相关IP的流量。
# 以下操作在nn1执行
$ hdfs namenode -format # 格式化namenode(仅在集群第一次构建完成后需要格式化),输出如下信息表示格式化成功
# .......省略部分输出
21/01/01 23:46:57 INFO util.GSet: 0.029999999329447746% max memory 889 MB = 273.1 KB
21/01/01 23:46:57 INFO util.GSet: capacity = 2^15 = 32768 entries
21/01/01 23:46:57 INFO namenode.FSImage: Allocated new BlockPoolId: BP-1160453677-192.168.20.2-1609516017755
21/01/01 23:46:57 INFO common.Storage: Storage directory /apps/usr/hadoop-2.10.1/data/tmp/dfs/name has been successfully formatted.
21/01/01 23:46:57 INFO namenode.FSImageFormatProtobuf: Saving image file /apps/usr/hadoop-2.10.1/data/tmp/dfs/name/current/fsimage.ckpt_0000000000000000000 using no compression
21/01/01 23:46:57 INFO namenode.FSImageFormatProtobuf: Image file /apps/usr/hadoop-2.10.1/data/tmp/dfs/name/current/fsimage.ckpt_0000000000000000000 of size 323 bytes saved in 0 seconds .
21/01/01 23:46:57 INFO namenode.NNStorageRetentionManager: Going to retain 1 images with txid >= 0
21/01/01 23:46:57 INFO namenode.FSImage: FSImageSaver clean checkpoint: txid = 0 when meet shutdown.
21/01/01 23:46:57 INFO namenode.NameNode: SHUTDOWN_MSG:
/************************************************************
SHUTDOWN_MSG: Shutting down NameNode at hadoop01/192.168.20.2
************************************************************/
$ hadoop-daemon.sh start namenode # 启动nn1
同步nn1的元数据信息并启动nn2
# 如下操作在nn2执行
$ hdfs namenode -bootstrapStandby
$ echo $? # 确认同步成功
0
$ hadoop-daemon.sh start namenode # 启动nn2
查看两个namenode的角色
分别访问两个namenode的50070端口,即可看到当前角色如下:
启动所有DataNode
# 在nn1上执行如下命令,注意执行的是 hadoop-daemons.sh 脚本
$ hadoop-daemons.sh start datanode
访问任意一个NameNode,并点击如下,可以看到所有DataNode节点已ok,即表示无误。
注:此时两个NameNode都是standby状态,故还无法提供任何读写操作。
将nn1切换为Active状态
$ hdfs haadmin -transitionToActive nn1 # 将nn1切换为Active状态
# 分别确认 nn1 和 nn2 的集群状态无误
$ hdfs haadmin -getServiceState nn1
active
$ hdfs haadmin -getServiceState nn2
standby
# HA集群运维指令
$ hdfs haadmin -help # 执行此命令,即可输出其支持的选项
hdfs haadmin支持的命令解释如下:
- transitionToActive 和 transitionToStandby:将指定NameNode的状态转换为Active或Standby,这些子命令分别导致给定的NameNode转换到Active或Standby状态(必须保证当前active状态的NameNode处于启动状态,才可转换成功)。这些命令不试图执行任何防护,因此应该很少使用。相反,应该总是使用
hdfs haadmin -failover
子命令。- failover:在两个namenode之间启动故障转移,这个子命令导致从第一个提供的NameNode到第二个NameNode的故障转移。如果第一个NameNode处于Standby状态,则该命令将第二个NameNode毫无错误地转换为Active状态。如果第一个NameNode处于Active状态,则会尝试将其优雅地转换为Standby状态。如果失败,将依次尝试防护方法(由dfs.ha.fencing.methods配置),直到其中一个成功。只有经过这个过程,第二个NameNode才会被转换为Active状态。如果没有成功的fencing方法,第二个NameNode将不会被转换到Active状态,返回错误。
- getServiceState:确定指定的NameNode是Active还是Standby
- getAllServiceState:返回所有namenode的状态
- checkHealth:检查指定NameNode的运行状况,连接到提供的NameNode以检查其运行状况。NameNode能够对自身执行一些诊断,包括检查内部服务是否按照预期运行。如果NameNode正常运行,该命令将返回0,否则将返回非0。可以将此命令用于监视目的。
至此,hdfs高可用已经配置完成,但集群的Active和Standby故障转移,还是需要手动转移,并且两个NameNode必须处于启动状态,才可转移成功,这样的话,HA的意义并不大,两个NameNode都正常启动着,干嘛要进行故障转移呢?所以接下俩还需配置HDFS-HA的自动故障转移
配置HDFS-HA自动故障转移
自动故障转移为HDFS增加了两个组件:Zookeeper 和 ZKFailoverController(ZKFC)进程,zookeeper是维护少量协调数据,通知客户端这些数据的改变和监视客户端故障的高可用服务。
HA的自动故障转移依赖于Zookeeper的以下功能:
- 故障检测:集群中的每个NameNode在Zookeeper中维护了一个持久会话,如果机器崩溃,Zookeeper中的会话将终止,Zookeeper通知另一个NameNode需要触发故障转移。
- 现役NameNode选择:Zookeeper提供了一个简单的机制用于唯一的选择一个节点为active状态。如果目前现役NameNode崩溃,另一个节点可能从Zookeeper获得特殊的排外锁以表明它应该成为现役NameNode。
ZKFC是自动故障转移中的另一个新组件,是Zookeeper的客户端,也监视和管理NameNode的状态。每个运行NameNode的主机也运行了一个ZKFC进程。
ZKFC负责如下:
- 健康检测:ZKFC使用一个健康检查命令定期地ping与之在相同主机的NameNode,只要该NameNode及时地回复健康状态,ZKFC认为该节点是健康的。如果该节点挂掉,冻结或进入不健康状态,健康检测器标识该节点为非健康的。
- Zookeeper会话管理:当本地NameNode是健康的,ZKFC保持一个在Zookeeper中打开的会话。如果本地NameNode处于Active状态,ZKFC也保持一个特殊的znode锁,该锁使用了Zookeeper对短暂节点的支持,如果会话终止,锁节点将自动删除。
- 基于Zookeeper的选择:如果本地NameNode是健康的,且ZKFC发现没有其他的节点当前持有znode锁,它将为自己获取该锁,如果成功,则它已经赢得了选择,并负责运行故障转移进程以使它的本地NameNode为Active。故障转移进程与前面描述的手动故障转移相似,首先如果必要保护之前的现役NameNode,然后本地NameNode转换为active状态。
关于故障自动转移流程示意图如下:
停止hdfs集群
在开始配置自动故障转移之前,应关闭集群。HDFS无法在集群运行时从手动故障转移设置过渡到自动故障转移设置。
$ stop-dfs.sh
部署Zookeeper集群
以下操作只需在Hadoop01上执行,然后将修改好的配置文件发送至其他节点,在生产中,如果说想减轻机器压力,或者说机器配置没那么高,可以将Zookeeper集群单独部署到其他节点,只要保证指定的地址无误即可。
wget http://archive.apache.org/dist/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz
tar zxf zookeeper-3.4.14.tar.gz -C /apps/usr/
ln -sf /apps/usr/zookeeper-3.4.14 /apps/usr/zk
# 配置
mkdir /apps/usr/zk/data
echo 1 > /apps/usr/zk/data/myid
cat > /apps/usr/zk/conf/zoo.cfg << EOF
dataDir=/apps/usr/zk/data
clientPort=2181
maxClientCnxns=0
tickTime=2000
initLimit=10
syncLimit=5
quorumListenOnAllIPs=true
server.1=hadoop01:2888:3888
server.2=hadoop02:2888:3888
server.3=hadoop03:2888:3888
EOF
# 上面的server可以指定IP+端口
# 注意,server.x :x表示节点id,必须和实际IP对应上,如果不对应,最后查看集群状态时,大概率会看到如下:
# $ /apps/usr/zk/bin/zkServer.sh status
# ZooKeeper JMX enabled by default
# Using config: /apps/usr/zk/bin/../conf/zoo.cfg
# Error contacting service. It is probably not running. # 尽管端口都在监听,但状态是没运行
# 现在接着配置
# 将zookeeper目录发送至其他节点
$ cd /apps/usr/
$ for i in 2 3;do rsync -az /apps/usr/zookeeper-3.4.14 hadoop0${i}:/apps/usr/;done
# 启动hadoop01节点的zookeeper
$ /apps/usr/zk/bin/zkServer.sh start
# 只有leader才会监听28888
ss -lnpt | egrep '2181|3888|2888'
修改hadoop02节点的配置文件
ln -sf /apps/usr/zookeeper-3.4.14/ /apps/usr/zk
echo 2 > /apps/usr/zk/data/myid
修改hadoop03节点的配置文件
ln -sf /apps/usr/zookeeper-3.4.14/ /apps/usr/zk
echo 3 > /apps/usr/zk/data/myid
执行以下指令启动各个zookeeper节点
$ /apps/usr/zk/bin/zkServer.sh start
ss -lnpt | egrep '2181|3888|2888' # 确定端口在监听
# 别忘了,只有leader才会监听2888
$ /apps/usr/zk/bin/zkServer.sh status # 查看集群状态(应该有一个leader,两个follower)
ZooKeeper JMX enabled by default
Using config: /apps/usr/zk/bin/../conf/zoo.cfg
Mode: follower
至此,zookeeper集群部署完成。
配置hdfs
$ vim etc/hadoop/hdfs-site.xml # 编辑hdfs-site配置文件
<!--开启自动故障转移-->
<property>
<name>dfs.ha.automatic-failover.enabled</name>
<value>true</value>
</property>
$ vim etc/hadoop/core-site.xml # 编辑 core-site配置文件
<!--指定Zookeeper集群地址-->
<property>
<name>ha.zookeeper.quorum</name>
<value>hadoop01:2181,hadoop02:2181,hadoop03:2181</value>
</property>
# 发送修改好的hadoop配置文件目录到其他节点
$ for i in 2 3 4;do rsync -az /apps/usr/hadoop-2.10.1/etc/hadoop/ hadoop0${i}:/apps/usr/hadoop-2.10.1/etc/hadoop/;done
初始化HA状态
# 在任意一个NameNode节点上执行即可
$ hdfs zkfc -formatZK # 在ZooKeeper中初始化HA状态,看到如下输出则表示初始化成功
# 这将在ZooKeeper中创建一个znode,自动故障转移系统将在其中存储其数据。
# ....... 省略部分输出
21/01/02 02:26:27 INFO zookeeper.ClientCnxn: Socket connection established to hadoop03/192.168.20.4:2181, initiating session
21/01/02 02:26:27 INFO zookeeper.ClientCnxn: Session establishment complete on server hadoop03/192.168.20.4:2181, sessionid = 0x30001215a990000, negotiated timeout = 10000
21/01/02 02:26:27 INFO ha.ActiveStandbyElector: Successfully created /hadoop-ha/mycluster in ZK.
21/01/02 02:26:27 INFO ha.ActiveStandbyElector: Session connected.
21/01/02 02:26:27 INFO zookeeper.ZooKeeper: Session: 0x30001215a990000 closed
21/01/02 02:26:27 INFO zookeeper.ClientCnxn: EventThread shut down for session: 0x30001215a990000
21/01/02 02:26:27 INFO tools.DFSZKFailoverController: SHUTDOWN_MSG:
/************************************************************
SHUTDOWN_MSG: Shutting down DFSZKFailoverController at hadoop01/192.168.20.2
************************************************************/
查看Zookeeper集群是否多了目录数据
$ /apps/usr/zookeeper-3.4.14/bin/zkCli.sh # 在Zookeeper机器上登录到Zookeeper中(也可使用ZooInspector 等客户端工具)
[zk: localhost:2181(CONNECTED) 1] ls / # 查看 /
[zookeeper, hadoop-ha]
[zk: localhost:2181(CONNECTED) 2] ls /hadoop-ha # 查看hadoop-ha目录,确认多了此目录
[mycluster]
注:同一个Zookeeper集群可以管理多个HDFS集群,但前提是HDFS集群名称不可冲突。如果你的Zookeeper集群开启了认证访问,可以参考官方文档进行配置认证访问Zookeeper。
安装fuser命令
由于在后期进行故障转移时,zkfc会连接到旧的active状态的NameNode进行补刀(再kill一次其NameNode进程,然后自己才会成为新的Active状态的NameNode),在此过程中会使用到fuser命令,需要提前安装,否则在进行故障转移时,zkfc日志中会报错如下:
$ tail /apps/usr/hadoop-2.10.1/logs/hadoop-root-zkfc-lv.log
# 安装fuser指令,所有NameNode节点上都需要执行
$ yum -y install psmisc-22.20-17.el7.x86_64
$ which fuser # 确认命令已存在
/usr/sbin/fuser
启动HDFS节点
$ start-dfs.sh # 在任意一个NameNode节点群起集群
确认集群启动成功
$ tail /apps/usr/hadoop-2.10.1/logs/hadoop-root-zkfc-lv.log # 查看zkfc的日志输出,是否成功选出了处于Active的NameNode
2021-01-02 02:44:02,872 INFO org.apache.hadoop.ipc.Server: IPC Server listener on 8019: starting
2021-01-02 02:44:02,971 INFO org.apache.hadoop.ha.HealthMonitor: Entering state SERVICE_HEALTHY
2021-01-02 02:44:02,971 INFO org.apache.hadoop.ha.ZKFailoverController: Local service NameNode at hadoop02/192.168.20.3:9000 entered state: SERVICE_HEALTHY
2021-01-02 02:44:02,983 INFO org.apache.hadoop.ha.ActiveStandbyElector: Checking for any old active which needs to be fenced...
2021-01-02 02:44:02,992 INFO org.apache.hadoop.ha.ActiveStandbyElector: Old node exists: 0a096d79636c757374657212036e6e311a086861646f6f70303120a84628d33e
2021-01-02 02:44:02,994 INFO org.apache.hadoop.ha.ZKFailoverController: Should fence: NameNode at hadoop01/192.168.20.2:9000
2021-01-02 02:44:03,014 INFO org.apache.hadoop.ha.ZKFailoverController: Successfully transitioned NameNode at hadoop01/192.168.20.2:9000 to standby state without fencing
2021-01-02 02:44:03,014 INFO org.apache.hadoop.ha.ActiveStandbyElector: Writing znode /hadoop-ha/mycluster/ActiveBreadCrumb to indicate that the local node is the most recent active...
2021-01-02 02:44:03,021 INFO org.apache.hadoop.ha.ZKFailoverController: Trying to make NameNode at hadoop02/192.168.20.3:9000 active...
2021-01-02 02:44:03,453 INFO org.apache.hadoop.ha.ZKFailoverController: Successfully transitioned NameNode at hadoop02/192.168.20.3:9000 to active state
# 从日志输出中可以看到,hadoop02为active状态。
访问hadoop02,确认其为Active状态。
访问hadoop01,确认其为Standby状态。
至此,HDFS的自动故障转移就配置完成了,可以自行停止当前Active状态的NameNode,然后确认Standby的NameNode可以顺利成为Active状态。
确认集群读写文件、故障自动转移正常
$ hdfs dfs -mkdir -p /ha/test/ # 创建测试目录
$ hdfs dfs -put zookeeper-3.4.14.tar.gz /ha/test/ # 上传文件
访问当前Active状态的NameNode,确认数据存在,如下:
为什么非要访问处于Active状态的NameNode呢?是因为Standby状态的NameNode是不允许读的,你会看到如下错误:
# 手动干掉当前处于Active状态的NameNode
$ jps | grep NameNode | cut -d ' ' -f1 | xargs kill -9
# 稍后可以执行如下命令启动NameNode
$ hadoop-daemon.sh start namenode
确认原先处于Standby的节点为Active状态:
确认刚刚上传的文件可以在新的Active状态的NameNode看到,如下:
好,至此即可验证,hdfs的高可用及自动故障转移配置无误。接下来配置YARN的高可用。
配置YARN高可用
参考:官方文档。
RM (ResourceManager)负责跟踪集群中的资源,调度应用程序(如MapReduce任务)。在Hadoop 2.4之前,ResourceManager是YARN集群中的单点故障。高可用性以主备ResourceManager对的形式增加冗余,消除了这种单点故障。
其高可用架构图如下:
ResourceManager HA通过Active/Standby体系结构实现在任何时间点,RM之一都处于Active状态,并且一个或多个RM处于Standby模式。
如果未启用自动故障转移,则管理员必须手动将其中一个RM转换为Active。要从一个RM到另一个RM进行故障转移,他们应该首先将Active-RM转换为Standby,然后将Standby-RM转换为Active。所有这些都可以使用yarn rmadmin
CLI完成。
RM可以选择嵌入基于Zookeeper的ActiveStandbyElector,以确定哪个RM应该是Active。当Active发生故障或无响应时,另一个RM将自动选作Active,然后接管。请注意,无需像HDFS那样运行单独的ZKFC守护程序,因为嵌入在RM中的ActiveStandbyElector充当故障检测器和领导者选举者,而不是单独的ZKFC守护进程。
当有多个RM时,预期客户端和节点使用的配置(yarn-site.xml)会列出所有RM。客户端,ApplicationMaster(AM)和NodeManager(NM)尝试以循环方式连接到RM,直到它们到达活动RM。如果Active服务器出现故障,他们将继续轮询,直到命中“新”Active服务器为止。
修改yarn-site.xml配置文件
注:以下配置只需在hadoop01上进行修改,修改完成后,分发到其他节点即可。
$ cat yarn-site.xml # 完整配置文件内容如下
<?xml version="1.0"?>
<configuration>
<!--Reducer获取数据的方式-->
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<!--开启日志聚集功能-->
<property>
<name>yarn.log-aggregation-enable</name>
<value>true</value>
</property>
<!--日志保留时间设置为7天-->
<property>
<name>yarn.log-aggregation.retain-seconds</name>
<value>604800</value>
</property>
<!--开启rm的ha功能-->
<property>
<name>yarn.resourcemanager.ha.enabled</name>
<value>true</value>
</property>
<!--指定yarn集群id-->
<property>
<name>yarn.resourcemanager.cluster-id</name>
<value>cluster-yarn1</value>
</property>
<!--指定参与集群的名称,可以指定两个以上-->
<property>
<name>yarn.resourcemanager.ha.rm-ids</name>
<value>rm1,rm2</value>
</property>
<!--指定实际yarn集群中的所有节点地址-->
<property>
<name>yarn.resourcemanager.hostname.rm1</name>
<value>hadoop03</value>
</property>
<property>
<name>yarn.resourcemanager.hostname.rm2</name>
<value>hadoop04</value>
</property>
<!--指定各个节点的yarn监听端口-->
<property>
<name>yarn.resourcemanager.webapp.address.rm1</name>
<value>hadoop03:8088</value>
</property>
<property>
<name>yarn.resourcemanager.webapp.address.rm2</name>
<value>hadoop04:8088</value>
</property>
<!--指定Zookeeper集群的监听地址-->
<property>
<name>yarn.resourcemanager.zk-address</name>
<value>hadoop01:2181,hadoop02:2181,hadoop03:2181</value>
</property>
<!--启用自动恢复-->
<property>
<name>yarn.resourcemanager.recovery.enabled</name>
<value>true</value>
</property>
<!--指定resourcemanager的状态信息存储在zookeeper集群中-->
<property>
<name>yarn.resourcemanager.store.class</name>
<value>org.apache.hadoop.yarn.server.resourcemanager.recovery.ZKRMStateStore</value>
</property>
<!--是否启动一个线程检查每个任务正使用的物理内存量,如果任务超出分配值,则直接将其杀掉,默认是true-->
<property>
<name>yarn.nodemanager.pmem-check-enabled</name>
<value>false</value>
</property>
<!--是否启动一个线程检查每个任务正使用的虚拟内存量,如果任务超出分配值,则直接将其杀掉,默认是true-->
<property>
<name>yarn.nodemanager.vmem-check-enabled</name>
<value>false</value>
</property>
</configuration>
# 配置mapred-site.xml
$ vim etc/hadoop/mapred-site.xml # 编辑此配置文件,以下是完整配置文件
$ cat mapred-site.xml
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<!-- 指定MapReduce运行时框架运行在YARN上,默认是local -->
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
<!--指定历史服务器端地址-->
<property>
<name>mapreduce.jobhistory.address</name>
<value>hadoop04:10020</value>
</property>
<!--历史服务器web端地址-->
<property>
<name>mapreduce.jobhistory.webapp.address</name>
<value>hadoop04:19888</value>
</property>
</configuration>
# 将修改后的配置文件发送到其他所有节点
$ for i in 2 3 4;do rsync -az /apps/usr/hadoop-2.10.1/etc/hadoop/ hadoop0${i}:/apps/usr/hadoop-2.10.1/etc/hadoop/;done
启动yarn角色
# 在hadoop03上执行
$ start-yarn.sh # 执行群起脚本后可以发现,它并没有像hdfs一样,启动集群中的所有resourcemanager
starting yarn daemons
starting resourcemanager, logging to /apps/usr/hadoop-2.10.1/logs/yarn-root-resourcemanager-lv.out
hadoop04: starting nodemanager, logging to /apps/usr/hadoop-2.10.1/logs/yarn-root-nodemanager-lv.out
hadoop01: starting nodemanager, logging to /apps/usr/hadoop-2.10.1/logs/yarn-root-nodemanager-lv.out
hadoop03: starting nodemanager, logging to /apps/usr/hadoop-2.10.1/logs/yarn-root-nodemanager-lv.out
hadoop02: starting nodemanager, logging to /apps/usr/hadoop-2.10.1/logs/yarn-root-nodemanager-lv.out
# 故需要在集群中的其他resourcemanager上(hadoop04)启动yarn
$ yarn-daemon.sh start resourcemanager
starting resourcemanager, logging to /apps/usr/hadoop-2.10.1/logs/yarn-root-resourcemanager-lv.out
访问yarn的web界面
访问任意yarn节点的8088端口即可,它都会自动重定向到当前处于Active状态的节点,如下(需要你的电脑配置hosts记录,否则会访问不到相应的yarn节点):