HADOOPHA场景下,即使Active节点发生故障,系统也不会自动触发从Active到Standby的故障转移。
需要进行手动的故障转移。 手动故障转移显然不是我们所需要的解决方案。 为了实现自动故障转移,
需要引入两个新组件:ZooKeeper和ZKFailoverController(ZKFC)进程。
Apache ZooKeeper是一种高可用性服务,用于维护少量协调数据,通知客户端该数据的更改以及监视客户端
是否存在故障。自动故障转移的实现依赖于ZooKeeper来实现以下功能:
-
故障检测:集群中的每个NameNode在ZooKeeper中维护了一个持久会话,如果机器崩溃,
ZooKeeper中的会话将终止,ZooKeeper通知另一个NameNode需要触发故障转移。 -
Active NameNode选举ZooKeeper提供了一个简单的机制用于唯一的选择一个节点为active状态。
如果当前Active节点崩溃,则另一个节点可能从ZooKeeper获得特殊的排他锁以表明它成为下一个Active节点。
ZKFC是自动故障转移中的另一个新组件,是ZooKeeper的客户端,也监视和管理NameNode的状态。
每个运行NameNode的主机也运行了一个ZKFC进程,ZKFC负责:
1)健康监测:ZKFC使用一个健康检查命令定期地ping与之在相同主机的NameNode,只要该NameNode及时地回复
健康状态,ZKFC认为该节点是健康的。如果该节点崩溃,冻结或进入不健康状态,健康监测器标识该节点为非
健康的。
2)ZooKeeper会话管理:当本地NameNode是健康的,ZKFC保持一个在ZooKeeper中打开的会话。
如果本地NameNode处于active状态,ZKFC也保持一个特殊的znode锁,该锁使用了ZooKeeper对短暂节点的支持,
如果会话终止,锁节点将自动删除。
3)基于ZooKeeper的选举(word文档):如果本地NameNode是健康的,且ZKFC发现没有其它的节点当前持有znode锁,
它将为自己获取该锁。如果成功,则它已经赢得了选举,并通过RPC将它的本地NameNode的状态置为active。
必要时对先前的Active进行隔离(Fence),然后本地NameNode转换为活动状态。
注意:
1)DN同时向active和standby发送心跳和块报告
2)ACTIVE NN将操作记录写到自己的edit log ,同时将edit log写入JN集群。每个JN都会写。
3)STANDBY NN:?同时接收JN集群的日志(随机选个写入成功的JN节点读),重演edits log操作,使得自己的元数据和active nn节点保持一致。
4)在激活新的Active NN之前,会对旧的Active NN进行隔离操作防止脑裂。(kill掉)
安装zookeeper:单机模式
a.下载zookeeper3.4.6.tar.gz 通过挂载盘上传
b.解压至【/home/crx/soft】
进入soft文件夹下解压
[crx@master soft]$ tar -zxvf zookeeper-3.4.6.tar.gz
c.创建软连接:
>
l
n
−
s
z
o
o
k
e
e
p
e
r
3.4.6
/
z
o
o
k
e
e
p
e
r
d
.
配
置
环
境
变
量
:
在
/
.
b
a
s
h
p
r
o
f
i
l
e
中
追
加
e
x
p
o
r
t
Z
O
O
K
E
E
P
E
R
H
O
M
E
=
/
h
o
m
e
/
c
r
x
/
s
o
f
t
/
z
o
o
k
e
e
p
e
r
e
x
p
o
r
t
P
A
T
H
=
>ln -s zookeeper3.4.6/ zookeeper d.配置环境变量:在~/.bash_profile中追加 export ZOOKEEPER_HOME=/home/crx/soft/zookeeper export PATH=
>ln−szookeeper3.4.6/zookeeperd.配置环境变量:在 /.bashprofile中追加exportZOOKEEPERHOME=/home/crx/soft/zookeeperexportPATH=ZOOKEEPER_HOME/bin:$PATH
[crx@master ~]$ source ~/.bash_profile
e.进入安装目录:/home/crx/soft/zookeeper-3.4.6/conf
复制一份 zoo_sample.cfg 为zoo.cfg
[crx@master conf]$ cp zoo_sample.cfg zoo.cfg
修改【{ZOOKEEPER_HOME}/conf/zoo.cfg】zoo.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/home/crx/tmp/zookeeper
clientPort=2181
f.开启zookeeper Server
$>zkServer.sh start
$>jps
5914 QuorumPeerMain //QuorumPeerMain:zookeeper Server的守护进程
5931 Jps
g.客户端连接:
$>zkCli.sh -server 127.0.0.1:2181
5988 ZooKeeperMain //ZooKeeperMain:zookeeper client的守护进程
$>quit 退出客户端
h.关机Zookeeper Server
$>zkServer.sh stop
i.查看:zookeeper.out /home/crx/tmp/zookeeper
1.zookeeper集群模式:
a.[修改zoo.cfg文件]:
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/home/crx/tmp/zookeeper
clientPort=2181
server.1=master:2888:3888
server.2=slave1:2888:3888
server.3=slave2:2888:3888
b.在/home/crx/tmp/zookeeper目录下,创建myid文件
>
e
c
h
o
"
1
"
>
>
m
y
i
d
/
/
在
m
a
s
t
e
r
节
点
c
.
将
z
o
o
k
e
e
p
e
r
复
制
到
其
它
两
个
节
点
[
c
r
x
@
m
a
s
t
e
r
s
o
f
t
]
>echo "1" >> myid //在master节点 c.将zookeeper复制到其它两个节点 [crx@master soft]
>echo"1">>myid//在master节点c.将zookeeper复制到其它两个节点[crx@mastersoft] scp -r zookeeper-3.4.6 crx@slave1:~/soft/
[crx@master soft]$ scp -r zookeeper-3.4.6 crx@slave2:~/soft/
分别创建zookeeper软链接 ln -s zookeeper-3.4.6 zookeeper
d.复制dataDir=/home/crx/tmp/zookeeper
$> scp -r ~/tmp/zookeeper/ crx@slave1:~/tmp/
$> scp -r ~/tmp/zookeeper/ crx@slave2:~/tmp/
e.复制环境变量
$> scp ~/.bash_profile crx@slave1:~/
$> scp ~/.bash_profile crx@slave2:~/
f.修改myid文件
slave1进入到zookeeper目录下创建myid 内容为2
slave2进入到zookeeper目录下创建myid 内容为3
分别三个节点启动【master slave1 slave2】zkServer.sh start
分别查看状态:
[crx@master conf]$ zkServer.sh status
[crx@slave1 conf]$ zkServer.sh status
[crx@slave2 conf]$ zkServer.sh status
设置自动容灾(官)
启动集群查50070 , 测试kill -9 active所在的节点
【修改hdfs-site.xml】
dfs.ha.automatic-failover.enabled
true
【修改core-site.xml】
ha.zookeeper.quorum
master:2181,slave1:2181
将配置文件同步至所有节点:
[crx@master ~]$ scp -r ~/soft/hadoop/etc/hadoop/hdfs-site.xml crx@slave1:~/soft/hadoop/etc/hadoop/
[crx@master ~]$ scp -r ~/soft/hadoop/etc/hadoop/hdfs-site.xml crx@slave2:~/soft/hadoop/etc/hadoop/
[crx@master ~]$ scp -r ~/soft/hadoop/etc/hadoop/core-site.xml crx@slave1:~/soft/hadoop/etc/hadoop/
[crx@master ~]$ scp -r ~/soft/hadoop/etc/hadoop/core-site.xml crx@slave2:~/soft/hadoop/etc/hadoop/
3.格式化zk
分别将所有节点的zkServer开启
[crx@master ~]$ zkServer.sh start
[crx@slave1 ~]$ zkServer.sh start
[crx@slave2 ~]$ zkServer.sh start
如果手动管理群集上的服务,则需要在运行NameNode的每台计算机上手动启动zkfc守护程序。
您可以通过运行以下命令来启动守护程序:
[hdfs] $ $ HADOOP_PREFIX / sbin / hadoop-daemon.sh --script $ HADOOP_PREFIX / bin / hdfs start zkfc
实例化hadoop和zookeeper的状态(让它们两个认识,一定要先开启zkServer)
[master]$>hdfs zkfc -formatZK
说明:将在Zookeeper的树状节点上注册一个Znode
4.确保zookeeper集群是开启状态:
$>jps
5458 QuorumPeerMain
5.start-dfs.sh
注意:nn1和nn2均要配置相同的无密登录;!!!!!!
6.测试:
将active的节点kill掉,查看另一个节点状态;
[crx@master hadoop]$ jps
16352 NameNode
16711 DFSZKFailoverController
16776 Jps
16203 JournalNode
16476 DataNode
12543 QuorumPeerMain
[crx@master hadoop]$ kill -9 16711
HA启动过程的顺序
step1:开启zookeeper服务:
$>zkServer.sh start
(略)step2:开启hdfs:
【$>start-dfs.sh //说明:namenode-->datanode-->journalnode-->zkfc】
step3:开启journalnode守护进程(在qjournal协议指定的节点上执行):
$>hadoop-daemon.sh start journalnode
step4:开启namenode守护进程(在nn1和nn2上执行)
$>hadoop-daemon.sh start namenode
step5:开启datanode守护进程(在namenode节点上执行hadoop-daemons.sh开启所有datanode节点)
$>hadoop-daemons.sh start datanode
step6:开启zkfc守护进程
$>hadoop-daemon.sh --script $HADOOP_PREFIX/bin/hdfs start zkfc
守护进程的说明:
5458 QuorumPeerMain 【zookeeper Server端】
7715 Jps 【java进程】
7626 DFSZKFailoverController 【ZKFC:设置自动故障转移】
7324 JournalNode 【QJM:日志节点进程】
7405 NameNode 【名称节点】
6015 DataNode 【数据节点】
以一个简单的例子来说明整个选举的过程.
假设有五台服务器组成的zookeeper集群,它们的id从1-5,同时它们都是最新启动的,也就是没有历史数据,
在存放数据量这一点上,都是一样的.假设这些服务器依序启动,来看看会发生什么.
-
服务器1启动,此时只有它一台服务器启动了,它发出去的报没有任何响应,所以它的选举状态一直是LOOKING状态
-
服务器2启动,它与最开始启动的服务器1进行通信,互相交换自己的选举结果,由于两者都没有历史数据,
所以id值较大的服务器2胜出(Leader id:就是我们配置的myid中的值,每个机器一个),
但是由于没有达到超过半数以上的服务器都同意选举它(这个例子中的半数以上是3),所以服务器1,2还是继续保持LOOKING状态. -
服务器3启动,根据前面的理论分析,服务器3成为服务器1,2,3中的老大,而与上面不同的是,此时有三台服务器选举了它,
所以它成为了这次选举的leader. -
服务器4启动,根据前面的分析,理论上服务器4应该是服务器1,2,3,4中最大的,但是由于前面已经有半数以上的服务器选举了服务器3,
所以它只能接收当小弟的命了. -
服务器5启动,同4一样,当小弟.
集群服务器宕掉的情况, 半数以上服务器宕机代表zookeeper服务器宕机
例如我们集群服务器有3台, 其中有一台zkServer宕机,那么整个ZK还是可以服务的
如果2台服务器,1台宕机是就是不可用的
案例演示 分别启动zkServer.sh start(master slave1 slave2)
1.查看选举状态zkServer.sh status(分别三台节点查看)
2.jps查看leader的状态 kill掉leader所在的节点
3.再次查看 slave2应该是leader
4.再次启动slave1的zkServer.sh start 它只能当小弟了
搭建zookeeper伪分布模式,采用了多线程的方式来模拟分布式环境
a.创建zoo*.cfg配置文件【创建三个文件:分别为5,6,7】
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/home/crx/tmp/zookeeper/data*
clientPort=218*
server.5=master:2888:3888
server.6=master:2889:3889
server.7=master:2890:3890
注:在【/home/crx/tmp/zookeeper/data*】目录下创建对应的myid文件
b.开启server端:
$>zkServer.sh start zoo*.cfg
c.开启客户端连接zookeeper集群:
$>zkCli.sh -server master:2181,master:2182,master:2183
***集群模式下,首先开启Zookeeper集群
http://zookeeper.apache.org/doc/current/zookeeperProgrammers.html
2.zookeeper维护了一份分布式文件系统,每个节点称为“Znode”,Znode维护数据和与之关联的子节点;
Znodes维护一个状态结构:
【znode:/根节点stat说明
cZxid = 0x0 //create事务ID
ctime = Wed Dec 31 16:00:00 PST 1969 //当前节点的create时间
mZxid = 0x0 //当前节点的修改事务ID
mtime = Wed Dec 31 16:00:00 PST 1969 //当前节点的时间
pZxid = 0x900000018 //最后修改此znode的子项的更改的zxid
cversion = 4 //此znode的子项的更改数
dataVersion = 0 //此znode数据的更改次数(版本)
aclVersion = 0 //ACL(访问控制列表)的版本
ephemeralOwner = 0x0 //表示当前节点是否为临时节点,如有数据为临时节点;如为0X0,代表当前节点的永久节点;
dataLength = 0 //表示当前节点的数据长度,默认情况下,每个Znode节点可存放1M的数据
numChildren = 2 //表示当前节点拥有的子节点数
】
3. 测试kill -9 leader所在的节点
zookeeper数据模型
1.会话
$ bin/zkCli.sh -server 127.0.0.1:2181
创建znode 临时,永久等
$ bin/zkCli.sh -server slave1:2181
$ bin/zkCli.sh -server master:2181,slave1:2181
close 关闭当前Zk连接会话
$>close
connect host:port 连接其他的Zookeeper服务端:
$>connect slave1:2181
1.创建永久节点,不加s和加s的区别,序号
[zk: master:2181(CONNECTED) 1] create /aaa1 helloword
Created /aaa1
[zk: master:2181(CONNECTED) 2] ls /
[zookeeper, aaa1, hadoop-ha]
[zk: master:2181(CONNECTED) 3] create -s /aaa2 helloword2
Created /aaa20000000023
2.zookeeper当中不能往znode中去追加内容,只能覆盖
set /aaa20000000023 newwords
3.创建临时znode (临时节点不能有子节点)
create -e /path data
4.删除znode(删除znode时,不能有子znode,需要先删除子znode再删除父znode)
delete /path
5.rmr 删除所有znode,可以包含子路径
ZooKeeper中的节点有两种,分别为临时节点和永久节点。节点的类型在创建时即被确定,并且不能改变。
① 临时节点:该节点的生命周期依赖于创建它们的会话。一旦会话(Session)结束,临时节点将被自动删除,
当然可以也可以手动删除。虽然每个临时的Znode都会绑定到一个客户端会话,但他们对所有的客户端还是可见的。
另外,ZooKeeper的临时节点不允许拥有子节点。
② 永久节点:该节点的生命周期不依赖于会话,并且只有在客户端显示执行删除操作的时候,他们才能被删除。
顺序节点
当创建Znode的时候,用户可以请求在ZooKeeper的路径结尾添加一个递增的计数。
这个计数对于此节点的父节点来说是唯一的,它的格式为"%10d"(10位数字,没有数值的数位用0补充,例如"0000000001")。
当计数值大于232-1时,计数器将溢出。
观察
客户端可以在节点上设置watch,我们称之为监视器。
当节点状态发生改变时(Znode的增、删、改)将会触发watch所对应的操作。
当watch被触发时,ZooKeeper将会向客户端发送且仅发送一条通知,
因为watch只能被触发一次,这样可以减少网络流量。
监听/path下的变化
stat /path true
****运行项目报错 ****
1.将log4j.properties拷贝到项目下,路径不能放错
2.将hadoop jar包引入项目下
Zookeeper API
1.API 测试:
//创建zookeeper对象
//192.168.179.103:2181 192.168.179.103 master
ZooKeeper zk = new ZooKeeper("192.168.179.103:2181",5000,null);
//创建永久znode create /ccc1 hellowordccc
// zk.create("/ccc2", “hellowordccc”.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
//创建永久znode create -s /ccc1 hellowordccc
// zk.create("/ccc2", “hellowordccc”.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
//创建临时znode create -e /ccc3 hellowordccc
// zk.create("/ccc3", “hellowordccc”.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
//创建临时znode zonde有序列 create -e /ccc3 hellowordccc
// zk.create("/ccc4", “hellowordccc”.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
// Thread.sleep(10000L);
//设置要给版本 set /ccc1 "new ccc1"
// zk.setData("/ccc1", "new ccc2 ".getBytes(), 1);
// ls /
// List paths = zk.getChildren("/ccc1", null);
// for(String path : paths) {
// System.out.println(path);
// }
//没有子路径的znode删除
// zk.delete("/ccc1", -1);
//有子路径的znode删除
// List paths = zk.getChildren("/ccc1", null);
// for(String path : paths) {
// zk.delete("/ccc1/"+path, -1);
// }
// zk.delete("/ccc1", -1);
zk.close();
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
String host = "192.168.179.136:2181";
ZooKeeper zKeeper = new ZooKeeper(host, 2000,null);
zKeeper.create("/bbb", "bbbword".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
zKeeper.close();
}
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
String host = "192.168.179.135:2181";
ZooKeeper zKeeper = new ZooKeeper(host, 2000,null);
//删除目录不能有子节点
zKeeper.delete("/aaaccc", 1);
Thread.sleep(10000L);
zKeeper.close();
}
// 版本 -1 ,可以不指定版本
zKeeper.setData("/test2", "ok1231212".getBytes(), 1);
[zk: master:2181(CONNECTED) 8] get /test2
ok1231212
byte[] byts = zk.getData("/a2", null, null);
System.out.println(new String(byts));
List<String> list = zKeeper.getChildren("/", null);
for(String znode : list) {
System.out.println(znode);
}
List<String> childres = zk.getChildren("/a2", null);
for(String path : childres) {
zk.delete("/a2/"+path, -1);
}
zk.delete("/a2", -1);
System.out.println("OK");
[zk: master:2181,slave1:2181(CONNECTED) 36] ls /a4
[aaa]
[zk: master:2181,slave1:2181(CONNECTED) 37] rmr /a4
Stat stat = zkClient.exists("/eclipse", false);
System.out.println(stat==null?"not exist":"exist");
实现观察者要么事先接口, 要么内部类
watcher(观察者 案例) zk客户端连接服务端触发一次事件(构造函数连接zk的服务端), 所以执行一次process
命令行: stat /test2 true
测试:stat命令可以得以一个znode节点的属性,并允许我们在已经存在的znode节点上设置监视点.
通过在路径后面设置参数true来添加监视点,当活动的主节点崩溃时, 我们会观察到.
$>stat /master true
$>set /master 2222222helloword
创建观察者两种方式,1创建 内部类 2实现Watcher接口
2.watcher(观察者)
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
Watcher wc = new Watcher(){
@Override
public void process(WatchedEvent event) {
System.out.println("观察者正在执行"+event.getType()+" " +event.toString());
}
};
String host = "192.168.179.135:2181";
ZooKeeper zKeeper = new ZooKeeper(host, 2000,wc);
//Thread.sleep(10000L);
zKeeper.setData("/test2", "ok3666".getBytes(), 2);
System.out.println("设置test2");
}