5.1 服务器初始化时Leader选举
Zookeeper由于其自身的性质,一般建议选取奇数个节点进行搭建分布式服务器集群。以3个节点组成的服务器集群为例,说明服务器初始化时的选举过程。启动第一台安装Zookeeper的节点时,无法单独进行选举,启动第二台时,两节点之间进行通信,开始选举Leader。
1. 每个Server投出一票。他们两都选自己为Leader,投票的内容为(SID,ZXID)。SID即Server的id,安装zookeeper时配置文件中所配置的myid;ZXID,事务id,为节点的更新程度,ZXID越大,代表Server对Znode的操作越新。由于服务器初始化,每个Sever上的Znode为0,所以Server1投的票为(1,0),Server2为(2,0)。两Server将各自投票发给集群中其他机器。
2. 每个Server接收来自其他Server的投票。集群中的每个Server先判断投票有效性,如检查是不是本轮的投票,是不是来Looking状态的服务器投的票。
3. 对投票结果进行处理。先了解下处理规则
首先对比ZXID。ZXID大的服务器优先作为Leader ,若ZXID相同,比如初始化的时候,每个Server的ZXID都为0,就会比较myid,myid大的选出来做Leader。
对于Server而言,他接受到的投票为(2,0),因为自身的票为(1,0),所以此时它会选举 Server2为Leader,将自己的更新为(2,0)。而Server2收到的投票为Server1的(1,0)由于比他自己小,Server2的投票不变。Server1和Server2再次将票投出,投出的票都为(2,0)。
4. 统计投票。每次投票之后,服务器都会统计投票信息,如果判定某个Server有过半的票数投它,那么该Server将会作为Leader。对于Server1和Server2而言,统计出已经有两台机器接收了(2,0)的投票信息,此时认为选出了Leader。
5. 改变服务器状态。当确定了Leader之后,每个Server更新自己的状态,Leader将状态更新为Leading,Follower将状态更新为Following。

5.2 服务器运行期间的Leader选举
Zookeeper运行期间,如果有新的Server加入,或者非Leader的Server宕机,那么Leader将会同步数据到新Server或者寻找其他备用Server替代宕机的Server。若Leader宕机,此时集群暂停对外服务,开始在内部选举新的Leader。假设当前集群中有Server1、Server2、Server3三台服务器,Server2为当前集群的Leader,由于意外情况,Server2宕机了,便开始进入选举状态。过程如下。
1. 变更状态。其他的非Observer服务器将自己的状态改变为Looking,开始进入Leader选举。
2. 每个Server发出一个投票(myid,ZXID),由于此集群已经运行过,所以每个Server上的ZXID可能不同。
假设Server1的ZXID为145,Server3的为122,第一轮投票中,Server1和Server3都投自己,票分别为(1,145)、(3,122),将自己的票发送给集群中所有机器。
3. 每个Server接收接收来自其他Server的投票,接下来的步骤与初始化时相同。
二、Zookeeper客户端使用
1. 配置Zookeeper的环境变量
为了简化我们每次操作Zookeeper而不用进入到Zookeeper的安装目录,我们可以将Zookeeper的安装信息配置到系统的环境变量中。
vim /etc/profile
添加的内容
export ZOOKEPPER_HOME=/opt/zookeeper
export PATH=$PATH:$ZOOKEEPER_HOME/bin
执行source命令
source /etc/profile
我们就可以在节点的任意位置操作Zookeeper了。
通过scp命令将profile文件发送到其他几个节点上。
scp /etc/profile bobo02:/etc/
2.客户端连接
通过bin目录下的zkCli.sh 命令连接即可。
zkCli.sh
zkCli.sh默认连接的是当前节点的Zookeeper节点,如果我们要连接其他节点执行如下命令即可。
zkCli.sh -timeout 5000 -server bobo02:2181
3.数据操作
3.1 Zookeeper的数据结构
1. 层次化的目录结构,命名符合常规文件系统规范。
2. 每个节点在Zookeeper中叫做znode,并且有一个唯一的路径标识。
3. 节点znode可以包含数据和子节点(但是EPHEMERAL类型的节点不能有子节点)。
4. 客户端应用可以在节点上设置监听器。

3.2 节点类型
1).znode有两种类型:
短暂性(ephemeral)(断开连接自己删除)
持久性(persistent)(断开连接不删除)
2).znode有四种形式的目录节点(默认是persistent)如下

创建znode时设置顺序标识,znode名称后会附加一个值,顺序号是一个单调递增的计数器,有父节点维护。
在分布式系统中,顺序号可以被用于为所有的事件进行全局排序,这样客户端可以通过顺序号推断事件的顺序。
3.3 常用命令
Zookeeper作为Dubbo的注册中心用来保存我们各个服务的节点信息,显示Zookeeper是可以实现输出的存储操作的,我们来看下Zookeeper中存储操作的基本命令。

ls
ls用来查看某个节点下的子节点信息。

增强的命令,查看节点下的子节点及当前节点的属性信息 ls2或者 ls -s 命令。

create
创建节点信息

get
get命令用来查看节点的数据。

如果要查看节点的属性信息那么我们可以通过get -s 来实现。

delete
delete只能删除没有子节点的节点要删除非空节点可以通过 rmr 或者 deleteall 命令实现。

set
set命令可以用来修改节点的内容。

3.4 事件监听
3.4.1 数据改变的监听
监听某个节点的数据内容变化,通过get命令 带 -w 参数即可,在3.4版本的Zookeeper中是通过 get path watch 来说实现监控的。

然后我们在其他节点上修改app1节点的数据,会触监听事件。


注意监听一次节点只会触发一次,如果要实现多次监听,那么可以在触发事件的处理函数中再次追加对节点的监听操作。
3.4.2 子节点的改变
监听节点下面的子节点的改变。
[zk: localhost:2181(CONNECTED) 14] ls -w /app1
[]
触发


三、Zookeeper Java API使用
介绍如何通过Java代码来操作Zookeeper中的数据。
在Zookeeper的安装目录下是提供的有相关的Jar依赖的。

但是我们对于Maven构建项目已经习惯而且是主流,那么我们可以通过maven坐标来管理。
<dependencies><dependency><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId><version>3.5.9</version></dependency><dependency><groupId>com.github.sgroschupf</groupId><artifactId>zkclient</artifactId><version>0.1</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency></dependencies>
1. API的使用
1.1 连接ZK服务
package com.bobo.test;import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.junit.Test;import java.io.IOException;public class Test1 {private String connectString = "192.168.100.121:2181,192.168.122:2181,192.168.100.122:2181";private int sessionTimeOut = 5000;/*** 连接Zookeeper服务端*/@Testpublic void test1() throws IOException {zooKeeper = new ZooKeeper(connectString, sessionTimeOut, new Watcher() {/*** 触发监听事件的回调方法* @param watchedEvent*/@Overridepublic void process(WatchedEvent watchedEvent) {System.out.println("触发了.....");}});//System.out.println("--->" + zooKeeper);}}
1.2 基本操作
package com.bobo.test;import jdk.nashorn.internal.ir.CallNode;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.junit.Before;
import org.junit.Test;import java.io.IOException;
import java.util.List;public class Test1 {private String connectString = "192.168.100.121:2181,192.168.122:2181,192.168.100.122:2181";private int sessionTimeOut = 10000;ZooKeeper zooKeeper = null;/*** 连接Zookeeper服务端*/@Beforepublic void test1() throws IOException {zooKeeper = new ZooKeeper(connectString, sessionTimeOut, new Watcher() {/*** 触发监听事件的回调方法* @param watchedEvent*/@Overridepublic void process(WatchedEvent watchedEvent) {System.out.println("触发了.....");}});//System.out.println("--->" + zooKeeper);}/*** 创建节点*/@Testpublic void createNode() throws Exception{String path = zooKeeper.create("/apptest" // 节点路径,"HelloZookeeper".getBytes() // 节点的数据, ZooDefs.Ids.OPEN_ACL_UNSAFE // 权限, CreateMode.PERSISTENT // 节点类型);System.out.println(path);}/*** 判断节点是否存在*/@Testpublic void exist() throws Exception{// true表示的是使用Zookeeper中的watchStat stat = zooKeeper.exists("/apptest", true);if(stat != null){System.out.println("节点存在"+ stat.getNumChildren());}else{System.out.println("节点不存在 ....");}}/*** 获取某个节点下面的所有的子节点*/@Testpublic void getChildrens() throws Exception{List<String> childrens = zooKeeper.getChildren("/app1", true);for (String children : childrens) {// System.out.println(children);// 获取子节点中的数据byte[] data = zooKeeper.getData("/app1/" + children, false, null);System.out.println(children+":" + new String(data));}}/*** 修改节点的内容*/@Testpublic void setData() throws Exception{// -1 不指定版本 自动维护Stat stat = zooKeeper.setData("/app1/a1", "666666".getBytes(), -1);System.out.println(stat);}/*** 删除节点*/@Testpublic void deleteNode() throws Exception{zooKeeper.delete("/app1",-1);}}
1.3 事件监听处理
Java程序如何监听Zookeeper中的数据的变化?
/*** 监听Node节点下的子节点的变化*/@Testpublic void nodeChildrenChange() throws Exception{List<String> list = zooKeeper.getChildren("/app1", new Watcher() {/*** None(-1),* NodeCreated(1),* NodeDeleted(2),* NodeDataChanged(3),* NodeChildrenChanged(4),* DataWatchRemoved(5),* ChildWatchRemoved(6);* @param watchedEvent*/@Overridepublic void process(WatchedEvent watchedEvent) {System.out.println("--->"+ watchedEvent.getType());}});for (String s : list) {System.out.println(s);}Thread.sleep(Integer.MAX_VALUE);}/*** 监听节点内容变更*/@Testpublic void nodeDataChanged() throws Exception{byte[] data = zooKeeper.getData("/app1/a1", new Watcher() {@Overridepublic void process(WatchedEvent watchedEvent) {System.out.println("--->" + watchedEvent.getType());}}, null);System.out.println("--->" + new String(data));Thread.sleep(Integer.MAX_VALUE);}