zookeeper学习三
1、ZK是分布式协调用的(只要有东西被修改了,则马上会有回调)
2、分布式锁
ZK配置及回调数据代码实现
如果有很多服务器,启动的时候需要获取配置,并且在运行的时候会发布订阅修改配置,
那么我们可以用zk去存配置的数据data,那么客户端可以watch这些数据是否修改,
并且能够取回这些数据。
代码实现:
首先定义一个ZKUtils,为的是快速创建zk的连接,抽取出
package com.lsd.zookeeper.config;
import org.apache.zookeeper.ZooKeeper;
import java.util.concurrent.CountDownLatch;
/**
* @ClassName ZKUtils
* @Description
* @Author SD.LIU
* @Date 2022/8/19 10:40
* @Version 1.0
**/
public class ZKUtils {
public static ZooKeeper zooKeeper;
/**
* 指定工作目录
*/
private static final String ADDRESS = "192.168.75.128:2181,192.168.75.130:2181,192.168.75.131:2181,192.168.75.132:2181/testConf";
private static DefaultWatch watch = new DefaultWatch();
private static CountDownLatch countDownLatch = new CountDownLatch(1);
public static ZooKeeper getZooKeeper() throws Exception {
zooKeeper = new ZooKeeper(ADDRESS, 1000, watch);
watch.setCountDownLatch(countDownLatch);
countDownLatch.await();
return zooKeeper;
}
}
由于是异步的去连接ZK的:
并且watchevent的stat能获取到这个连接是否是已连接了的,所以我们要等到ZK给的回调为连接成功了,我们才能把CountDownLatch给减一。
package com.lsd.zookeeper.config;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import java.util.concurrent.CountDownLatch;
/**
* @ClassName DefaultWatch
* @Description
* @Author SD.LIU
* @Date 2022/8/19 10:45
* @Version 1.0
**/
public class DefaultWatch implements Watcher {
private CountDownLatch countDownLatch;
public void setCountDownLatch(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void process(WatchedEvent event) {
System.out.println("new zk default watch:" + event.toString());
switch (event.getState()) {
case Unknown:
break;
case Disconnected:
break;
case NoSyncConnected:
break;
case SyncConnected:
countDownLatch.countDown();
break;
case AuthFailed:
break;
case ConnectedReadOnly:
break;
case SaslAuthenticated:
break;
case Expired:
break;
}
}
}
再写如下测试类,注意这里要建立一个WatchCallBack,分别是一个监听节点的watcher,以及状态回调
的StatCallback,以及数据回调的DataCallback:
package com.lsd.zookeeper.config;
import org.apache.zookeeper.ZooKeeper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* @ClassName TestConfig
* @Description
* @Author SD.LIU
* @Date 2022/8/19 10:40
* @Version 1.0
**/
public class TestConfig {
ZooKeeper zk;
@Before
public void conn() throws Exception {
zk = ZKUtils.getZooKeeper();
}
@After
public void close() throws InterruptedException {
zk.close();
}
@Test
public void getConf() throws InterruptedException {
WatchCallBack watchCallBack = new WatchCallBack();
watchCallBack.setZooKeeper(zk);
MyConf myConf = new MyConf();
//两个线程共享同一个引用
watchCallBack.setMyConf(myConf);
//1、节点不存在,会一直等创建节点的回调
//2、节点已存在,则直接回调取数据
watchCallBack.aWait();
while (true) {
if (myConf.getConf().equals("")) {
System.out.println("conf loss....");
watchCallBack.aWait();
} else {
System.out.println(myConf.getConf());
}
Thread.sleep(10000);
}
}
}
package com.lsd.zookeeper.config;
import org.apache.zookeeper.AsyncCallback;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import java.util.concurrent.CountDownLatch;
/**
* @ClassName WatchCallBack
* @Description
* @Author SD.LIU
* @Date 2022/8/19 10:59
* @Version 1.0
**/
public class WatchCallBack implements Watcher, AsyncCallback.StatCallback, AsyncCallback.DataCallback {
private ZooKeeper zooKeeper;
private MyConf myConf;
CountDownLatch countDownLatch = new CountDownLatch(1);
public MyConf getMyConf() {
return myConf;
}
public void setMyConf(MyConf myConf) {
this.myConf = myConf;
}
public ZooKeeper getZooKeeper() {
return zooKeeper;
}
public void setZooKeeper(ZooKeeper zooKeeper) {
this.zooKeeper = zooKeeper;
}
/**
* dataCallBack
*
* @param rc
* @param path
* @param ctx
* @param data
* @param stat
*/
@Override
public void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) {
if(data !=null) {
String test = new String(data);
myConf.setConf(test);
countDownLatch.countDown();
}
}
/**
* statCallBack
*
* @param rc
* @param path
* @param ctx
* @param stat
*/
@Override
public void processResult(int rc, String path, Object ctx, Stat stat) {
//代表节点已存在
if (stat != null) {
// 回调方法processResult(int rc, String path, Object ctx, byte[] data, Stat stat)
zooKeeper.getData("/AppConf",this,this,"csd");
}
}
public void aWait() {
//testConf/AppConf
zooKeeper.exists("/AppConf",this,this,"ctx");
// 回调方法 processResult(int rc, String path, Object ctx, Stat stat)
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* watchCallBack
* 当节点发生变化的时候
* @param event
*/
@Override
public void process(WatchedEvent event) {
switch (event.getType()) {
case None:
break;
case NodeCreated:
//节点刚被创建,还是走取数据的流程
zooKeeper.getData("/AppConf",this,this,"csd");
break;
case NodeDeleted:
//删除节点就是容忍性的问题
myConf.setConf("");
countDownLatch = new CountDownLatch(1);
break;
case NodeDataChanged:
//节点数据被变更了,则重新调用更新配置
zooKeeper.getData("/AppConf",this,this,"csd");
break;
case NodeChildrenChanged:
break;
}
}
}
还有一个配置类是存返回数据的,因为是异步回调,主线程和回调的线程不是同一个线程
,所以这里需要让主线程把这个myconf对象传入进去,并且让回调的线程再把这个myconf的值给设置进去:
package com.lsd.zookeeper.config;
/**
* @ClassName MyConf
* @Description 这个class是未来最关心的地方
* @Author SD.LIU
* @Date 2022/8/19 11:11
* @Version 1.0
**/
public class MyConf {
private String conf;
public String getConf() {
return conf;
}
public void setConf(String conf) {
this.conf = conf;
}
}
刚启动是阻塞的状态
然后创建一个我客户端设置的节点
此时是有循环打印的:
再重新更改一下这个节点的内容:
那么程序中就会监听到并且打印新的内容:
删除配置时打印:
再重新创建更新节点会有如下打印:
zooKeeper分布式锁的概念
这里拿两个节点来举例,里面有业务只能同步执行,不能两个业务都执行那个业务,则需要
zookeeper来实现一把分布式锁。
分布式锁:
1、争抢锁,只有一个人获得锁
2、获得锁的人出问题了,采用zookeeper的临时节点(session)来规避
3、获得锁成功的人,需要释放锁
4、锁被释放了,删除了,别人是怎么知道的
4-1:主动轮询,心跳,弊端:会有延迟,如果服务器多了以后,会造成zookeeper
压力变大
4-2:使用watch:解决延迟的问题,弊端:通信上面有压力【当其中一个节点释放锁后
,zookeeper会回调其他的所有节点,然后其他的所有节点再抢锁】
4-3:序列节点(sequence)+watch:watch谁?每一个sequnce的人都watch前一个
,最小的获得锁,一旦最小的释放了锁,成本:zk只给第二个发事件回调
分布式锁代码实现
说明见代码注释:
package com.lsd.zookeeper.lock;
import com.lsd.zookeeper.config.ZKUtils;
import org.apache.zookeeper.ZooKeeper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* @ClassName TestLock
* @Description
* @Author SD.LIU
* @Date 2022/8/19 13:37
* @Version 1.0
**/
public class TestLock {
ZooKeeper zk;
@Before
public void conn() throws Exception {
zk = ZKUtils.getZooKeeper();
}
@After
public void close() throws InterruptedException {
zk.close();
}
@Test
public void lock() {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
//每一个线程做的事情
WatchCallBack watchCallBack = new WatchCallBack();
watchCallBack.setZooKeeper(zk);
String threadName = Thread.currentThread().getName();
watchCallBack.setThreadName(threadName);
//1、抢锁
watchCallBack.tryLock();
//2、干活,如果把如下代码注释掉的话可能会产生,我第二个线程在监控之前第一个线程已经把要监控的节点删除了
System.out.println("gan huo ....");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//3、释放锁
watchCallBack.unLock();
}).start();
}
while (true) {
}
}
}
package com.lsd.zookeeper.lock;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* @ClassName WatchCallBack
* @Description
* @Author SD.LIU
* @Date 2022/8/19 13:42
* @Version 1.0
**/
public class WatchCallBack implements Watcher, AsyncCallback.StringCallback, AsyncCallback.Children2Callback, AsyncCallback.StatCallback {
private ZooKeeper zooKeeper;
private String threadName;
private CountDownLatch countDownLatch = new CountDownLatch(1);
private String pathName;
public String getPathName() {
return pathName;
}
public void setPathName(String pathName) {
this.pathName = pathName;
}
public String getThreadName() {
return threadName;
}
public void setThreadName(String threadName) {
this.threadName = threadName;
}
public ZooKeeper getZooKeeper() {
return zooKeeper;
}
public void setZooKeeper(ZooKeeper zooKeeper) {
this.zooKeeper = zooKeeper;
}
public void tryLock() {
try {
//每个线程创建节点,会得到有序的带序列号的
// /testLock/lock
System.out.println(threadName + " create....");
// if(zooKeeper.getData("/")),同时创建十个lock目录
zooKeeper.create("/lock", threadName.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL, this, "abc");
//为了解决第一个节点删除第二个节点监控不到的问题,加上一步将自己的信息写入,并且也是锁的重入
zooKeeper.setData("/", threadName.getBytes(), -1);
countDownLatch.await();
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void unLock() {
try {
// -1忽略版本的判定
zooKeeper.delete(pathName, -1);
System.out.println(threadName + " over work");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
/**
* zooKeeper.exists watch
*
* @param event
*/
@Override
public void process(WatchedEvent event) {
//如果第一个人的锁释放了,其实只有第二个收到了回调的事件
//如果,不是第一个人,某一个挂了,那么也能造成它后边的节点收到这个通知,从而让它后面的人
//去监控挂掉的这个机器的前一个
switch (event.getType()) {
case None:
break;
case NodeCreated:
break;
case NodeDeleted:
//重新去获取锁的目录,重新完成监控
zooKeeper.getChildren("/", false, this, "cde");
break;
case NodeDataChanged:
break;
case NodeChildrenChanged:
break;
}
}
/**
* StringCallBack
* create的回调
*
* @param rc
* @param path
* @param ctx
* @param name
*/
@Override
public void processResult(int rc, String path, Object ctx, String name) {
if (name != null) {
//返回对应的名称
System.out.println(threadName + "create node : " + name);
this.pathName = name;
//testLock,父节点不需要监控 回调到processResult
//此处watch为false的原因为锁不需要关注其目录的变化,要关注它前面的变化,然后进入getChildren回调方法
zooKeeper.getChildren("/", false, this, "cde");
}
}
/**
* getChildren call back
*
* @param rc
* @param path
* @param ctx
* @param children
* @param stat
*/
@Override
public void processResult(int rc, String path, Object ctx, List<String> children, Stat stat) {
//创建成功后看子节点的目录回调,能看到前面的包含自己的所有节点
/* System.out.println(threadName+"look locks");
for (String child : children) {
System.out.println(child);
}*/
//此时发现上面是乱序的,将自己的children排序
Collections.sort(children);
//判断自己是不是在1位置上面
int i = children.indexOf(pathName.substring(1));
if (i == 0) {
//如果是第一个,那么拿到锁,自己countdown,那么自己await的那个地方就能顺着往下走
System.out.println(threadName + " :i am first");
countDownLatch.countDown();
} else {
// no 监控的是前面的一个节点,而不是自己的节点,如果前边的节点发生删除的时候才会回调自己
//找前面一个并放一个watch进去,监控他是否被删除了,如果它前面一个被删除了
// 那么就得重新触发排序看自己是否是第一个
//然后此处的回调暂时没写
zooKeeper.exists("/" + children.get(i - 1), this, this, "asd");
}
}
@Override
public void processResult(int rc, String path, Object ctx, Stat stat) {
//暂不加回调逻辑
}
}
结果如下: