是一个分布式服务框架,是Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。
java编程经常会遇到配置项, 比如数据库的url、 schema、 user和password等。 通常这些配置项我们会放置在配置文件中, 再将配置文件放置在服务器上当需要更改配置项时, 需要去服务器上修改对应的配置文件。 但是随着分布式系统的兴起, 由于许多服务都需要使用到该配置文件, 因此有必须保证该配置服务的高可用性(highavailability) 和各台服务器上配置数据的一致性。 通常会将配置文件部署在一个集群上,然而一个集群动辄上千台服务器, 此时如果再一台台服务器逐个修改配置文件那将是非常繁琐且危险的的操作, 因此就需要一种服务, 能够高效快速且可靠地完成配置项的更改等操作, 并能够保证各配置项在每台服务器上的数据一致性。
zookeeper就可以提供这样一种服务, 其使用Zab这种一致性协议来保证一致性。 现在有很多开源项目使用zookeeper来维护配置, 比如在hbase中, 客户端就是连接一个zookeeper, 获得必要的hbase集群的配置信息, 然后才可以进一步操作。 还有在开源的消息队列kafka中, 也使用zookeeper来维护broker的信息。 在alibaba开源的soa框架dubbo中也广泛的使用zookeeper管理一些配置来实现服务治理。
一个集群是一个分布式系统, 由多台服务器组成。 为了提高并发度和可靠性,多台服务器上运行着同一种服务。 当多个服务在运行时就需要协调各服务的进度, 有时候需要保证当某个服务在进行某个操作时, 其他的服务都不能进行该操作, 即对该操作进行加锁, 如果当前机器挂掉后, 释放锁并fail over 到其他的机器继续执行该服务。
一个集群有时会因为各种软硬件故障或者网络故障, 出现某些服务器挂掉而被移除集群, 而某些服务器加入到集群中的情况, zookeeper会将这些服务器加入/移出的情况通知给集群中的其他正常工作的服务器, 以及时调整存储和计算等任务的分配和执行等。 此外zookeeper还会对故障的服务器做出诊断并尝试修复。
在过去的单库单表型系统中, 通常可以使用数据库字段自带的auto_increment属性来自动为每条记录生成一个唯一的ID。 但是分库分表后, 就无法在依靠数据库的auto_increment属性来唯一标识一条记录了。 此时我们就可以用zookeeper在分布式环境下生成全局唯一ID。 做法如下: 每次要生成一个新Id时, 创建一个持久顺序节点, 创建操作返回的节点序号, 即为新Id, 然后把比自己节点小的删除即可
zookeeper的数据节点可以视为树状结构(或者目录) , 树中的各节点被称为znode(即zookeeper node) , 一个znode可以有多个子节点。 zookeeper节点在结构上表现为树状; 使用路径path来定位某个znode。
znode, 兼具文件和目录两种特点。 既像文件一样维护着数据、 元信息、 ACL、 时间戳等数据结构, 又像目录一样可以作为路径标识的一部分
一个Znode大体上有3个部分
zookeeper中的节点有两种, 分别为临时节点和永久节点。 节点的类型在创建时即被确定, 并且不能改变。
zookeeper下载地址
服务器IP | 主机名 | myid的值 |
---|---|---|
192.168.18.101 | hadoop101 | 1 |
192.168.18.102 | hadoop102 | 2 |
192.168.18.103 | hadoop103 | 3 |
因为zookeeper的安装是需要jdk的环境的,这里不说jdk的安装。配置zookeeper非常简单,只需要更改一下存储zookeeper中数据的内存快照、 及事物日志文件
cd /opt/module/zookeeper-3.4.9/conf
cp zoo_sample.cfg zoo.cf
vim zoo.cfg
# 存储zookeeper中数据的内存快照、 及事物日志文件
dataDir=/opt/module/zookeeper-3.4.9/zkdatas
# 保留多少个快照
autopurge.snapRetainCount=3
# 日志多少小时清理一次
autopurge.purgeInterval=1
# 集群中服务器地址
server.1=hadoop101:2888:3888
server.2=hadoop102:2888:3888
server.3=hadoop103:2888:3888
myid相当于每一台机器的标识,后面会详细讲到
在/opt/module/zookeeper-3.4.9/zkdatas/下创建文件名为myid,内容为1
echo 1 > /opt/module/zookeeper-3.4.9/zkdatas/myid
然后分发到各个机器上,注意xsync是自己写的脚本,这个在hadoop那提到过,最后修改每台机器上的myid文件内容
cd /opt/module/zookeeper-3.4.9/bin
#启动
./zkServer.sh start
#停止
./zkServer.sh stop
#查看状态
./zkServer.sh status
注意:zookeeper不像hadoop一样,zookeeper是不能群起的,所以对每台机器都必须启动一下
注意端口号一般为2181
./zkCli.sh -server ip
因为集群启动需要一个一个机器启动,所以自己写了一个脚本
#!/bin/sh
params=$1
if [ "$params" = "start" ]
then
for (( i=1 ; i <= 3 ; i = $i + 1 )) ;
do
echo ============= hadoop10$i $params =============
ssh [email protected]$i "/opt/module/zookeeper-3.4.9/bin/zkServer.sh start"
done
fi
if [ "$params" = "stop" ]
then
for (( i=1 ; i <= 3 ; i = $i + 1 )) ;
do
echo ============= hadoop10$i $params =============
ssh [email protected]$i "/opt/module/zookeeper-3.4.9/bin/zkServer.sh stop"
done
fi
if [ "$params" = "status" ]
then
for (( i=1 ; i <= 3 ; i = $i + 1 )) ;
do
echo ============= hadoop10$i $params =============
ssh [email protected]$i "/opt/module/zookeeper-3.4.9/bin/zkServer.sh status"
done
fi
将该脚本放到/usr/local/bin/zk.sh就可以使用,一键启动脚本
create [-s] [-e] path data #其中-s 为有序节点, -e 临时节点
set /hadoop "345"
delete path
想删除某个节点及其所有后代节点, 可以使用递归删除, 命令为 rmr path
get path
节点各个属性如下表。 其中一个重要的概念是 Zxid(ZooKeeper Transaction Id), ZooKeeper 节点的每一次更改都具有唯一的 Zxid, 如果 Zxid1 小于 Zxid2, 则Zxid1 的更改发生在 Zxid2 更改之前。
状态属性 | 说明 |
---|---|
cZxid | 数据节点创建时的事务 ID |
ctime | 数据节点创建时的时间 |
mZxid | 数据节点最后一次更新时的事务 ID |
mtime | 数据节点最后一次更新时的时间 |
pZxid | 数据节点的子节点最后一次被修改时的事务 ID |
cversion | 子节点的更改次数 |
dataVersion | 节点数据的更改次数 |
aclVersion | 节点的 ACL 的更改次数 |
ephemeralOwner | 如果节点是临时节点, 则表示创建该节点的会话的 SessionID; 如果节点是持久节点, 则该属性值为 0 |
dataLength | 数据内容的长度 |
numChildren | 数据节点当前的子节点个数 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MpHOlTDs-1608376830488)(Zookeeper(1)].assets/image-20201216203412038.png)
ls /
使用 get path [watch] 注册的监听器能够在节点内容发生改变的时候, 向客户端发出通知。 需要注意的是 zookeeper 的触发器是一次性的 (One-time trigger), 即触发一次后就会立即失效 。
get /hadoop watch
当使用另外一个客户端修改/hadoop的值得时候,watch将发出通知
zookeeper的文件系统类似于linux,client 可以创建节点、 更新节点、 删除节点, 那么如何做到节点的权限的控制呢? zookeeper的access control list 访问控制列表可以做到这一点。
zookeeper中权限控制是通过访问控制列表来实现的,访问控制列表格式为:
权限模式(schemem):授权对象(id):权限(permission)
权限模式:采用何种方式授权
方案 | 描述 |
---|---|
world | 只有一个用户: anyone, 代表登录zokeeper所有人(默认) |
ip | 对客户端使用IP地址认证 |
auth | 使用已添加认证的用户认证 |
digest | 使用“用户名:密码”方式认证 |
授权对象ID是指, 权限赋予的实体, 例如: IP 地址或用户
授予什么权限
权限 | ACL简写 | 描述 |
---|---|---|
create | c | 可以创建子节点 |
delete | d | 可以删除子节点(仅下一级节点) |
read | r | 可以读取节点数据及显示子节点列表 |
write | w | 可以设置节点数据 |
admin | a | 可以设置节点访问控制列表权限 |
命令 | 使用方式 | 描述 |
---|---|---|
getAcl | getAcl | 读取ACL权限 |
setAcl | setAcl | 设置ACL权限 |
addauth | addauth | 添加认证用户 |
[zk: localhost:2181(CONNECTED) 5] create /node1 "node"
#赋予/node1节点不能创建子节点的权限
[zk: localhost:2181(CONNECTED) 8] setAcl /node1 world:anyone:drwa
#试图创建子节点
[zk: localhost:2181(CONNECTED) 11] create /node1/node11 "node11"
Authentication is not valid : /node1/node11
[zk: localhost:2181(CONNECTED) 12] getAcl /node1
'world,'anyone
: drwa
[zk: localhost:2181(CONNECTED) 13] create /hive "hive"
#设置只有ip为192.168.18.102的主机才能访问/hive节点
[zk: localhost:2181(CONNECTED) 14] setAcl /hive ip:192.168.18.102:cdrwa
#当前节点已经不能访问
[zk: localhost:2181(CONNECTED) 15] get /hive
Authentication is not valid : /hive
[zk: localhost:2181(CONNECTED) 16] getAcl /hive
'ip,'192.168.18.102
: cdrwa
[zk: localhost:2181(CONNECTED) 0] addauth digest zookeeper1:12345
[zk: localhost:2181(CONNECTED) 1] create /node3 "node1"
Created /node3
[zk: localhost:2181(CONNECTED) 2] setAcl /node3 auth:zookeeper1:cdrwa
setAcl <path> digest:<user>:<password>:<acl>
这里的密码是通过了SHA1及BASE64处理的密文
#先计算出密文
[root@hadoop101 ~]# echo -n user1:12345 | openssl dgst -binary -sha1 | openssl base64
+owfoSBn/am19roBPzR1/MfCblE=
digest授权
#创建节点
[zk: localhost:2181(CONNECTED) 16] create /node5 "node5"
Created /node5
#设置权限使用digest
[zk: localhost:2181(CONNECTED) 17] setAcl /node5 digest:user1:+owfoSBn/am19roBPzR1/MfCblE=:crdwa
#尝试获取节点
[zk: localhost:2181(CONNECTED) 0] get /node5
Authentication is not valid : /node5
#添加认证用户
[zk: localhost:2181(CONNECTED) 1] addauth digest user1:12345
#获取节点成功
[zk: localhost:2181(CONNECTED) 2] get /node5
node5
cZxid = 0x40000001e
ctime = Thu Dec 17 20:26:31 CST 2020
mZxid = 0x40000001e
mtime = Thu Dec 17 20:26:31 CST 2020
pZxid = 0x40000001e
cversion = 0
dataVersion = 0
aclVersion = 1
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
zookeeper的权限管理模式有一种叫做super, 该模式提供一个超管可以方便的访问
任何权限的节点。
假设这个超管是: user2:admin, 需要先为超管生成密码的密文
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>3.8.1version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.apache.zookeepergroupId>
<artifactId>zookeeperartifactId>
<version>4.13.1version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.16version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.6.1version>
dependency>
public class ZookeeperConnection {
public static void main(String[] args) throws IOException {
final CountDownLatch countDownLatch = new CountDownLatch(1);
String ip = "192.168.18.101:2181";
ZooKeeper zooKeeper = new ZooKeeper(ip, 5000, new Watcher() {
public void process(WatchedEvent watchedEvent) {
if(watchedEvent.getState() == Event.KeeperState.SyncConnected){
System.out.println("链接创建成功!");
countDownLatch.countDown();
}
}
});
try {
countDownLatch.await();
System.out.println(zooKeeper.getSessionId());
zooKeeper.close();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
ZooKeeper构造函数的参数:
// 同步方式
create(String path, byte[] data, List<ACL> acl, CreateMode createMode)
// 异步方式
create(String path, byte[] data, List<ACL> acl, CreateMode createMode,
AsyncCallback.StringCallback callBack,Object ctx)
// 同步方式
setData(String path, byte[] data, int version)
// 异步方式
setData(String path, byte[] data, int version, AsyncCallback.StatCallback
callBack, Object ctx)
// 同步方式
delete(String path, int version)
// 异步方式
delete(String path, int version, AsyncCallback.VoidCallback callBack,
Object ctx)
// 同步方式
getData(String path, boolean b, Stat stat)
// 异步方式
getData(String path, boolean b, AsyncCallback.DataCallback callBack,
Object ctx)
// 同步方式
delete(String path, int version)
// 异步方式
delete(String path, int version, AsyncCallback.VoidCallback callBack,
Object ctx)
// 同步方式
getData(String path, boolean b, Stat stat)
// 异步方式
getData(String path, boolean b, AsyncCallback.DataCallback callBack,
Object ctx)
zookeeper提供了数据的发布/订阅功能,多个订阅者可同时监听某一特定主题对象,当该主题对象的自身状态发生变化时(例如节点内容改变、节点下的子节点列表改变等),会实时、主动通知所有订阅者
zookeeper采用了Watcher机制实现数据的发布/订阅功能。该机制在被订阅对象发生变化时会异步通知客户端,因此客户端不必在Watcher注册后轮询阻塞,从而减轻了客户端压力。
Watcher实现由三个部分组成:
特性 | 说明 |
---|---|
一次性 | watcher是一次性的,一旦被触发就会移除,再次使用时需要重新注册 |
客户端顺 序回 调 | watcher回调是顺序串行化执行的,只有回调后客户端才能看到最新的数据状态。一个watcher回调逻辑不应该太多,以免影响别的watcher执行 |
轻量级 | WatchEvent是最小的通信单元,结构上只包含通知状态、事件类型和节点路径,并不会告诉数据节点变化前后的具体内容; |
时效性 | watcher只有在当前session彻底失效时才会无效,若在session有效期内快速重连成功,则watcher依然存在,仍可接收到通知; |
枚举属性 | 说明 |
---|---|
SyncConnected | 客户端与服务器正常连接时 |
Disconnected | 客户端与服务器断开连接时 |
Expired | 会话session失效时 |
AuthFailed | 身份认证失败时 |
EventType是数据节点(znode)发生变化时对应的通知类型。EventType变化时KeeperState永远处于SyncConnected通知状态下;当KeeperState发生变化时,EventType永远为None。其路径为org.apache.zookeeper.Watcher.Event.EventType,是一个枚举类,枚举属性如下:
枚举属性 | 说明 |
---|---|
None | 无 |
NodeCreated | Watcher监听的数据节点被创建时 |
NodeDeleted | Watcher监听的数据节点被删除时 |
NodeDataChanged | Watcher监听的数据节点内容发生变更时(无论内容数据 是否变化) |
NodeChildrenChanged | Watcher监听的数据节点的子节点列表发生变更时 |
等会儿通过案例来使用这些接口
工作中有这样的一个场景: 数据库用户名和密码信息放在一个配置文件中,应用读取该配置文件,配置文件信息放入缓存。若数据库的用户名和密码改变时候,还需要重新加载缓存,比较麻烦,通过ZooKeeper可以轻松完成,当数据库发生变化时自动完成缓存同步。
public class MyConfigCenter {
private static String IP = "192.168.18.102:2181";
private CountDownLatch count = new CountDownLatch(1);
private ZooKeeper zooKeeper;
private static MyConfig config = new MyConfig();
public MyConfigCenter(){
this.getValue();
}
class MyWatch implements Watcher {
public void process(WatchedEvent watchedEvent) {
if(watchedEvent.getType() == Event.EventType.None){
if(watchedEvent.getState() == Event.KeeperState.SyncConnected){
System.out.println("连接成功");
count.countDown();
}else if (watchedEvent.getState() == Event.KeeperState.Disconnected){
System.out.println("连接断开!");
} else if (watchedEvent.getState() == Event.KeeperState.Expired){
System.out.println("连接超时!自动重新连接。。。");
try {
zooKeeper = new ZooKeeper(IP,5000,this);
} catch (IOException e) {
e.printStackTrace();
}
} else if (watchedEvent.getState() == Event.KeeperState.AuthFailed){
System.out.println("验证失败!");
}
//监听节点发生变化
}else if (watchedEvent.getType() == Event.EventType.NodeDataChanged) {
getValue();
}
}
}
public void getValue(){
try {
//判断zookeeper是否为空值,防止重复创建
if(zooKeeper == null) {
zooKeeper = new ZooKeeper(IP, 5000, new MyWatch());
count.await();
}
config.setUsername(new String(zooKeeper.getData("/config/username", new MyWatch(), null)));
config.setPassword(new String(zooKeeper.getData("/config/password", new MyWatch(), null)));
config.setUrl(new String(zooKeeper.getData("/config/url", new MyWatch(), null)));
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
MyConfigCenter center = new MyConfigCenter();
while (true){
//休眠3秒
try { TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}
System.out.println(config);
}
}
}
public class MyConfig {
private String url;
private String username;
private String password;
}
测试:
当节点的配置信息发生变化时
在过去的单库单表型系统中,通常可以使用数据库字段自带的auto_increment属性来自动为每条记录生成一个唯一的ID。但是分库分表后,就无法在依靠数据库的auto_increment属性来唯一标识一条记录了。此时我们就可以用zookeeper在分布式环境下生成全局唯一ID。
public class GloballyUniqueId {
private String IP = "192.168.18.102:2181";
private CountDownLatch count = new CountDownLatch(1);
private ZooKeeper zooKeeper;
private String defaultPath = "/uniqueId";
public GloballyUniqueId(){
if (zooKeeper == null) {
try {
zooKeeper = new ZooKeeper(IP, 5000, new MyWatch());
count.await();
} catch (Exception e) {
e.printStackTrace();
}
}
}
class MyWatch implements Watcher {
public void process(WatchedEvent watchedEvent) {
if (watchedEvent.getType() == Event.EventType.None) {
if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
System.out.println("连接成功");
count.countDown();
} else if (watchedEvent.getState() == Event.KeeperState.Disconnected) {
System.out.println("连接断开!");
} else if (watchedEvent.getState() == Event.KeeperState.Expired) {
System.out.println("连接超时!自动重新连接。。。");
try {
zooKeeper = new ZooKeeper(IP, 5000, this);
} catch (IOException e) {
e.printStackTrace();
}
} else if (watchedEvent.getState() == Event.KeeperState.AuthFailed) {
System.out.println("验证失败!");
}
}
}
}
public String getId() {
String path = "";
try {
//创建临时节点
path = zooKeeper.create(defaultPath, new byte[0],
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
} catch (Exception e) {
e.printStackTrace();
}
//System.out.println(path);
// /uniqueId0000000015
return path.substring(9);
}
public static void main(String[] args) {
GloballyUniqueId Id = new GloballyUniqueId();
while (true){
try { TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}
System.out.println(Id.getId());
}
}
}
设计思路:
多线程中的锁可以有sync锁,这里我采用的是自旋的方式
public class MyLock {
private String IP = "192.168.18.102:2181";
private CountDownLatch count = new CountDownLatch(1);
private ZooKeeper zooKeeper;
private String lockPath = "/Locks4";
private String lockName = "Lock_";
private String path = "";
private boolean flag = false;
class MyWatch implements Watcher {
public void process(WatchedEvent watchedEvent) {
if (watchedEvent.getType() == Event.EventType.None) {
if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
System.out.println("连接成功");
count.countDown();
}else if (watchedEvent.getState() == Event.KeeperState.Disconnected) {
System.out.println("连接断开!");
} else if (watchedEvent.getState() == Event.KeeperState.Expired) {
System.out.println("连接超时!自动重新连接。。。");
try {
zooKeeper = new ZooKeeper(IP, 5000, this);
} catch (IOException e) {
e.printStackTrace();
}
} else if (watchedEvent.getState() == Event.KeeperState.AuthFailed) {
System.out.println("验证失败!");
}
}
}
}
public MyLock(){
if (zooKeeper == null) {
try {
zooKeeper = new ZooKeeper(IP, 5000,new MyWatch());
count.await();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void lock(){
createNode();
attempLock();
}
public void createNode(){
try {
Stat stat = zooKeeper.exists(lockPath, null);
if(stat == null){
zooKeeper.create(lockPath, new byte[0],
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
//创建临时有序子节点
path = zooKeeper.create(lockPath + "/" + lockName, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println("节点创建成功" + path);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Watcher watcher = new Watcher() {
public void process(WatchedEvent watchedEvent) {
//监听到删除节点
if (watchedEvent.getType() == Event.EventType.NodeDeleted) {
// synchronized (this) {
// notifyAll();
// }
flag = true;
}
}
};
public void attempLock(){
try {
List<String> list = zooKeeper.getChildren(lockPath, false);
Collections.sort(list);
// /Locks/Lock_000000001
//判断自己创建的这个节点是否是子节点中的第一个节点
// 获取 Lock_000000001
int i = list.indexOf(path.substring(lockPath.length() + 1));
//说明是第一个进来的
if(i == 0){
System.out.println("获取锁成功!");
return;
}else{
String prePath = list.get(i - 1);
Stat stat = zooKeeper.exists(lockPath + "/" + prePath, watcher);
if(stat == null){
attempLock();
}else{
// synchronized (watcher){
// watcher.wait();
// }
while(flag){}
attempLock();
}
}
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void unlock(){
try {
zooKeeper.delete(path, -1);
flag = false;
System.out.println("锁已经释放" + path);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
}
ZAB 协议是 Zookeeper 专门设计的一种支持崩溃恢复的原子广播协议。通过该协议,Zookeepe 基于
主从模式的系统架构来保持集群中各个副本之间数据的一致性。具体如下:
Zookeeper 使用一个单一的主进程来接收并处理客户端的所有事务请求,并采用原子广播协议将数据状
态的变更以事务 Proposal 的形式广播到所有的副本进程上去。如下图
ZAB 协议包括两种基本的模式,分别是崩溃恢复和消息广播:
Leader 服务会为每一个 Follower 服务器分配一个单独的队列,然后将事务 Proposal 依次放入队列中,并根据 FIFO(先进先出) 的策略进行消息发送。Follower 服务在接收到 Proposal 后,会将其以事务日志的形式写入本地磁盘中,并在写入成功后反馈给 Leader 一个 Ack 响应。当 Leader 接收到超过半数 Follower 的 Ack 响应后,就会广播一个 Commit 消息给所有的 Follower 以通知其进行事务提交,之后 Leader 自身也会完成对事务的提交。而每一个 Follower 则在接收到 Commit 消息后,完成事务的提交。
looking:寻找leader状态。当服务器处于该状态时,它会认为当前集群中没有
leader,因此需要进入leader选举状态。
leading: 领导者状态。表明当前服务器角色是leader。
following: 跟随者状态。表明当前服务器角色是follower。
observing:观察者状态。表明当前服务器角色是observer。
成为leader。
集群初始化阶段,当有一台服务器server1启动时,其单独无法进行和完成leader选举,当第二台服务器server2启动时,此时两台机器可以相互通信,每台机器都试图找到leader,于是进入leader选举过程。选举过程如下:
每个server发出一个投票。由于是初始情况,server1和server2都会将自己作为leader服务器来进行投票,每次投票会包含所推举的服务器的myid和zxid,使用(myid, zxid)来表示,此时server1的投票为(1, 0),server2的投票为(2, 0),然后各自将这个投票发给集群中其他机器。
集群中的每台服务器接收来自集群中各个服务器的投票。
处理投票。针对每一个投票,服务器都需要将别人的投票和自己的投票进行pk,pk
规则如下:
统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息,对于server1、server2而言,都统计出集群中已经有两台机器接受了(2, 0)的投票信息,此时便认为已经选出了leader
改变服务器状态。一旦确定了leader,每个服务器就会更新自己的状态,如果是follower,那么就变更为following,如果是leader,就变更为leading。
在zookeeper运行期间,leader与非leader服务器各司其职,即便当有非leader服务器宕机或新加入,此时也不会影响leader,但是一旦leader服务器挂了,那么整个集群将暂停对外服务,进入新一轮leader选举,其过程和启动时期的Leader选举过程基本一致。