Zookeeper笔记
Zookeeper
概念
Zookeeper从设计模式角度来理解,是一个基于观察者模式设计的分布式服务管理框架
Zookeeper = 文件系统 + 通知机制
负责存储和管理大家都关心的数据,然后接收观察者的注册,ZK负责通知在ZK上注册的那些观察者做出相应的反应
特点
- 由一个leader和多个follwer组成
- 集群中只要有半数以上的节点存活就能正常服务
- 全局数据一致,无论Client连接到那个server,数据都一样
- 更新请求顺序进行
- 数据更新原子性(一次数据更新要么成功要么失败)
- 实时性
- ZK中数据保存的格式为树状结构,没有文件的概念,节点下直接存的就是内容
如果不是第一次搭集群,需要把ZK安装目录下的zkData目录和logs删除
组成
节点
分为持久型和短暂型,序号型和不带序号型
持久化目录节点:客户端与ZK断开连接后,节点依然存在
持久化顺序编号目录节点:客户端与ZK断开连接后,节点依然存在,且节点名称含有顺序编号
临时目录节点:客户端与ZK断开连接后,节点被删除
临时顺序编号目录节点:客户端与ZK断开连接后,节点被删除,且节点名称含有顺序编号
Stat结构体
(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方法
部署方式
部署方式分为单机模式,集群模式和伪集群模式
- 单机部署:一般用来检验 Zookeeper 基础功能,熟悉 Zookeeper 各种基础操作及特性。
- 伪集群部署:在单台机器上部署集群,方便在本地验证集群模式下的各种功能。
- 集群部署:一般在生产环境使用,具备一致性、分区容错性。
ZK常用命令
zkServer.sh start
zkServer.sh status
zkCli.sh 启动客户端
ls /
get /
delete
API调用
public class ZookeeperTest {/* 1. 获取ZK客户端连接对象* 2. 调用相关API完成对应功能* 3. 关闭资源/private ZooKeeper zk;@Beforepublic 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("根据具体业务进行下一步操作(监听事件发生了,该做什么)");}});}@Afterpublic 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> acl,CreateMode createModezk.create("/honglou","jiabaoyu".getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);}// 获取子节点@Testpublic void testGetChild() throws KeeperException, InterruptedException {//String path, boolean watch
// 不监控:// List<String> 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);}}// 判断节点是否存在@Testpublic void testExist() throws KeeperException, InterruptedException {Stat exists = zk.exists("/", false);if (exists==null)System.out.println("节点不存在");elseSystem.out.println("节点存在");}/* 获取子节点数据*/@Testpublic 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));}}/* 修改节点*/@Testpublic 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());}}/* 删除单个节点/@Testpublic 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());}}/* 删除有子节点的节点* 使用递归*/@Testpublic 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);}}}
}