14 November 2017

配置环境

在下面 4 台虚拟机上搭建 zookeeper 服务器,对这 4 台虚拟机的要求是

  • 关闭了 selinux
  • 已经配置 ssh 可以在本地直接免密码登录到 root

接下来会进行 zookeeper 的运行,崩溃,以及恢复等情况。本文是在边实验,边记录完成的

➜ vi /etc/hosts
192.168.99.120   fedora-0
192.168.99.121   fedora-1
192.168.99.122   fedora-2
192.168.99.123   fedora-3

实验中用到了 ansible

➜ vi /etc/ansible/hosts
[fedora]
fedora-0
fedora-1
fedora-2
fedora-3

安装 zookeeper,以及 iptables (这个主要用来模拟网络中断)

➜ ansible fedora -uroot -a "yum install zookeeper iptables -y"
==> 这里解释下,ansible 是一个批量执行工具
==> ansible fedora 中的 fedora 就是我们在 /etc/ansible/hosts 上定义好的 4 个服务器的集合
==> -uroot 表示执行虚拟机命令的用户是 root
==> -a     表示我们要执行的命令
==> 上面的指令就会让 yum install zookeeper iptables -y 这条命令在 4 个服务器上一起执行

==> ansible 也执行单独执行
==> ansible fedora-0 xxx 表示单独在 fedora-0 这台机子上执行

配置 zookeeper

➜ fedora-0
echo 1 > /var/lib/zookeeper/data/myid

➜ fedora-1
echo 2 > /var/lib/zookeeper/data/myid

➜ fedora-2
echo 3 > /var/lib/zookeeper/data/myid

➜ fedora-2
echo 4 > /var/lib/zookeeper/data/myid

➜ ansible fedora -uroot -a "\cp -f /etc/zookeeper/zoo_sample.cfg /etc/zookeeper/zoo.cfg"

在这里需要手动去 4 台服务器上的 /etc/zookeeper/zoo.cfg 文件里面添加

cat >> /etc/zookeeper/zoo.cfg <<EOF

server.1=192.168.99.120:2888:3888
server.2=192.168.99.121:2888:3888
server.3=192.168.99.122:2888:3888
server.4=192.168.99.123:2888:3888
EOF

运行 zookeeper

执行到这里,我们就可以启动 zookeeper 服务器了

➜ ansible fedora -uroot -a "iptables -F"       <== 清除防火墙信息
➜ ansible fedora -uroot -a "zkCleanup.sh -n 3"
➜ ansible fedora -uroot -a "systemctl restart zookeeper"
➜ ansible fedora -uroot -a "systemctl status zookeeper"

在本机,分别查看各个服务器的 zookeeper 运行状态 (这里 node count 为什么是 5)

➜ echo stat | nc fedora-0 2181
...
Model: follower
Node count: 5

➜ echo stat | nc fedora-1 2181
...
Model: follower
Node count: 5

➜ echo stat | nc fedora-2 2181
...
Model: follower
Node count: 5

➜ echo stat | nc fedora-3 2181
...
Model: leader
Node count: 5

随便连接一台

➜ zkCli -server fedora-0:2181
[zk: fedora-0:2181(CONNECTED) 0] ls /
[zookeeper]
[zk: fedora-0:2181(CONNECTED) 1] create /my-test test
Created /my-test
[zk: fedora-0:2181(CONNECTED) 2] get /my-test
test
...

再连接一台

➜ zkCli -server fedora-3:2181
[zk: fedora-0:2181(CONNECTED) 2] get /my-test
test
...

可以看到数据是一致的

模拟 follower 中断

一台崩溃

可以看到 fedora-3 这台主机是 leader,其他是 follower
我们以 fedora-0 这个 follower 为例,来模拟 follower 中断

模拟的过程很简单,就是利用 iptables 把他禁止了

➜ ansible fedora-1,fedora-2,fedora-3 -uroot -a "iptables -A INPUT -s 192.168.99.120 -j DROP"
➜ ansible fedora-1,fedora-2,fedora-3 -uroot -a "iptables -A INPUT -d 192.168.99.120 -j DROP"
➜ echo stat | nc fedora-0 2181
This ZooKeeper instance is not currently serving requests

➜ echo stat | nc fedora-1 2181
xxx   <== 其他节点是正常的,只有 fedora-0 被剔除了

两台崩溃

fedora-1 也中断

➜ ansible fedora-2,fedora-3 -uroot -a "iptables -A INPUT -s 192.168.99.121 -j DROP"
➜ ansible fedora-2,fedora-3 -uroot -a "iptables -A INPUT -d 192.168.99.121 -j DROP"

这时候,集群就会都崩溃,因为没有超过 1/2 的节点集群了

一台崩溃,崩溃的时候 commit 了信息

➜ ansible fedora -uroot -a "iptables -F"
➜ ansible fedora -uroot -a "systemctl restart zookeeper"

我们还是模拟 fedora-0 崩溃,但是,fedora-0 知道自己崩溃是有时间间隔的,在这个时间间隔内,我们马上 create 一个新节点

➜ ansible fedora-1,fedora-2,fedora-3 -uroot -a "iptables -A INPUT -s 192.168.99.120 -j DROP"
➜ ansible fedora-1,fedora-2,fedora-3 -uroot -a "iptables -A INPUT -d 192.168.99.120 -j DROP"

马上切换到 fedora-0

➜ zkCli.sh
➜ create /my-tmp1 tmp1
Exception in thread "main"
WATCHER::

WatchedEvent state:Disconnected type:None path:null
org.apache.zookeeper.KeeperException$ConnectionLossException: KeeperErrorCode = ConnectionLoss for /my-tmp1
      at org.apache.zookeeper.KeeperException.create(KeeperException.java:99)
      at org.apache.zookeeper.KeeperException.create(KeeperException.java:51)
      at org.apache.zookeeper.ZooKeeper.create(ZooKeeper.java:783)
      at org.apache.zookeeper.ZooKeeperMain.processZKCmd(ZooKeeperMain.java:706)
      at org.apache.zookeeper.ZooKeeperMain.processCmd(ZooKeeperMain.java:599)
      at org.apache.zookeeper.ZooKeeperMain.executeLine(ZooKeeperMain.java:371)
      at org.apache.zookeeper.ZooKeeperMain.run(ZooKeeperMain.java:331)
      at org.apache.zookeeper.ZooKeeperMain.main(ZooKeeperMain.java:290)

可以看到,fedora-0 的消息没有发送出去,现在我们重启 fedora-0

➜ ansible fedora-1,fedora-2,fedora-3 -uroot -a "iptables -D INPUT -s 192.168.99.120 -j DROP"
➜ ansible fedora-1,fedora-2,fedora-3 -uroot -a "iptables -D INPUT -d 192.168.99.120 -j DROP"
➜ ansible fedora-0 -uroot -a "systemctl restart zookeeper"

切换到 fedora-0

➜ zkCli.sh
➜ ls /
xxx         <== 没有 fedora-0 失败前想创建的 /my-tmp1

模拟 leader 崩溃

➜ ansible fedora -uroot -a "iptables -F"
➜ ansible fedora -uroot -a "systemctl restart zookeeper"
➜ ansible fedora-0,fedora-1,fedora-2 -uroot -a "iptables -A INPUT -s 192.168.99.123 -j DROP"
➜ ansible fedora-0,fedora-1,fedora-2 -uroot -a "iptables -A INPUT -d 192.168.99.123 -j DROP"

运行发现 fedora-3 leader 挂了之后,剩下的 follower 重新选举了 fedora-2 作为 leader

➜ echo stat | nc fedora-3 2181
This ZooKeeper instance is not currently serving requests

➜ echo stat | nc fedora-2 2181
...
Mode: leader

总结

zookeeper 的 ZAB 协议是 2pc 的一个简化版本,同时他比 2pc 更适用。2pc 不支持任何一个 follower 和 leader 的崩溃,ZAB 即使发生主机崩溃,在崩溃不超过节点数一般的情况,zookeeper 还是能正常运作

另外,半数同意这个设置太奇妙了,他防止了 “网络分区”、“脑裂” 的现象,因为如果分成两个区或者两个以上的区,就不会有半数的节点来同意一个请求