目录
ZooKeeper进阶
目录结构
节点类型
ZK客户端命令行操作
ZooKeeper会话
事件监听原理刨析
事件监听Watcher
广播模式刨析
广播模式:
Zookeeper集群的特点
ZK常见的应用场景
ZK API实战
IDEA环境搭建
创建ZooKeeper客户端
- Znode数据结构
- ZK有一个最开始的节点 /
- ZK的节点叫做znode节点
- 每个znode节点都可存储数据
- 每个znode节点(临时节点除外)都可创建自己的子节点
- 多个znode节点共同形成了znode树
- Znode树的维系实在内存中,目的是供用户快速的查询
- 每个znode节点都是一个路径(通过路径来定位这个节点)
- 每个路径名都是唯一的。
层次的,目录型结构,便于管理逻辑关系
znode信息
包含最大1MB的数据信息
记录了zxid等元数据信息
znode有两种类型,临时的(ephemeral)和持久的(persistent)
znode支持序列SEQUENTIAL
临时znode
客户端会话结束时,ZooKeeper将该临时znode删除,临时znode没有子节点
持久znode
不依赖于客户端会话,只有当客户端明确要删除该持久znode时才会被删除
znode的类型在创建时确定并且之后不能再修改
有序znode节点被分配唯一单调递增的整数。
比如:客户端创建有序znode,路径为/task/task-,则ZooKeeper为其分配序号1,并追加到znode节点:
/task/task-000000001。有序znode节点唯一,同时也可根据该序号查看znode创建顺序。
znode有四种形式的目录节点
PERSISTENT:普通持久
EPHEMERAL:普通临时
PERSISTENT_SEQUENTIAL:顺序持久
EPHEMERAL_SEQUENTIAL:顺序临时
要想执行以下指令,需要先启动zk服务器端,再启动zk客户端
./zkServer.sh start:启动zk服务器端
./zkCli.sh:启动zk客户端
指令 |
示例 |
ls查看指令 |
ls / |
create创建节点指令,注意,在创建节点时,要分配初始数据。 |
create /zk01 hello create /zk02 ‘’ |
get查看节点数据指令 hello 数据 cZxid = 0x2 ctime = Mon May 15 05:58:32 PDT 2017创建节点的时间戳 mZxid = 0x2 mtime = Mon May 15 05:58:32 PDT 2017修改此节点数据的最新时间戳 pZxid = 0x2 cversion = 0 dataVersion = 0数据版本号,每当数据发生变化,版本号递增1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 5数据大小 numChildren = 0子节点个数 |
get /zk01 |
set更新节点数据指令(执行后mtime、dataVersion肯定会放生变化,dataLength可能会变化) |
set /zk01 hellozk |
delete删除节点 |
delete /zk01 |
create指令补充:
普通持久节点: 普通临时节点:创建此临时节点的客户端失去和zk连接后,此节点消失.zk是通过临时节点监控哪个服务器挂掉的。 顺序持久节点:会根据用户指定的节点路径,自动分配一个递增的顺序号。(顺序节点实现分布式锁的效果,服务器1抢到zk05分配zk050001,服务器2抢到zk05分配zk050002) 顺序临时节点: |
2.1.create /zk01 hello 2.2. create –e /zk02 abc 2.4.create –s -e /zk05 abcd zk050000000003 再创建一个就是: zk050000000004 |
quit退出zk客户端 |
1、客户端通过TCP协议与独立服务器或者一个集群中的某个服务器建立会话连接。
2、会话提供顺序保障,即同一个会话中的请求以FIFO的顺序执行。如果客户端有多个并发会话,FIFO顺序在多个会话之间未必能够保持。
3、如果连接的Server出现问题,在没有超过Timeout时间时,可以连接其他节点。ZooKeeper客户端透明地转移一个会话到不同的服务器。
4、同一session期内的特性不变
5、当一个会话因某种原因终止,在这个会话期间创建的临时节点将会消失。
Session是由谁来创建的?
Leader:产生一个唯一的session,放到消息队列,让所有server知道
过半机制:保证session创建成功或者失
事件监听原理刨析
客户端轮询指定节点下的数据
通过网络轮询,代价很大
基于通知(notification)的机制:
客户端向ZooKeeper注册需要接收通知的znode,
通过对znode设置监视点(watch)来接收通知。监视点是一个单次触发的操作,意即监视点会触发一个通知。
为了接收多个通知,客户端必须在每次通知后设置一个新的监视点。
Watcher 在 ZooKeeper 是一个核心功能,Watcher 可以监控目录节点的数据变化以及子目录的变化,一旦这些状态发生变化,服务器就会通知所有设置在这个目录节点上的Watcher,从而每个客户端都很快知道它所关注的目录节点的状态发生变化,而做出相应的反应。
可以设置观察点的操作:exists,getChildren,getData
可以触发观察的操作:create,delete,setData
回调client方法
业务核心代码在哪里?
client
ZooKeeper的核心是原子广播,这个机制保证了各个server之间的信息同步。实现这个机制的协议叫做ZAB协议。
ZAB协议有两种模式:
1.恢复模式:当服务启动或者在领导者崩溃后,ZAB就进入了恢复模式。当领导者被选举出来,且大多数server的完成了和leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和follower以及observer具有相同的系统状态
2.广播模式
广播模式需要保证proposal被按顺序处理,因此zk采用了递增的事务id号(zxid)来保证。所有的提议(proposal)都在被提出的时候加上了zxid(比如:0x1000000300000002)。
epoch也称为纪元数字。实现中zxid是一个64位的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch,低32位是个递增计数。
无主,无服务。选举过程耗时在200ms之内,一般情况下ZooKeeper恢复服务时间间隔不超过200ms
- 数据发布/订阅
- 分布式环境下的分布式锁
- 集群管理问题
在该包下建类ZooKeeperTest
import java.io.IOException;
import java.util.List;
import org.apache.ZooKeeper.CreateMode;
import org.apache.ZooKeeper.KeeperException;
import org.apache.ZooKeeper.WatchedEvent;
import org.apache.ZooKeeper.Watcher;
import org.apache.ZooKeeper.ZooDefs.Ids;
import org.apache.ZooKeeper.ZooKeeper;
import org.apache.ZooKeeper.data.Stat;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ZooKeeperTest {
private static final int SESSION_TIMEOUT = 30000;
public static final Logger LOGGER = LoggerFactory.getLogger(ZooKeeperTest.class);
private Watcher watcher = new Watcher() {
public void process(WatchedEvent event) {
LOGGER.info("process : " + event.getType());
}
};
private ZooKeeper ZooKeeper;
/**创建ZooKeeper连接,单元测试前先执行该方法
* @throws IOException
*/
@Before
public void connect() throws IOException {
// indicate : all servers
ZooKeeper = new ZooKeeper("192.168.20.52:2181,192.168.20.53:2181,192.168.20.54:2181", SESSION_TIMEOUT, watcher);
}
/**测试完毕后关闭zk连接
*/
@After
public void close() {
try {
ZooKeeper.close();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 创建 znode
* 1.CreateMode
* PERSISTENT :持久化
* PERSISTENT_SEQUENTIAL
* EPHEMERAL :临时
* EPHEMERAL_SEQUENTIAL
* Access Control List: 访问控制列表
* https://baike.baidu.com/item/ACL/362453?fr=aladdin
* OPEN_ACL_UNSAFE: ANYONE CAN VISIT
*
------------------------------
*/
@Test
public void testCreate() {
String result = null;
try {
result = ZooKeeper.create("/zk001", "zk001data-p".getBytes(),
Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
// result = ZooKeeper.create("/zk002", "zk002data-e".getBytes(),
// Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
// Thread.sleep(30000);
} catch (Exception e) {
LOGGER.error(e.getMessage());
//抛出AssertionError
Assert.fail();
}
LOGGER.info("create result : {}", result);
}
/**
* 删除
*/
@Test
public void testDelete() {
try {
ZooKeeper.delete("/zk002", -1);
} catch (Exception e) {
LOGGER.error(e.getMessage());
//抛出AssertionError
Assert.fail();
}
}
/**
* 获取数据
*/
@Test
public void testGetData() {
String result = null;
try {
byte[] bytes = ZooKeeper.getData("/zk001", null, null);
result = new String(bytes);
} catch (Exception e) {
LOGGER.error(e.getMessage());
Assert.fail();
}
LOGGER.info("getdata result------------------------------------------------- : {}", result);
}
/**查看从哪个zk服务器获取的数据,然后xshell关闭对应的zk服务器(zkServer.sh stop)
* 然后分析日志:
* @throws Exception
*/
@Test
public void testGetData01() throws Exception {
String result = null;
try {
byte[] bytes = ZooKeeper.getData("/zk001", null, null);
result = new String(bytes);
} catch (Exception e) {
LOGGER.error(e.getMessage());
Assert.fail();
}
LOGGER.info("getdata result-----------------1------------------ : {}", result);
Thread.sleep(30000);
byte[] bytes;
try {
bytes = ZooKeeper.getData("/zk001", null, null);
result = new String(bytes);
} catch (KeeperException | InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
LOGGER.info("getdata result-----------------2-------------------- : {}", result);
}
/**
* 注册
*/
@Test
public void testGetDataWatch() {
String result = null;
try {
System.out.println("get:");
byte[] bytes = ZooKeeper.getData("/zk001", new Watcher() {
public void process(WatchedEvent event) {
LOGGER.info("testGetDataWatch watch : {}", event.getType());
System.out.println("watcher ok");
//testGetDataWatch();
}
}, null);
result = new String(bytes);
} catch (Exception e) {
LOGGER.error(e.getMessage());
Assert.fail();
}
LOGGER.info("getdata result------------------------------------------ : {}", result);
// wacth NodeDataChanged
try {
System.out.println("set:");
ZooKeeper.setData("/zk001", "testSetDataWAWWW".getBytes(), -1);
System.out.println("set2:");
ZooKeeper.setData("/zk001", "testSetDataWAWWW".getBytes(), -1);
} catch (Exception e) {
LOGGER.error(e.getMessage());
Assert.fail();
}
System.out.println("over");
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Test
public void testExists() {
Stat stat = null;
try {
stat = ZooKeeper.exists("/zk001", false);
} catch (Exception e) {
LOGGER.error(e.getMessage());
Assert.fail();
}
Assert.assertNotNull(stat);
LOGGER.info("exists result : {}", stat.getCzxid());
}
@Test
public void testSetData() {
Stat stat = null;
try {
stat = ZooKeeper.setData("/zk001", "testSetData".getBytes(), -1);
} catch (Exception e) {
LOGGER.error(e.getMessage());
Assert.fail();
}
Assert.assertNotNull(stat);
LOGGER.info("exists result : {}", stat.getVersion());
}
@Test
public void testExistsWatch1() {
Stat stat = null;
try {
stat = ZooKeeper.exists("/zk001", true);
} catch (Exception e) {
LOGGER.error(e.getMessage());
Assert.fail();
}
Assert.assertNotNull(stat);
try {
ZooKeeper.delete("/zk001", -1);
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void testExistsWatch2() {
Stat stat = null;
try {
stat = ZooKeeper.exists("/zk002", new Watcher() {
@Override
public void process(WatchedEvent event) {
LOGGER.info("testExistsWatch2 watch : {}", event.getType());
}
});
} catch (Exception e) {
LOGGER.error(e.getMessage());
Assert.fail();
}
Assert.assertNotNull(stat);
try {
ZooKeeper.setData("/zk002", "testExistsWatch2".getBytes(), -1);
} catch (Exception e) {
e.printStackTrace();
}
try {
ZooKeeper.delete("/zk002", -1);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
*/
@Test
public void testGetChild() {
try {
ZooKeeper.create("/zk/001", "001".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
ZooKeeper.create("/zk/002", "002".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
List list = ZooKeeper.getChildren("/zk", true);
for (String node : list) {
LOGGER.info("fffffff {}", node);
}
} catch (Exception e) {
LOGGER.error(e.getMessage());
Assert.fail();
}
}
}
框架实现的客户端和服务器之间的故障转移过程
注册Watcher,查询注册watch,增删改触发watcher
如果是多次增删改,回调方法调用几次?
1、watcher事件是一次性的
2、是增删改触发watcher,但是watcher是线程异步执行
3、watcher可以反复注册
如何平滑反复注册?在回调方法中迭代调用即可。