PERSISTENT-持久化目录节点
客户端与zookeeper断开连接后,该节点依旧存在
PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点
客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号
EPHEMERAL-临时目录节点
客户端与zookeeper断开连接后,该节点被删除
EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点
客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号
常用命令详情
docker 下操作 zoopkeeper
docker zookeeper 启动成功 ,简单操作zoopkeeper ,节点不能重复创建
[zk: localhost:2181(CONNECTED) 3] create /t1 ck
Created /t1
[zk: localhost:2181(CONNECTED) 4] create /t2 zk
Created /t2
[zk: localhost:2181(CONNECTED) 5] get /t1
ck
[zk: localhost:2181(CONNECTED) 6] set /t1 aaaa
[zk: localhost:2181(CONNECTED) 7] get /t1
aaaa
[zk: localhost:2181(CONNECTED) 8] delete /t1
[zk: localhost:2181(CONNECTED) 9] get /t1
Node does not exist: /t1
[zk: localhost:2181(CONNECTED) 10] create /t2 zk
Node already exists: /t2
#子节点的操作
[zk: localhost:2181(CONNECTED) 12] create /t1/y1 bbb
Created /t1/y1
[zk: localhost:2181(CONNECTED) 13] ls /t1
[y1]
[zk: localhost:2181(CONNECTED) 14] delete /t1/y1
[zk: localhost:2181(CONNECTED) 15] delete /t1
[zk: localhost:2181(CONNECTED) 16] get /t1
Node does not exist: /t1
// -e :创建:临时节点
[zk: localhost:2181(CONNECTED) 0] create -e /app1
Created /app1
// 退出客户端后 临时节点 app1 消失了
[zk: localhost:2181(CONNECTED) 4] quit
[zk: localhost:2181(CONNECTED) 1] ls /
[t2, zookeeper]
[zk: localhost:2181(CONNECTED) 2] create -s /app1
Created /app10000000004
[zk: localhost:2181(CONNECTED) 3] create -s /app1
Created /app10000000005
[zk: localhost:2181(CONNECTED) 4] create -s /app1
Created /app10000000006
[zk: localhost:2181(CONNECTED) 5] create -s /app1
Created /app10000000007
[zk: localhost:2181(CONNECTED) 6] ls /
[app10000000004, app10000000005, app10000000006, app10000000007, t2, zookeeper]
Created /app20000000008
[zk: localhost:2181(CONNECTED) 8] create -es /app2
Created /app20000000009
[zk: localhost:2181(CONNECTED) 9] create -es /app2
Created /app20000000010
[zk: localhost:2181(CONNECTED) 10] ls /
[app10000000004, app10000000005, app10000000006, app10000000007, app20000000008, app20000000009, app20000000010, t2, zookeeper]
[zk: localhost:2181(CONNECTED) 11] quit
我是再2022年 docker 下安装的zookeeper ,没有 ls2 命令 !!!
用以下命令
[zk: localhost:2181(CONNECTED) 4] ls -s /
[app10000000004, app10000000005, app10000000006, app10000000007, t2, zookeeper]
cZxid = 0x0
ctime = Thu Jan 01 00:00:00 UTC 1970
mZxid = 0x0
mtime = Thu Jan 01 00:00:00 UTC 1970
pZxid = 0x17
cversion = 16
dataVersion = 0
aclVersion = 0
#是否临时
ephemeralOwner = 0x0
dataLength = 0
#有几个子节点
numChildren = 6
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.2.0</version>
</dependency>
<!-- curator-recipes -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>
RetryUntil elapsed(v. (时间)消逝,流逝;) : 重试总共时间,到了结束不重试
@Test
void contextLoads() {
//重试策略
RetryPolicy retry = new ExponentialBackoffRetry(3000, 10);
//创建连接
/* CuratorFramework client = CuratorFrameworkFactory.newClient("124.222.131.252:2181", 60000, 60000, retry);
//开启连接
client.start();
*/
//namespace :起到隔离的作用 你 create /app1 == create /cunk/app1
CuratorFramework client = CuratorFrameworkFactory.builder().connectString("124.222.131.252:2181")
.retryPolicy(retry).namespace("cunk").build();
}
前置知识 测试生命周期:
测试实例生命周期
为了允许隔离执行单个的测试方法,并避免由于可变测试实例状态而产生的意外副作用,JUnit在执行每个测试方法之前创建每个测试类的新实例(请参阅下面的讲解,何为测试方法)。这个”per-method”测试实例生命周期是JUnit Jupiter中的默认行为,类似于JUnit以前的所有版本。
如果您希望JUnit Jupiter在同一个测试实例上执行所有测试方法,只需使用@TestInstance(Lifecycle.PER_CLASS)对您的测试类进行注解即可。当使用这种模式时,每个测试类将创建一个新的测试实例。因此,如果您的测试方法依赖于存储在实例变量中的状态,则可能需要在@BeforeEach或@AfterEach方法中重置该状态。
“per-class”模式比默认的”per-method”模式有一些额外的好处。具体来说,使用”per-class”模式,可以在非静态方法和接口默认方法上声明@BeforeAll和@AfterAll(否则@BeforeAll与@AfterAll必须是注解在static的方法上才能生效)。因此,”per-class”模式也可以在@Nested测试类中使用@BeforeAll和@AfterAll方法。
如果使用Kotlin编程语言编写测试,则可能会发现,通过切换到”per-class”测试实例生命周期模式,可以更轻松地实现@BeforeAll和@AfterAll方法。
来源于: https://blog.csdn.net/HD243608836/article/details/101000429
推荐一个宝藏网站 : https://www.bookstack.cn/
节点类型枚举
@Public
public enum CreateMode {
PERSISTENT(0, false, false, false, false),
PERSISTENT_SEQUENTIAL(2, false, true, false, false),
EPHEMERAL(1, true, false, false, false),
EPHEMERAL_SEQUENTIAL(3, true, true, false, false),
CONTAINER(4, false, false, true, false),
PERSISTENT_WITH_TTL(5, false, false, false, true),
PERSISTENT_SEQUENTIAL_WITH_TTL(6, false, true, false, true);
@SpringBootTest
class ZookeeperApplicationTests {
CuratorFramework client ;
//在任何test方法执行之气那执行
//相对于after
@BeforeEach
void contextLoads() {
//重试策略
RetryPolicy retry = new ExponentialBackoffRetry(3000, 10);
//创建连接
/* CuratorFramework client = CuratorFrameworkFactory.newClient("124.222.131.252:2181", 60000, 60000, retry);
//开启连接
client.start();
*/
//namespace :起到隔离的作用
client = CuratorFrameworkFactory.builder().connectString("***.***.***.***:2181")
.retryPolicy(retry).namespace("cunk").build();
client.start();
//创建(create)节点 持久 临时 顺序 数据
// 1.创建基本
}
@Test
public void create() throws Exception {
String path = client.create().forPath("/ck1");
System.out.println(path);
}
res:
[zk: localhost:2181(CONNECTED) 7] ls /
[app10000000004, app10000000005, app10000000006, app10000000007, cunk, t2, zookeeper]
[zk: localhost:2181(CONNECTED) 8] ls /cunk
[ck1]
[zk: localhost:2181(CONNECTED) 9] get /cunk/ck1
***.***.*** === 默认是有数据 的 是当前机器的ip地址
@AfterEach
public void close(){
if (client!=null){
client.close();
}
}
}
@Test
public void createWithDateAndEPHEMERAL() throws Exception {
//创建临时节点 ,当前会话一但结束 ,节点瞬间消失!!!
/*
* 临时节点消失策略 : 会话结束边消失
* 1.在linux 上调用客户端 然后退出客户端 ,节点消失
* 2.在java api上创建节点 ,然后关闭java客户端 ,连接终端 ,节点消失
* */
client.create().withMode(CreateMode.EPHEMERAL).forPath("/app3","hi~".getBytes(StandardCharsets.UTF_8)) ;
}
while(true){} :可以一直保持连接
然程序空转会一直保持连接
[zk: localhost:2181(CONNECTED) 12] ls /cunk
[app3, ck1]
假如我在linux 退出zk 会怎样呢 ? 临时节点还会存在吗?
WatchedEvent state:SyncConnected type:None path:null
[zk: localhost:2181(CONNECTED) 0] ls /cunk
[app3, ck1]
^[[A[zk: localhost:2181(CONNECTED) 0] get /cunk/app3
hi~
可以发现 ,在linux 客户端启动 zk 然后退出 并不会 对 java api与zk 建立连接后创建的临时节点有任何影响,说明他们是两次不同的会话,不同会话产生的临时节点的消失 ,代表着 相对应会话的消失
此时关闭java客户端再查 app3
[zk: localhost:2181(CONNECTED) 4] get /cunk/app3
Node does not exist: /cunk/appc
//创建多级节点
@Test
public void createWithParent() throws Exception{
//creatingParentContainersIfNeeded() :调用这个方法即使父节点不存在也可以创建多级节点
client.create().creatingParentContainersIfNeeded().forPath("/app4/cunk1");
}
@Test
public void checkWithParent() throws Exception {
byte[] bytes = client.getData().forPath("/cunk2");
System.out.println("========================================================");
System.out.println("查询结果是: 》》"+new String(bytes)+" <<<<<");
}
@Test
public void checkForParh() throws Exception {
List<String> list = client.getChildren().forPath("/app4");
System.out.println("========================================================");
System.out.println("查询结果是: 》》"+list+" <<<<<");
}
@Test
public void checkForParh3() throws Exception {
//创建一个stat 查询出来的节点信息会封装进 stat
Stat stat = new Stat();
byte[] bytes = client.getData().storingStatIn(stat).forPath("/cunk2");
System.out.println("========================================================");
System.out.println("查询结果是: 》》"+stat+" <<<<<");
}
========================================================
查询结果是: 》》59,59,1660367996068,1660367996068,0,0,0,0,10,0,59
@Test
public void pathset() throws Exception {
//修改app4 节点
client.setData().forPath("/app4", "new data".getBytes(StandardCharsets.UTF_8));
//再次查询app4节点的数值
byte[] bytes = client.getData().forPath("/app4");
System.out.println("========================================================");
System.out.println("查询结果是: 》》"+new String(bytes)+" <<<<<");
}
前置知识 乐观锁,为什么更新需要带上版本号:乐观锁链接
/* 当查询值为 1 时候 准备让值加 1 ,但是多线程情况下别的线程会对该值加一,总共会加 2
修改数据一般需要带上版本号
查询出来的版本号和 当前版本号相同才进行修改 (乐观锁)
*/
@Test
public void pathsetForVersion() throws Exception {
//查询版本号
Stat stat = new Stat();
byte[] bytes = client.getData().storingStatIn(stat).forPath("/app4");
//根据版本号就行修改
int version = stat.getVersion();
client.setData().withVersion(version).forPath("/app4","version Data".getBytes(StandardCharsets.UTF_8));
//再次查询app4节点的数值
System.out.println("========================================================");
System.out.println("查询结果是: 》》"+new String(bytes)+" <<<<<");
}
linux 查询数据:
[zk: localhost:2181(CONNECTED) 11] get /cunk/app4
versionDate
```css
//删除节点
// 1. 删除单个节点 2. 删除子节点的节点 3.必须删除成功 4. 回调
@Test
public void pathsetDelete() throws Exception {
//删除单个节点
client.delete().forPath("/app4") ;
}
//删除带有子节点的节点
@Test
public void pathsetDeletea() throws Exception {
client.delete().deletingChildrenIfNeeded().forPath("/app4");
}
//必须删除成功 ,不断重试删除节点 。类似于rabbitmq 的消息发不出去的重试机制
//在测试前可以关闭zookeeper,启动测试,过一段时间再连接发现还能删除。
@Test
public void mustpathsetDeletea() throws Exception {
client.delete().guaranteed().forPath("/app4/cunk1") ;
}
//回调
@Test
public void returnDeletea() throws Exception {
client.delete().guaranteed().inBackground(new BackgroundCallback() {
//回调函数
@Override
public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {
System.out.println("删除成功!!!!!");
System.out.println(curatorEvent);
}
}).forPath("/app4") ;
}
res :
exiting
删除成功!!!!!
CuratorEventImpl{type=DELETE, resultCode=0, path='/app4', name='null', children=null, context=null, stat=null, data=null, watchedEvent=null, aclList=null, opResults=null}
2022-08-13 14:25:07.980 INFO 26036 --- [ain-EventThread] org.apache.zookeeper.ClientCnxn : EventThread shut down for session: 0x1005b20cf740027
2022-08-13 14:25:07.980 INFO 26036 --- [ main] org.apache.zookeeper.ZooKeeper : Session: 0x1005b20cf740027 closed
类似于观察者模式+发布订阅 。 当一个节点信息改变 他会告诉其他的节点。
@Test
public void lensen() throws Exception {
//创建 NodeCache 监听(/cunk/ck1)上的变动
NodeCache nodeCache = new NodeCache(client, "/ck1");
// 注册监听
nodeCache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
System.out.println("节点变化了 ~~~");
//获取修改后的节点数据
byte[] data = nodeCache.getCurrentData().getData();
System.out.println(new String(data));
}
});
// 开启监听 buildInitial:true ,开启监听时候开启缓存
nodeCache.start(true);
//保证一直监听
while (true){
}
}
@Test
public void lensenC() throws Exception {
//创建 NodeCache 监听(/cunk/ck1)上的变动
PathChildrenCache pathChildrenCache = new PathChildrenCache(client, "/ck1", true);
// 注册监听
pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
System.out.println("儿子们变化了 ~~~");
System.out.println(pathChildrenCacheEvent);
// 监听孩子们 的数据变更
PathChildrenCacheEvent.Type childrenCacheEventType = pathChildrenCacheEvent.getType();
//判断孩子节点是不是更新
if (childrenCacheEventType.equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)){
log.info("有个孩子节点更新咯 ~~");
byte[] data = pathChildrenCacheEvent.getData().getData();
System.out.println(new String(data));
}
}
});
//监听孩子们节点
@Test
public void lensenAll() throws Exception {
//创建 NodeCache 监听(/cunk/ck1)上的变动
TreeCache treeCache = new TreeCache(client, "/ck1");
// 注册监听
treeCache.getListenable().addListener(new TreeCacheListener() {
@Override
public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent treeCacheEvent) throws Exception {
log.info("节点变化了 ~~");
log.info("变化情况是:{}",treeCacheEvent);
TreeCacheEvent.Type type = treeCacheEvent.getType();
if (type.equals(TreeCacheEvent.Type.NODE_ADDED)){
log.info("新增了个节点~~");
}
}
});
// 开启监听 buildInitial:true ,开启监听时候开启缓存
treeCache.start();
//保证一直监听
while (true){
}
}
要详细了解分布式锁 请移步:redis实现分布式锁…
分布式锁解决:跨机器进程之间的数据同步问题例如 抢票,秒杀。。。。
redis做分布式锁的弊端:虽然性能高 ,但是 redis挂了 ,所有人都能获得锁
核心思想: 当客户想要获得锁,先创建节点,使用完毕锁,删除该节点
为什么创建的是临时顺序节点: 假如创建的是默认持久的节点 ,然后Clint1获取锁便 宕机了 ,Clien1与zk 失去连接 ,
但是/lock/1得不到释放。锁得不到释放!!!
这种情况再redis中的解决方案是:设置TTL 过期时间 。
临时节点的作用:当客户端 异常跟zk断联, 临时节点会自动释放 。其他节点就可以获得锁
如果发现自己创建的节点不是lock所有节点序号中最小的(此时顺序节点的作用体现出来咯),
说明还没获得锁,此时客户端找到比自己小的
那个节点(找一个),同时对该节点注册监听,假如该节点删除,自己删除
lock1 删完 lock2 删 lock2 删完locak3 删
这些概念请异步至 redis实现分布式锁…
@Slf4j
public class TickCLinet implements Runnable{
private static int tick = 10 ; //10 张票
private InterProcessMutex lock ;
//创建锁
public TickCLinet(InterProcessMutex lock ){
this.lock = lock ;
}
@Override
public void run( ) {
while (true){
//获取锁
try {
// 获取失败等待3 秒
lock.acquire(3,TimeUnit.SECONDS);
if (tick > 0){
// log.info("线程:"+Thread.currentThread().getName()+"获得 了票 ");
this.wait();
tick -- ;
//log.info("当前票数:{}",tick);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
//防止程序发生异常锁得不到释放 加 finnaly 释放锁
lock.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
@Slf4j
@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
class TickTests {
private InterProcessMutex lock ;
CuratorFramework client ;
private int tickets = 10 ; //一共十张票
//在任何test方法执行之气那执行
//相对于after
@BeforeEach
void contextLoads() {
RetryPolicy retry = new ExponentialBackoffRetry(3000, 10);
client = CuratorFrameworkFactory.builder().connectString("124.222.131.252:2181")
.retryPolicy(retry).namespace("cunk").build();
client.start();
//创建分布式锁
lock = new InterProcessMutex(client, "/cunk");
}
@Test
public void selltick() throws Exception{
// 10个客户端去强票
for (int i=0;i < 10;i++){
new Thread(new TickCLinet(lock),"ClientApp_"+i).start();
}
//防止主线程先挂了
while (true){
}
}
@AfterEach
public void testNodeclose(){
if (client!=null){
client.close();
}
}
}
[zk: localhost:2181(CONNECTED) 26] ls /cunk/cunk
## 这是 zk自动创建的锁
[_c_0581849d-005e-499c-a77f-6e246726da08-lock-0000000481, _c_0858fb78-c0fc-4e8d-9726-aab3ed5fe4d5-lock-0000000488, _c_0f1afac3-c590-405d-b663-eac5eabb1cf2-lock-0000000489, _c_45fe5ebc-ff86-4904-95a4-a3fd7f931950-lock-0000000484, _c_5fc2fd51-e9a8-4fa2-8a14-036d599a14dd-lock-0000000486, _c_a8089b0b-aff9-416d-8c5e-fa90378145b5-lock-0000000485, _c_d36115ef-da09-4a94-a36e-3f0503c248f4-lock-0000000482, _c_eeb5830a-cd9d-41f3-82cb-ab3bc7a267af-lock-0000000487, _c_f43096f8-ea9a-4fe6-8068-d11a0793e5b1-lock-0000000490, _c_f54c84c4-cabe-4b59-894d-8a6b424888d2-lock-0000000483]
# 当线程执行结束 或者异常关闭之后 锁得到释放
[zk: localhost:2181(CONNECTED) 27] ls /cunk/cunk
Node does not exist: /cunk/cunk
有个小的知识点: 当程序异常结束的时候 , zk 释放锁不是瞬间释放的 ,是过了至少十秒才释放 ,意思是 一个服务宕机了 ,另外一个客户端不能瞬间获得锁 ,, 虽然这个研究有点无聊哈哈哈…
我有个问题 ,假如redis 和zk 做分布式锁 同时宕机了,redis由于watchdog 自动释放锁 , zk由于客户端断开连接 下一个获得锁的 谁快一些呢 ????
具体怎么搭建 :dockers下搭建 zk集群