Zookeeper从设计模式角度来理解,是一个基于观察者模式设计的分布式服务管理框架
Zookeeper = 文件系统 + 通知机制
负责存储和管理大家都关心的数据,然后接收观察者的注册,ZK负责通知在ZK上注册的那些观察者做出相应的反应
如果不是第一次搭集群,需要把ZK安装目录下的zkData目录和logs删除
分为持久型和短暂型,序号型和不带序号型
持久化目录节点:客户端与ZK断开连接后,节点依然存在
持久化顺序编号目录节点:客户端与ZK断开连接后,节点依然存在,且节点名称含有顺序编号
临时目录节点:客户端与ZK断开连接后,节点被删除
临时顺序编号目录节点:客户端与ZK断开连接后,节点被删除,且节点名称含有顺序编号
(1)czxid-创建节点的事务zxid
每次修改ZooKeeper状态都会收到一个zxid形式的时间戳,也就是ZooKeeper事务ID。
事务ID是ZooKeeper中所有修改总的次序。每个修改都有唯一的zxid,如果zxid1小于zxid2,那么zxid1在zxid2之前发生。
(2)ctime - znode被创建的毫秒数(从1970年开始)
(3)mzxid - znode最后更新的事务zxid
(4)mtime - znode最后修改的毫秒数(从1970年开始)
(5)pZxid-znode最后更新的子节点zxid
(6)cversion - znode子节点变化号,znode子节点修改次数
(7)dataversion - znode数据变化号
(8)aclVersion - znode访问控制列表的变化号
(9)ephemeralOwner- 如果是临时节点,这个是znode拥有者的session id。如果不是临时节点则是0。
(10)dataLength- znode的数据长度
(11)numChildren - znode子节点数量
半数存活原则表明ZK适合奇数台机器
选举机制总原则:集群中的每台机器都参与投票,通过交换选票得到每台机器的最终得票,一旦出现得票数超过机器总数一半以上数量,当前机器即为leader。
以5台机器为例,集群的机器顺时启动,当前集群中没有任何数据。
①. server1 启动,首先server1给自己投一票,然后看当前票数是否超过半数,结果没有超过,这时候leader就没选出来,当前选举状态是Locking状态。
②. server2 启动,首先server2先给自己投一票,因为当前集群已经有两台机器已启动,所以server1,server2会交换选票,交换后发现各自有一票,接下来比较myid 发现server2的myid值 > server2的myid值。此时server2胜出,最后server2有两票。最后再看当前票数是否半,发现未过半,集群的选举状态集训保持locking状态。
③. server3启动, 首先自己投自己一票,server1和server2也会投自己一票,然后交换选票发现都一样,接着比较myid 最后server3胜出,此时server3就有3票,同时server3的票数超过半数。所以server3成为leader。
④. server4启动,发现当前集群已经有leader 它自己自动成为follower
⑤. server5启动,发现当前集群已经有leader 它自己自动成为follower
以5台机器为例,当前集群正在使用(有数据/没数据),leader突然宕机的情况。
当集群中的leader挂掉,集群会重新选出一个leader,此时首先会比较每一台机器的czxid,czxid最大的被选为leader。极端情况,czxid都相等的情况,那么就会直接比较myid。
首先有一个main()线程
在main线程中创建ZK客户端,这是创建两个线程:一个负责网络连接通信connect,一个负责监听listener
通过connnect线程将注册的监听事件发送给ZK
在ZK的注册监听器列表中将注册的监听事件添加至列表
ZK监听到有数据或者路径变化时,将消息发送给listener
listener内部调用process方法
部署方式分为单机模式,集群模式和伪集群模式
zkServer.sh start
zkServer.sh status
zkCli.sh 启动客户端
ls /
get /
delete
public class ZookeeperTest {
/**
* 1. 获取ZK客户端连接对象
* 2. 调用相关API完成对应功能
* 3. 关闭资源
*
*/
private ZooKeeper zk;
@Before
public void init() throws IOException {
// String connectString, int sessionTimeout, Watcher watcher
// connectString 连接zk服务的地址 hadoop102:2181, hadoop103:2181, hadoop104:2181
// sessionTimeout 超时时间
String connectStr="hadoop102:2181, hadoop103:2181, hadoop104:2181";
int sessionTimeout=10000;
zk= new ZooKeeper(connectStr, sessionTimeout, new Watcher() {
public void process(WatchedEvent watchedEvent) {
System.out.println("根据具体业务进行下一步操作(监听事件发生了,该做什么)");
}
});
}
@After
public void close() throws InterruptedException {
zk.close();
}
@Test
/**
* 创建节点
* 1.path:指定创建节点的路径
* 2.data:指定要创建的节点下的数据
* 3.acl:对操作用户的权限控制
* 4.createMode:指定当前节点的类型(持久化、临时、临时+序列、持久化+序列
*/
public void testCreate() throws KeeperException, InterruptedException {
// final String path, byte data[], List acl,CreateMode createMode
zk.create("/honglou","jiabaoyu".getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
// 获取子节点
@Test
public void testGetChild() throws KeeperException, InterruptedException {
//String path, boolean watch
// 不监控:
// List children = zk.getChildren("/", false);
// 监控
List<String> children = zk.getChildren("/", new Watcher() {
public void process(WatchedEvent event) {
System.out.println("你好。" );
}
});
for (String child : children) {
System.out.println(child);
}
}
// 判断节点是否存在
@Test
public void testExist() throws KeeperException, InterruptedException {
Stat exists = zk.exists("/", false);
if (exists==null)
System.out.println("节点不存在");
else
System.out.println("节点存在");
}
/**
* 获取子节点数据
*/
@Test
public void testGetData() throws KeeperException, InterruptedException {
//final String path, Watcher watcher, Stat stat
// 判断节点是否存在
String path="/sanguo";
Stat stat=zk.exists(path,false);
System.out.println("======================");
if (stat==null)
System.out.println("节点不存在");
else {
byte[] data = zk.getData(path, false, stat);
System.out.println(new String(data));
}
}
/**
* 修改节点
*/
@Test
public void testSetData() throws KeeperException, InterruptedException {
String path="/sanguo";
Stat stat=zk.exists(path,false);
if (stat==null)
System.out.println("节点不存在");
else {
//final String path, byte data[], int version
// version 版本号,保证数据的一致性
zk.setData(path,"zhuge,abei".getBytes(),stat.getVersion());
}
}
/**
* 删除单个节点
*
*/
@Test
public void testDel() throws KeeperException, InterruptedException {
String path="/sanguo/shuhan";
Stat stat=zk.exists(path,false);
if (stat==null)
System.out.println("节点不存在");
else {
//final String path, byte data[], int version
// version 版本号,保证数据的一致性
zk.delete(path,stat.getVersion());
}
}
/**
* 删除有子节点的节点
* 使用递归
*/
@Test
public void testDelAll() throws KeeperException, InterruptedException {
delAll("/a",zk);
}
public void delAll(String path,ZooKeeper zk) throws KeeperException, InterruptedException {
Stat stat=zk.exists(path,false);
if (stat==null)
System.out.println("节点不存在");
else {
//获取当前节点的子节点
List<String> children = zk.getChildren(path, false);
if (children.isEmpty()){
zk.delete(path,stat.getVersion());
}
else {
// 递归调用自己,先把所有子节点删除
for (String child : children) {
delAll(path+"/"+child,zk);
}
//最后删除自己
delAll(path,zk);
}
}
}
}