在有curator这个框架之前的时候,然后zookeeper怎么去实现分布式锁,就是利用刚才的那种方案,就是可以去实现,
你会有各种各样的疑问,你刚才讲的设计是得先get一下,你不如直接create,因为创建性能还不是那么高的,其实你有各种各样的疑问
你得去了解他,你要知道怎么做才能快,你要为性能考虑才能那样去做,之前这块也说了,我们往上走一走吧,很多介绍的东西,
你当时有一个大体的概念,但是没有一个很详细的概念,刚才在这里为什么那么去说,就是他的数据基本上是存在内存中的,
就是他的一些视图模型,单一视图,都是在内存中的,所以说,在JMater百分百读的场景下,并发可以达到这个,12到13万,
你想想吧,所以get的性能是很高的,我一个client端,只要网络没有太大的问题,没有延迟的情况下,get一下马上就能得到反馈,
这个效率是非常高的,所以之前我先get一下有没有,你不能get都不get就直接create,等着那块就抛异常了,等着报异常就不好,
一般我们不那么去做,就是让性能能更好,都是先去get一下,先去内存里去取,看有没有这条数据,然后再决定到底要不要create,
极少量的情况下会发生两个同时get,这个时候创建点肯定有一个先后顺序的,肯定是一个成功,一个失败的,这就是zookeeper怎么
去实现分布式这块
然后再往下看,还是要看这个API,其实倒不如直接讲Curator框架,如果不讲这些东西,你直接拿去用的话,面试的时候是很吃亏的,
zookeeper怎么去实现分布式锁的,你得讲明白是用临时节点,为什么啊,因为临时节点效率也高,第二点我什么要用get啊,
因为zookeeper是有这么一个特性,数据是存在内存中的,get的性能是非常高的,所以说我这么去做,是有理由有依据的,
然后咱们再去看一下API,这块要说的一点,咱们是不允许递归创建节点的,你父节点没有创建的情况下是不允许创建子节点的,
咱们可以试一试
我们先ls /一下
我把testRoot删掉, rmr /testRoot
然后ls /,当前只有一个zookeeper节点,不允许递归创建节点,父节点不存在的情况下是不允许创建节点的,
它是不允许支持序列化框架的,比如我们能不能做这么一件事,user的key就换一个主键ID,value就放一个user对象,
或者就一个JAVA对象,这个zookeeper是不支持的,以后它是不支持序列化的,你可以去使用一些第三方的序列化框架,
比如Hessian,Kryo序列号框架,就是这些序列号框架,Kryo框架的性能也是非常好的,包括dubbo中也是采用了一种方式,
第三个参数是一个权限节点,模模糊糊不知道是什么概念,Ids它是一个API,它是一个静态的,然后他有一个OPEN_ACL_UNSAFE,
开放权限即可,这个表示开放权限,后期有一些参数,基本上我从来没有关注过,包括很多API封装的时候,第三方框架就写死了,
一般在权限没有太高要求的情况下是没有必要关注的,你就不用费这个事去关注了,相关的问题我们通过网络,不会考虑太详细,
只不过是他提供的功能是这样,这个不用关注,然后最后一个是创建节点类型,有4种类型,有持久化的,有持久化顺序的,
有个先后顺序,当然会有个什么问题,你可能调一个方法,一个client端C1,现在10个节点,那你这10个节点要有顺序,
可能网络走到这个时候,第一个创建可能绕了一圈,第二个可能是没有先后顺序的,如果你有顺序的话你就可以去看看,
你这个zookeeper要做一个队列,你可以create一个sequential,就是你要那他做一个队列的一个事,其实很多技术可以提供一个解决
方案,就是Redis有一个list类型,它是有一些附加的功能,只不过这种实现起来会比较难,你要抓住最核心的作用是干什么的,
然后你辅助的功能,很简单的情况下你可以去用,我们不用消息队列,你想实现基于内存的,很多问题你自己可以去解决,
两种解决你可以自己去做,包括这个zookeeper也一样,他想实现一个队列,他可以去提供这个接口,一般有没有人这么去做呢,
这是我强调的一个问题,他不仅仅能实现一点,我只是大概提一下,然后往下走,还有一种是异步的方式,节点也分同步和异步,
同步的方式就是我创建完了之后,我发一个请求到zookeeper上,我这边得到一个返回值,这是同步的方式,我的代码肯定是在这一点
延迟,创建调API,同步方式,异步呢,代码是直接往下走,我又起了一个额外的线程,去监听zookeeper的返回,这是可以去实现的,
这里有一个异步方式
package com.learn.zookeeper.base;
import java.util.concurrent.CountDownLatch;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooKeeper;
/**
* 这就是关于zookeeper增删改查操作
* 简单的讲一下
* 里面有一些特殊的方法
* 每一套API
* 无论是exist,delete,还是get
* 都有两套
* 一种是同步的方式
* 一种是回调的方式
* 然后基本上去操作API呢
* 会有一些限制
* 比如说不能递归的去创建啊
* 不能递归的去删除
* 去掉getChildren方法的时候
* 十六进制JAVA怎么表示来着
* 有点不记得了
* 用byte[]字节数组
* get只能取得一层
* 你不能把下面的节点也取得到
* 有很多限制
* 这是关于咱们zookeeper的API
*
* @author Leon.Sun
*
*/
public class ZookeeperBase {
static final String CONNECT_ADDR = "59.110.138.145:2181";
// static final String CONNECT_ADDR = "192.168.80.88:2181,192.168.80.87:2181,192.168.80.86:2181";
static final int SESSION_OUTTIME = 2000;//ms
/** 信号量,阻塞程序执行,用于等待zookeeper连接成功,发送成功信号 */
static final CountDownLatch connectedSemaphore = new CountDownLatch(1);
public static void main(String[] args) throws Exception{
ZooKeeper zk = new ZooKeeper(CONNECT_ADDR, SESSION_OUTTIME, new Watcher(){
@Override
public void process(WatchedEvent event) {
KeeperState keeperState = event.getState();
EventType eventType = event.getType();
//如果是建立连接
if(KeeperState.SyncConnected == keeperState){
if(EventType.None == eventType){
try {
System.out.println("开始连接.....");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("zk 建立连接");
//如果建立连接成功,则发送信号量,让后续阻塞程序向下执行
connectedSemaphore.countDown();
}
}
}
});
//进行阻塞
connectedSemaphore.await();
System.out.println("执行啦..");
Thread.sleep(5000);
//创建父节点
// String ret = zk.create("/testRoot", "testRoot11".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
// System.out.println(ret);
//创建子节点
/**
* 现在我想又一次创建
* 肯定是没有/testRoot这个节点的
* 就像创建/children
* 行吗
* 肯定是不行的
* 咱们运行一下吧
* KeeperException$NoNodeException: KeeperErrorCode = NoNode for /testRoot/children
* 就是你没有父节点你就想创建子节点
* zookeeper原生的API是不支持的
* 怎么去实现的
* 你得先判断一下
* 先来个if再来个else
* 其实Curator的框架怎么去封装的呢
* 无非就是先if判断当前
* 如果路径很长的话
* 还有个aaa
* 还有个ccc
* 一层一层的去判断
* 先判断children他的上一级有没有
* 再判断ccc他的上一级有没有
* aaa他的上一级有没有
* 其实就是这个意思
* 只要他的上一级没有我就抛一个异常
* 然后像Curator框架
* 如果不存在我就一层一层的创建
* 如果/testRoot这个不存在就创建出来
* 如果/aaa这个不存在就创建出来
* /ccc这个不存在我还得创建出来
* 他有个ifparentexist的方法
* 不允许去创建没有的父节点的
* 咱们把刚才的事说一下
* 把刚才的事说一下
* 不允许递归创建节点
*
*
*/
// String ret = zk.create("/testRoot/aaa/ccc/children", "children data".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
/**
* 如果你这么去创建是不行的
* 咱们ls /一下
* 我现在根节点下只有一个zookeeper
* 然后是没有testRoot的
* 创建它会返回一个什么值呢
* 它是抛了一个异常
* KeeperException$NoNodeException: KeeperErrorCode = NoNode for /testRoot/children
* 在原生的zookeeper
* 它是不允许你递归的去创建节点的
* 以后的Curator框架
* 会有一个ifparentexist
* 它会帮你把父节点创建好
* 这也是原生API的问题
* 其实Curator也是把原生的API进行一个封装
* 无非是在判断节点的时候判断一下他上个节点是不是存在
* 如果不存在就把他创建好
* 如果存在的话就创建节点
* 这是第一个问题
* 在原生的API中是不允许递归创建节点的
*/
// String ret = zk.create("/testRoot/children", "children data".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
/**
* create其实是有CallBack和Object的
* 就是每个方法都提供了两套API
* 一个是正常的创建同步的创建
* 还有个是异步的创建
* 异步创建效率有点高
* 咱们同步的是一个
* 异步的去删除
* 创建成功了
* /testRoot
* 然后我在下面ls /
* 下面就有testRoot这个节点了
* 然后接下来进行delete
* 这个Delete操作其实也很简单
*
* 这里先注释
* 否则再创建就会报exists节点已经存在
*
* /testRoot这个创建好了以后
* 我再去取的时候就有了
* dataVersion = 0
* 版本号的标识被改了几次
* update了几次
*
*
*/
// String ret = zk.create("/testRoot", "testRoot11".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
// System.out.println(ret);
/**
* 现在这样去做
* 我这里delete一个节点
* 其实它这个delte方法也有
* zookeeper能存数据的版本号
* 比如我们去get /testRoot
* dataVersion = 0
* 你会发现这个版本号是0
* 这里有很多version
* 但是我们就看dataVersion
* 第一次是0
* 比如我修改一下
* set /testRoot 2222
* 我再去get的时候发现dataVersion改变了
* dataVersion = 1
* 如果我在改一下
* set /testRoot 5555
* dataVersion = 2
* 我能取到每一个版本号
* 对应的你要delete哪一个版本号的数据
* 你要是写-1就是全清空
* 就是全删除
* 一般我们去做delete节点的话
* 现在的testRoot的版本号已经到了2了
*
* 现在我想去删除
* delete一般传了第二个参数-1
* 4个参数吗
* 然后这里有一个VoidCallback
* Object ctx2可以传入一个Callback的参数
* 实例化出来VoidCallback这么一个对象
* 这个是一个静态的
* 它是异步的
*
*
*/
// zk.delete("/testRoot", -1,new VoidCallback() {
//
// /**
// * 然后你要去重载一个方法是这个processResult
// *
// * 这就是我之前说的一个callback方法
// * 每一个delete都会存在一个回调
// *
// */
// @Override
// public void processResult(int rc, String path, Object ctx2) {
// /**
// * 我休眠1秒钟
// */
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// /**
// * 第一个int类型的东西是啥啊
// * 响应码
// * 如果是0表示回调成功
// * 如果是-4表示端口连接
// * 如果是-110表示指定节点存在
// * -112表示会话已经过期
// * callback可能成功可能失败
// * 能有好多个状态
// */
// System.out.println(rc);
// /**
// * path表示节点的参数路径是什么
// *
// * 第二个参数是path
// * 当前删除的节点是什么
// */
// System.out.println(path);
// /**
// * 然后还有一些东西
// * ctx上下文环境
// * 还有一个name
// * 很多回调的参数
// * 我现在只取了这三个
// *
// */
// System.out.println(ctx2);
// }
//
// /**
// * 这个a是一个Object
// * 就是传入任何一个参数
// * 都会回调到ctx2这里
// * 咱们之前学RocketMQ的事务
// * 事务的callback里面也会有一个参数
// * 其实一个道理
// * 这是一个callback的一个机制
// * -1是跳过版本检查
// * 如果不是当前的版本就不删除了
// * 我数据库可能有一份记录
// * 当前咱两是不是一个版本号
// * 如果是的话我做一个check
// */
// },"a");
System.out.println("继续执行.......");
/**
* 在这里等待这么长的时间
*/
// Thread.sleep(5000);
/**
* 我现在直接做一个删除
* 成功了以后我们再去get
* get /testRoot
* Node does not exist: /testRoot
* 一般不考虑版本号
* 一般用-1
*
*/
// zk.delete("/testRoot", 2);
/**
* 其实你也不用休眠了
*/
// Thread.sleep(10000);
//获取节点洗信息
/**
* 接下来看获得节点
* ZooKeeper.getData(String path, boolean watch, Stat stat)
* watch先不用理会
* stat状态呢也不用理会
* 因为如果你要讲API的话
* 但是不用关注后面两个参数
* 其实就是想去获得节点的value值
* value值返回的还是一个data类型
* testRoot咱们包装成一个String
* 基本上就是这个操作
* 先讲简单的再讲复杂的
* 咱们create /testRoot 1122
* 创建成功以后ls /
* 看到testRoot了
* 咱们现在执行一下看有没有效果
* 后面两个参数我们后面会讲
* 先不用关注这么复杂的东西
*
*
*/
// byte[] data = zk.getData("/testRoot", false, null);
// /**
// * 现在我想获得一堆怎么办
// *
// */
System.out.println(new String(data));
//
// /**
// * 一堆children我想获得的话
// *
// */
System.out.println(zk.getChildren("/testRoot", false));
//
// /**
// * 他返回的是一个String类型的list
// * ZooKeeper.getChildren(String path, boolean watch)
// * 第二个参数是watch
// * 总是有一个watch的东西
// * 这个东西其实很烦
// * create /testRoot/a1 1111
// * create /testRoot/a2 2222
// * create /testRoot/a5 2222
// * 现在ls /testRoot下面
// * [a1, a2, a5]
// * 你会发现有三个节点三个孩子了
// * 他相当于一个父亲了
// * 然后我现在getChildren的时候
// * 把孩子都给拿出来了
// * 这是很简单的
// * 返回的类型已经固定了
// *
// */
// List<String> list = zk.getChildren("/testRoot", false);
//
// /**
// * 我就去循环他
// *
// */
// for (String path : list) {
// /**
// * 这个path是一个相对的路径
// * a1,a2,a3
// * 如果你想取值的话
// * 你得加上前面的父路径/testRoot
// * 然后a1,a2,a3
// * 他取出来的是一个相对的
// * 我就用之前取的方法
// *
// * 我这块就是这样去做的
// * 我直接循环path
// * 看path有没有就行了
// * 你会发现这是没有的
// * 就是getChildren方法你想做递归呢
// * 他是只支持直接子节点
// * 能理解我说的意思吧
// * 这是肯定的
// * 原生API就是这么去实现的
// * 为什么这么去实现呢
// * 其实也是有他的道理的
// * 这是关于getChildren方法的
// *
// *
// */
// System.out.println(path);
// String realPath = "/testRoot/" + path;
// /**
// * realPath这个路径传进去
// * 得到的结果就是1111,2222,2222这个了
// * 这个应该很简单
// * 暂时不用去理会这个事
// * 先把这个简单的API讲完
// * 再加一层就不行了
// * 你试一下
// * 我在a1下面还有一层b1
// * a1下面有一个b1
// * create /testRoot/a1/b1 11
// * 咱们可能就是这种情况了
// * 我现在能不能把b1也取出来呢
// *
// *
// */
// System.out.println(new String(zk.getData(realPath, false, null)));
// }
/**
* 修改节点其实也很简单
* 修改节点
* testRoot原先叫什么名字咱们看一下
* get /testRoot
* 原先叫1122
* 我想给他改成"modify data root"这个
* -1就是不检查版本号了
* 不管你版本号这个问题了
* 修改完了再去取
* 就变成modify data root这个了
* get /testRoot
* 数据就变成modify data root这个了
* 这个就相当于update
*
*
*/
// zk.setData("/testRoot", "modify data root".getBytes(), -1);
// byte[] data = zk.getData("/testRoot", false, null);
// System.out.println(new String(data));
//修改节点的值
// zk.setData("/testRoot", "modify data root".getBytes(), -1);
// byte[] data = zk.getData("/testRoot", false, null);
// System.out.println(new String(data));
//判断节点是否存在
/**
* exists你可以先判断节点是否已经存在
* 比如a2
* 打印一下
* 它会返回一个布尔类型
* 4127,4127,1558515470535,1558515470535,0,0,0,0,4,0,4127
* 这个长的字符串不用理会
* 就跟cZxid = 0x101b这个有点像了
* 来试一下吧
* 每次都一样
* 我不知道行不行
*
* 如果你给一个a5
*
*/
// System.out.println(zk.exists("/testRoot/a1", false));
/**
* 如果你给一个a8
* 我在运行的话
* 返回的就是一个Null
* null就证明这个节点不存在
* 如何去判断是否存在
* 现在也把exist这个方法测试OK了
* 然后最后还有一个delete
* 这个delete就不用说了
* 就是想删除一个节点
* 既然不支持递归的创建
*
* get /testRoot
* 我现在查的是a2
* 那我还得这么去做
* get /testRoot/a2
* cZxid = 0x1035
* a2是这个值也应该存在
*
*
*
*/
System.out.println(zk.exists("/testRoot/a2", false));
long k = 0x1035;
System.out.println(k);
// 删除节点
/**
* 原生的API只有两个方法
* 基本上你找不到递归删除的方法
*
*
*/
// zk.delete("/testRoot/a2", -1);
/**
* 删除这个就报异常了
* KeeperException$NotEmptyException: KeeperErrorCode = Directory not empty for /testRoot
* Directory not empty
* 这个Directory不是空的
* 下面有孩子你就删不了了
* 既不支持递归的创建
* 也不支持递归的删除
* 能理解这个意思吗
* 这是最基本的API操作
* 让你去了解zookeeper最基本的操作
*
*
*/
// zk.delete("/testRoot", -1);
// System.out.println(zk.exists("/testRoot/a2", false));
zk.close();
}
}