半数机制:集群中半数以上机器存活,集群可用。所以zookeeper适合装在奇数台机器上。
Zookeeper虽然在配置文件中并没有指定master和slave。但是,zookeeper工作时,是有一个节点为leader,其他则为follower,Leader是通过内部的选举机制临时产生的。只有一个leader。
图形说明:
(1)服务器1启动,此时只有它一台服务器启动了,它发出去的信息没有任何响应,所以它的选举状态一直是LOOKING状态。
(2)服务器2启动,它与最开始启动的服务器1进行通信,互相交换自己的选举结果,由于两者都没有历史数据,所以id值较大的服务器2胜出,但是由于没有达到超过半数以上的服务器都同意选举它(这个例子中的半数以上是3),所以服务器1、2还是继续保持LOOKING状态。
(3)服务器3启动,根据前面的理论分析,服务器3成为服务器1、2、3中的老大,而与上面不同的是,此时有三台服务器选举了它,所以它成为了这次选举的leader。
(4)服务器4启动,根据前面的分析,理论上服务器4应该是服务器1、2、3、4中最大的,但是由于前面已经有半数以上的服务器选举了服务器3,所以它只能接收当小弟的命了。
(5)服务器5启动,同4一样当小弟。
满足奇数节点,如果已经选定了老大,那么其他加入进来的只能成为小弟。
2.节点类型:
1.Znode有两种类型:
短暂(ephemeral):客户端和服务器端断开连接后,创建的节点自动删除
持久(persistent):客户端和服务器端断开连接后,创建的节点不删除
2.Znode有四种形式的目录节点(默认是persistent )
3.创建znode时设置顺序标识,znode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护
4.在分布式系统中,顺序号可以被用于为所有的事件进行全局排序,这样客户端可以通过顺序号推断事件的顺序.
3. stat结构体:(节点属性)
名称 | 说明 |
czxid | 引起这个znode创建的zxid,创建节点的事务的zxid |
ctime | znode被创建的毫秒数(从1970年开始) |
mzxid | znode最后更新的zxid |
mtime | znode最后修改的毫秒数(从1970年开始) |
pZxid | znode最后更新的子节点zxid |
cversion | znode子节点变化号,znode子节点修改次数 |
dataversion | znode数据变化号 |
aclVersion | znode访问控制列表的变化号 |
ephemeralOwner | 如果是临时节点,这个是znode拥有者的session id。如果不是临时节点则是0。 |
dataLength | znode的数据长度 |
numChildren | znode子节点数量 |
czxid说明:
每次修改ZooKeeper状态都会收到一个zxid形式的时间戳,也就是ZooKeeper事务ID。事务ID是ZooKeeper中所有修改总的次序。每个修改都有唯一的zxid,如果zxid1小于zxid2,那么zxid1在zxid2之前发生。
4.监听器原理: 监听节点状态变化,为了及时通知客户端进行调整。
翻译:
ZooKeeper可以为所有的读操作设置watch,这些读操作包括:exists()、getChildren()及getData()。watch事件是一次性的触发器,当watch的对象状态发生改变时,将会触发此对象上watch所对应的事件。watch事件将被异步地发送给客户端,并且ZooKeeper为watch机制提供了有序的一致性保证。理论上,客户端接收watch事件的时间要快于其看到watch对象状态变化的时间。
三个关键点:
1. "监视点"是一次性的,当数据有了变化时zkserver向客户端发送一个watch,它是一次性的动作,即触发一次就不再有效,类似一次性纸杯。只监控一次,如果想继续Watch的话,需要客户端重新设置Watcher。因此如果你得到一个watch事件且想在将来的变化得到通知,必须新设置另一个watch。
。
2.发往客户端
Watches是异步发往客户端的,Zookeeper提供一个顺序保证:在看到watch事件之前绝不会看到变化,这样不同客户端看到的是一致性的顺序。
在(导致观察事件被触发的)修改操作的成功返回码到达客户端之前,事件可能在去往客户端的路上,但是可能不会到达客户端。观察事件是异步地发送给观察者(客户端)的。ZooKeeper会保证次序:在收到观察事件之前,客户端不会看到已经为之设置观察的节点的改动。网络延迟或者其他因素可能会让不同的客户端在不同的时间收到观察事件和更新操作的返回码。这里的要点是:不同客户端看到的事情都有一致的次序。
3. 为数据设置watch:1)节点创建;2)节点删除;3)节点数据修改;4)子节点变更。都会可以设置监听来通知客户端
分类:
1. 数据watch(data watches):getData和exists负责设置数据watch
2.子节点watch(child watches):getChildren负责设置子节点watch
返回的数据来设置不同的watch:
1.getData和exists:返回关于节点的数据信息
2. getChildren:返回子节点列表
① 成功的setData操作将触发Znode的数据watch
② 成功的create操作将触发Znode的数据watch以及子节点watch
③ 成功的delete操作将触发Znode的数据watch以及子节点watch
4.时序性和一致性
Watches是在client连接到Zookeeper服务端的本地维护,这可让watches成为轻量的,可维护的和派发的。当一个client连接到新server,watch将会触发任何session事件,断开连接后不能接收到。当客户端重连,先前注册的watches将会被重新注册并触发。
关于watches,Zookeeper维护这些保证:
(1)Watches和其他事件、watches和异步恢复都是有序的。Zookeeper客户端保证每件事都是有序派发
(2)客户端在看到新数据之前先看到watch事件
(3)对应更新顺序的watches事件顺序由Zookeeper服务所见
Watch由客户端所连接的ZooKeeper服务器在本地维护,因此watch可以非常容易地设置、管理和调度。当客户端连接到一个新的服务器 时,任何的会话事件都将可能触发watch。另外,当从服务器断开连接的时候,watch将不会被接收。但是,当一个客户端重新建立连接的时候,任何先前 注册过的watch都会被重新注册。有一种情况可能会遗漏Watch:如果在断开连接时创建并删除了znode,则会遗漏Watch中是否存在尚未创建的znode。
监听流程:
(图片来源网络)
监听一次性:
import java.io.IOException;
import org.apache.log4j.Logger;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.data.Stat;
public class WatchOne
{
/**
* Logger for this class
*/
private static final Logger logger = Logger.getLogger(HelloZK.class);
//定义常量
private static final String CONNECTSTRING = "94.191.86.252:2181";
private static final String PATH = "/clock";
private static final int SESSION_TIMEOUT = 50*1000;
//定义实例变量
private ZooKeeper zk = null;
//以下为业务方法
public ZooKeeper startZK() throws IOException
{
return new ZooKeeper(CONNECTSTRING, SESSION_TIMEOUT, new Watcher() {
@Override
public void process(WatchedEvent event)
{
}
});
}
public void stopZK() throws InterruptedException
{
if(zk != null)
{
zk.close();
}
}
public void createZNode(String path,String nodeValue) throws KeeperException, InterruptedException
{
zk.create(path,nodeValue.getBytes(),Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
}
public String getZNode(String path) throws KeeperException, InterruptedException
{
byte[] byteArray = zk.getData(path,new Watcher() {
@Override
public void process(WatchedEvent event)
{
try
{
triggerValue(path);
}catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
}, new Stat());
return new String(byteArray);
}
public String triggerValue(String path) throws KeeperException, InterruptedException
{
byte[] byteArray = zk.getData(path,false, new Stat());
String retValue = new String(byteArray);
System.out.println("**************triggerValue: "+retValue);
return retValue;
}
public static void main(String[] args) throws IOException, KeeperException, InterruptedException
{
WatchOne watchOne = new WatchOne();
watchOne.setZk(watchOne.startZK());
if(watchOne.getZk().exists(PATH, false) == null)
{
watchOne.createZNode(PATH,"BBB");
System.out.println("**********************>: "+watchOne.getZNode(PATH));
Thread.sleep(Long.MAX_VALUE);
}else{
System.out.println("i have znode");
}
}
//setter---getter
public ZooKeeper getZk(){
return zk;
}
public void setZk(ZooKeeper zk)
{
this.zk = zk;
}
}
监听多次:
import java.io.IOException;
import org.apache.log4j.Logger;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.data.Stat;
public class WatchMoreTest
{
/**
* Logger for this class
*/
private static final Logger logger = Logger.getLogger(HelloZK.class);
//定义常量
private static final String CONNECTSTRING = "94.191.86.252:2181";
private static final String PATH = "/clock";
private static final int SESSION_TIMEOUT = 50*1000;
//定义实例变量
private ZooKeeper zk = null;
private String lastValue = "";
//以下为业务方法
public ZooKeeper startZK() throws IOException
{
return new ZooKeeper(CONNECTSTRING, SESSION_TIMEOUT, new Watcher() {
@Override
public void process(WatchedEvent event)
{}
});
}
public void stopZK() throws InterruptedException
{
if(zk != null)
{
zk.close();
}
}
public void createZNode(String path,String nodeValue) throws KeeperException, InterruptedException
{
zk.create(path,nodeValue.getBytes(),Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
}
public String getZNode(String path) throws KeeperException, InterruptedException
{
byte[] byteArray = zk.getData(path,new Watcher() {
@Override
public void process(WatchedEvent event)
{
try
{
triggerValue(path);
}catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
}, new Stat());
return new String(byteArray);
}
/*自我监听 监听多次*/
public boolean triggerValue(String path) throws KeeperException, InterruptedException
{
byte[] byteArray = zk.getData(path,new Watcher() {
@Override
public void process(WatchedEvent event)
{
try
{
triggerValue(path);
}catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
}, new Stat());
String newValue = new String(byteArray);
if(lastValue.equals(newValue))
{
System.out.println("there is no change~~~~~~~~");
return false;
}else{
System.out.println("lastValue: "+lastValue+"\t"+"newValue: "+newValue);
this.lastValue = newValue;
return true;
}
}
public static void main(String[] args) throws IOException, KeeperException, InterruptedException
{
WatchMoreTest watch = new WatchMoreTest();
watch.setZk(watch.startZK());
if(watch.getZk().exists(PATH, false) == null)
{
String initValue = "0000";
watch.setLastValue(initValue);
watch.createZNode(PATH,initValue);
System.out.println("**********************>: "+watch.getZNode(PATH));
Thread.sleep(Long.MAX_VALUE);
}else{
System.out.println("i have znode");
}
}
//setter---getter
public ZooKeeper getZk()
{
return zk;
}
public void setZk(ZooKeeper zk)
{
this.zk = zk;
}
public String getLastValue()
{
return lastValue;
}
public void setLastValue(String lastValue)
{
this.lastValue = lastValue;
}
}
监听获取子节点:
import java.io.IOException;
import java.util.List;
import org.apache.log4j.Logger;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
public class WatchChildNode
{
/**
* Logger for this class
*/
private static final Logger logger = Logger.getLogger(HelloZK.class);
//定义常量
private static final String CONNECTSTRING = "94.191.86.252:2181";
private static final String PATH = "/clock";
private static final int SESSION_TIMEOUT = 50*1000;
//定义实例变量
private ZooKeeper zk = null;
//以下为业务方法
public ZooKeeper startZK() throws IOException
{
return new ZooKeeper(CONNECTSTRING, SESSION_TIMEOUT, new Watcher() {
@Override
public void process(WatchedEvent event)
{
if(event.getType() == EventType.NodeChildrenChanged && event.getPath().equals(PATH))
{
showChildLists(PATH);
}else{
showChildLists(PATH);
}
}
});
}
public void stopZK() throws InterruptedException
{
if(zk != null)
{
zk.close();
}
}
public void showChildLists(String path)
{
List list = null;
try
{
list = zk.getChildren(PATH, true);
}catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
System.out.println("**********showChildLists: "+list);
}
public static void main(String[] args) throws IOException, KeeperException, InterruptedException
{
WatchChildNode watch = new WatchChildNode();
watch.setZk(watch.startZK());
Thread.sleep(Long.MAX_VALUE);
}
//setter---getter
public ZooKeeper getZk()
{
return zk;
}
public void setZk(ZooKeeper zk)
{
this.zk = zk;
}
}
5.写数据流程:
各节点数据完全一致
6.读数据流程:
7.时间复杂度
zookeeper 使用concurrenthashmap进行存储。锁的粒度是segment,减少锁竞争,segment里对应一个hashtable 的若干桶.
所以时间复杂度都是 O(1)
8.最终一致性
读数据时,有可能会脏读。使用watch的方式,实现数据的及时生效。同时客户端所需要的时间小于客户端接受到数据的时间。
9.Session
客户端使用某种语言绑定创建一个到服务的句柄时,就建立了一个ZooKeeper会话。会话创建后,句柄处于CONNECTING状态,客户端库会试图连接到组成ZooKeeper服务的某个服务器;连接成功则进入到CONNECTED状态。通常操作中句柄将处于这两个状态之一。如果发生不可恢复的错误,如会话过期、身份鉴定失败,或者应用显式关闭,则句柄进入到CLOSED状态。下图显式了ZooKeeper客户端可能的状态转换:
资源来源zookeeper官网:https://zookeeper.apache.org/doc/current/zookeeperProgrammers.html