zookeeper的代码实现
//用于阻塞线程
CountDownLatch cc = new CountDownLatch(1);
//减一,使线程继续运行
cc.countDown
//使进程阻塞
cc.await()
zookeeper基本演示
package com.msb.zookeeper;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.util.concurrent.CountDownLatch;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args ) throws Exception {
System.out.println( "Hello World!" );
//CountDownLatch保证线程安全,因为在创建zk对象时,对象会很快返回,但是zk异步的会去创建session,建立连接,所以需要保证线程阻塞。
final CountDownLatch cd = new CountDownLatch(1);
//zk是有session概念的,没有连接池的概念
//watch分为两类:分别是观察和回调
//watch的注册只发生在读类型调用,比如get,exites..
//第一类:new zk 时候,传入的watch,这个watch,session级别的,跟path 、node没有关系。
//连接zookeeper,端口号用,隔开;传入端口,seession过期时间,watch监控
final ZooKeeper zk = new ZooKeeper("192.168.113.138:2181,192.168.113.139:2181,192.168.113.141:2181,192.168.113.142:2181",
3000, new Watcher() {
//Watch 的回调方法!
@Override
public void process(WatchedEvent event) {
Event.KeeperState state = event.getState();
Event.EventType type = event.getType();
String path = event.getPath();
System.out.println("new zk watch: "+ event.toString()+"这里的回调和path无关,和连接状态有关");
switch (state) {
case Unknown:
break;
case Disconnected:
break;
case NoSyncConnected:
break;
//同步连接状态
case SyncConnected:
System.out.println("connected");
//在回调时cd对象-1
cd.countDown();
break;
case AuthFailed:
break;
case ConnectedReadOnly:
break;
case SaslAuthenticated:
break;
case Expired:
break;
}
switch (type) {
case None:
break;
case NodeCreated:
break;
case NodeDeleted:
break;
case NodeDataChanged:
break;
case NodeChildrenChanged:
break;
}
}
});
//阻塞线程,等上一步运行完成运行该步骤
cd.await();
//zookeeper的状态
ZooKeeper.States state = zk.getState();
switch (state) {
case CONNECTING:
//连接中,没有连接上打印
System.out.println("ing......");
break;
case ASSOCIATING:
break;
case CONNECTED:
//连接成功打印
System.out.println("ed........");
break;
case CONNECTEDREADONLY:
break;
case CLOSED:
break;
case AUTH_FAILED:
break;
case NOT_CONNECTED:
break;
}
//创建节点有两种方式,短参:同步阻塞(1、目录,2、数据二进制安全,所以需要调用getBytes(),3,ZooDefs.Ids设置zk的访问形式,4.CreateMode节点类型),长参:异步调用
String pathName = zk.create("/ooxx", "olddata".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
//用于放源数据的stat对象
final Stat stat=new Stat();
//getData分为两大类,返回byte为同步,void为异步,4种方式
//一:1、path:获取的目录 2、Watche 针对这个path再来一个watch 3、stat,用于放节点的源数据
//二:1、path:获取的目录 2、boolean false,没有watch不关心有什么事件,true watch回调时重新监听与注册 3、stat用于放节点的源数据
byte[] node = zk.getData("/ooxx", new Watcher() {
//针对/ooxx的一个watche,未来如果/ooxx发生变化,回调这个方法
@Override
public void process(WatchedEvent event) {
System.out.println("getData watch: "+event.toString()+"这里的watch和path有关");
//当回调完之后继续监控/ooxx需要的操作,因为watch只注册一次,那么我们需要手动重复注册
try {
//true,调用default Watch,也就是连接的watch 被重新注册 new zk的那个watch
// zk.getData("/ooxx",true ,stat);
//为this才是本身的watch
zk.getData("/ooxx",this ,stat);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, stat);
System.out.println(new String(node));
//触发回调,最后一个参数是版本号
Stat stat1 = zk.setData("/ooxx", "newdata".getBytes(), 0);
//还会触发吗?
Stat stat2 = zk.setData("/ooxx", "newdata01".getBytes(), stat1.getVersion());
//当连接的那个zk断开,根据日志会有3个知识点,
//1、session出现变动,session的watch会回调
//2、zk断开之后,zk对象会选择新的连接连接成功
//3、重新连接的sessionid不会变
System.out.println("-------async start----------");
//异步回调,getData的异步方法,并不会在这阻塞,因为他没有返回值
zk.getData("/ooxx", false, new AsyncCallback.DataCallback() {
@Override
public void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) {
System.out.println("-------async call back----------");
System.out.println(ctx.toString());
System.out.println(new String(data));
}
},"abc");
System.out.println("-------async over----------");
Thread.sleep(2222222);
}
}
zookeeper的分布式配置
轮询:每隔一段时间去共享资源库查询一下,避免公共资源发生改变而没有获取到
弊端:可能更新不及时,会有延迟,并且实时监控浪费资源
分布式配置:为了避免配置文件重复配置,我们把配置放在一个共享的空间内,redis,zookeeper,mysql都可以,为什么推荐zookeeper呢?,因为zookeeper可以避免对配置文件的轮询,可以直接通过watch机制来增加效率,减少资源浪费
演示
方便代码读取,把功能抽离
1、先提供一个zookeeper的工具类
package com.sy.utlis;
import org.apache.zookeeper.ZooKeeper;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
/**
* 功能: 工具类 --层
* <p>工具类 --层</p >
* <ul>
* <li>数据库: 增 删 改 查 </li>
* <li>{@link ZkUtils} connect to {@link ZkUtils}</li>
* </ul>
*
* @Program: zookeeper
* @Date: 2022-03-30 10:20
* @Version 1.0
* @Author yang.su
*/
public class ZkUtils {
public static void setZk(ZooKeeper zk) {
ZkUtils.zk = zk;
}
private static ZooKeeper zk;
//连接的字符串,后面加上一个path目录,我们的操作都将在改目录下,换言之就是我们的根变了
//private static String address="192.168.113.138:2181,192.168.113.139:2181,192.168.113.141:2181,192.168.113.142:2181/testConf";
private static String address="192.168.113.138:2181,192.168.113.139:2181,192.168.113.141:2181,192.168.113.142:2181/testLock";
private static DefaultWatch defaultWatch=new DefaultWatch();
//什么时候放开?连接成功回调session的watch的时候引用,所以需要在DefaultWatch类中引用
private static final CountDownLatch init = new CountDownLatch(1);
public static ZooKeeper getZk() throws InterruptedException {
try {
zk = new ZooKeeper(address,1000,defaultWatch);
defaultWatch.setCc(init);
init.await();
} catch (IOException e) {
e.printStackTrace();
}
return zk;
}
}
2、连接时有一个基于session的回调watch,为了代码整洁,我们提供一个类defaultWatch继承Watche
package com.sy.utlis;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import java.util.concurrent.CountDownLatch;
/**
* 功能: 封装好的session的watch,不用一直创建 --层
* <p>封装好的watch,不用一直创建 --层</p >
* <ul>
* <li>数据库: 增 删 改 查 </li>
* <li>{@link DefaultWatch} connect to {@link DefaultWatch}</li>
* </ul>
*
* @Program: zookeeper
* @Date: 2022-03-30 10:27
* @Version 1.0
* @Author yang.su
*/
public class DefaultWatch implements Watcher {
public CountDownLatch getCc() {
return cc;
}
public void setCc(CountDownLatch cc) {
this.cc = cc;
}
private CountDownLatch cc;
@Override
public void process(WatchedEvent event) {
System.out.println(event.toString());
//session的连接状态
Event.KeeperState state = event.getState();
switch (state) {
case Unknown:
break;
case Disconnected:
break;
case NoSyncConnected:
break;
case SyncConnected:
//连接成功,然后阻塞状态放开
System.out.println("连接成功,解除阻塞");
cc.countDown();
break;
case AuthFailed:
break;
case ConnectedReadOnly:
break;
case SaslAuthenticated:
break;
case Expired:
break;
case Closed:
break;
}
}
}
3、提供测试的接口
package com.sy.config;
import com.sy.utlis.WatchCallBack;
import com.sy.utlis.ZkUtils;
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 org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* 功能: 干活的类 --层
* <p>干活的类 --层</p >
* <ul>
* <li>数据库: 增 删 改 查 </li>
* <li>{@link TestConfig} connect to {@link TestConfig}</li>
* </ul>
*
* @Program: zookeeper
* @Date: 2022-03-30 10:19
* 属于代码的客户端
* @Version 1.0
* @Author yang.su
*/
public class TestConfig {
ZooKeeper zk;
@Before
public void conn() throws InterruptedException {
zk = ZkUtils.getZk();
}
@After
public void close(){
try {
zk.close();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Test
public void getConf() throws InterruptedException {
//exists监测是否改变,存在的话会回调,我们将在回调的时候get数据,因为这样写比较麻烦,我需要把他封装好
// zk.exists("/Appconf", new Watcher() {
// @Override
// public void process(WatchedEvent event) {
//
// }
// },new AsyncCallback.StatCallback() {
// @Override
// public void processResult(int rc, String path, Object ctx, Stat stat) {
// //如果该节点为空,那么stat将返回null,所以
// if (stat !=null){
// zk.getData()
// }
// }
// }, "abc");
WatchCallBack watchCallBack = new WatchCallBack();
watchCallBack.setZk(zk);
//异步调用,如何让本线程获取exists回调里面的getData数据呢?
//创建个数据接收类
MyConf myConf = new MyConf();
watchCallBack.setMyConf(myConf);
watchCallBack.aWait();
//1、节点不存在
//2、节点存在
while (true){
if(myConf.getConf().equals("")){
System.out.println("conf diu le");
watchCallBack.aWait();
}
System.out.println(myConf.getConf());
Thread.sleep(3000);
}
}
}
4、通过注释掉的代码可以知道我们需要很大的代码量,会很乱,所以我们要把监控的watch封装起来和回调的Callback封装起来
package com.sy.utlis;
import com.sy.config.MyConf;
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;
/**
* 功能: 异步回调方法的watch --层
* <p>异步回调方法的watch --层</p >
* <ul>
* <li>数据库: 增 删 改 查 </li>
* <li>{@link WatchCallBack} connect to {@link WatchCallBack}</li>
* </ul>
*
* @Program: zookeeper
* @Date: 2022-03-30 11:04
* @Version 1.0
* @Author yang.su
*/
public class WatchCallBack implements Watcher , AsyncCallback.StatCallback, AsyncCallback.DataCallback {
ZooKeeper zk;
MyConf myConf;
CountDownLatch cc = new CountDownLatch(1);
public MyConf getMyConf() {
return myConf;
}
public void setMyConf(MyConf myConf) {
this.myConf = myConf;
}
public ZooKeeper getZk() {
return zk;
}
public void setZk(ZooKeeper zk) {
this.zk = zk;
}
public void aWait() throws InterruptedException {
zk.exists("/appConf",this,this,"exists");
cc.await();
}
//watch的重写
@Override
public void process(WatchedEvent event) {
switch (event.getType()) {
case None:
break;
case NodeCreated:
//该节点本来没有,发生了创建这个事件
zk.getData("/appConf",this,this,"getDataCreated");
break;
case NodeDeleted:
//容忍性,取了这个数据之后,这个数据遭到删除是否处理,根据自己需求定
myConf.setConf("");
//并且需要给cc重新复制,不然已经结束阻塞
cc=new CountDownLatch(1);
break;
case NodeDataChanged:
System.out.println("节点数据改变");
zk.getData("/appConf",this,this,"getDataChanged");
break;
case NodeChildrenChanged:
break;
case DataWatchRemoved:
break;
case ChildWatchRemoved:
break;
case PersistentWatchRemoved:
break;
}
}
//StatCallback的重写,exists异步调用方法,获取数据是否存在的方法
@Override
public void processResult(int rc, String path, Object ctx, Stat stat) {
System.out.println(ctx.toString());
if(stat !=null){
zk.getData("/appConf",this,this,"getData1");
}
}
//DataCallback获取数据时的异步调用方法
@Override
public void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) {
System.out.println(ctx.toString());
String s = new String(data);
myConf.setConf(s);
cc.countDown();
}
}
5、回调的方法有了,运行的方法也有了,那么如何把异步回调时的数据返回给主线程?我们需要创建一个接收数据的实体类
package com.sy.config;
/**
* 功能: 用于获取数据 --层
* <p>用于获取数据 --层</p >
* <ul>
* 这个class是你未来最关心的地方
* <li>数据库: 增 删 改 查 </li>
* <li>{@link MyConf} connect to {@link MyConf}</li>
* </ul>
*
* @Program: zookeeper
* @Date: 2022-03-30 15:25
* @Version 1.0
* @Author yang.su
*/
public class MyConf {
public String getConf() {
return conf;
}
public void setConf(String conf) {
this.conf = conf;
}
private String conf;
}
以上一系列的代码完成分布式配置,比较绕,需要多看,多写
zookeeper的分布式锁
分布式锁在上一篇文章中有提过,这里只记录代码
1、分布式配置时的获取zookeeper的可以复用,所以我们先建立一个lock的测试类
package com.sy.lock;
import com.sy.utlis.ZkUtils;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooKeeper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* 功能: 锁的测试类 --层
* <p>锁的测试类 --层</p >
* <ul>
* <li>数据库: 增 删 改 查 </li>
* <li>{@link TestLock} connect to {@link TestLock}</li>
* </ul>
*
* @Program: zookeeper
* @Date: 2022-03-31 10:47
* @Version 1.0
* @Author yang.su
*/
public class TestLock {
ZooKeeper zk;
@Before
public void conn() throws InterruptedException {
zk = ZkUtils.getZk();
}
@After
public void close(){
try {
zk.close();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Test
public void lock(){
for (int i = 0; i <10 ; i++) {
new Thread(){
public void run(){
WatchCallBack watchCallBack = new WatchCallBack();
watchCallBack.setZk(zk);
String name = Thread.currentThread().getName();
watchCallBack.setThreadName(name);
//每一个线程;
//抢锁
watchCallBack.tryLock();
//干活
System.out.println("干活");
// try {
// //如果运行时间太短的话,因为是异步调用的,可能监控还没有建立,而后面的unlock已经删除了,所以导致后面的锁没有运行成功
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//释放锁
try {
watchCallBack.unLock();
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
while (true){
}
}
}
2、建立回调和watch的类
package com.sy.lock;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* 功能: --层
* <p> --层</p >
* <ul>
* <li>数据库: 增 删 改 查 </li>
* <li>{@link WatchCallBack} connect to {@link WatchCallBack}</li>
* </ul>
*
* @Program: zookeeper
* @Date: 2022-03-31 10:56
* @Version 1.0
* @Author yang.su
*/
public class WatchCallBack implements Watcher , AsyncCallback.StringCallback, AsyncCallback.ChildrenCallback, AsyncCallback.StatCallback {
ZooKeeper zk;
String threadName;
CountDownLatch cc = new CountDownLatch(1);
String pathName;
public String getPathName() {
return pathName;
}
public void setPathName(String pathName) {
this.pathName = pathName;
}
public ZooKeeper getZk() {
return zk;
}
public void setZk(ZooKeeper zk) {
this.zk = zk;
}
public String getThreadName() {
return threadName;
}
public void setThreadName(String threadName) {
this.threadName = threadName;
}
public void tryLock(){
try {
System.out.println(threadName+"create");
//创建序列节点
zk.create("/lock",threadName.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL,this,"创建数据的进程");
//解除阻塞状态
cc.await();
} catch (Exception e) {
e.printStackTrace();
}
}
public void unLock() throws KeeperException, InterruptedException {
zk.delete(pathName,-1);
System.out.println("释放锁成功");
}
//Wacher的方法
@Override
public void process(WatchedEvent event) {
switch (event.getType()) {
case None:
break;
case NodeCreated:
break;
case NodeDeleted:
//注意,删除事件并不是广播的,不关心锁目录的变化,因为在前面的设置中,每个人是盯着前面的节点
zk.getChildren("/",false,this,"释放锁之后的操作");
break;
case NodeDataChanged:
break;
case NodeChildrenChanged:
break;
case DataWatchRemoved:
break;
case ChildWatchRemoved:
break;
case PersistentWatchRemoved:
break;
}
}
//StringCallbask的方法
@Override
public void processResult(int rc, String path, Object ctx, String name) {
System.out.println(ctx.toString());
if (name!=null){
System.out.println(threadName+"create node"+name);
pathName =name;
//因为父节点中需要创建若干个子节点,所以不要watch
zk.getChildren("/",false,this,"用于获取序列节点");
}
}
//ChildrenCallback的方法
@Override
public void processResult(int rc, String path, Object ctx, List<String> children) {
//到这里证明可以看到该节点中已经存在的数据,List<String> children就是当前创建完成时,已经存在的节点的集合
System.out.println(ctx.toString());
// System.out.println(threadName+"look locks.........");
// for (String child : children) {
// System.out.println(child);
// }
//获取的集合没有顺序,需要排序
Collections.sort(children);
//获取的pathName是有斜杠的,所以需要截取
//拍段是否是第一个元素
int i = children.indexOf(pathName.substring(1));
//是
if(i==0){
System.out.println(threadName+"i am first");
//?
try {
//-1是忽略版本信息
zk.setData("/",threadName.getBytes(),-1);
//使进程阻塞
cc.countDown();
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
//需要watch,因为我们就是为了让分布式锁监控前一个是否释放锁
//还需要回调吗?需要,因为检测是否存在未必成功,虽然刚刚在上面的代码中确定了该节点存在,但是说不定在运行到这的时候会突然被删除,所以需要回调
zk.exists("/"+children.get(i-1),this,this,"exists的回调");
}
}
//statCallbask的回调
@Override
public void processResult(int rc, String path, Object ctx, Stat stat) {
//
}
}
以上就实现了分布式锁,还有需要优化的地方,比如解决重入锁,解决statCallBask的回调(因为正常情况下不需要回调,但是不正常的时候是需要回调的)
以上代码虽然不长,但是因为各种回调,watch,有点类似于递归,所以比较烧脑,需要慢慢看,多看