本文的项目:zookeeper-demo1
一.简介
1.功能及用途
- 1.维护配置信息
- 2.分布式锁
- 3.集群管理
- 4.生成分布式唯一id
2.设计目标及优点
优点
- 1.高性能
内存,存储数据 - 2.高可用
集群部署 - 3.严格顺序访问
生成的全局唯一的id,分布式锁
对比redis
redis | zookeeper | |
---|---|---|
存储类型 | 内存 | 内存 |
数据类型 | k-v | 树形结构 |
性能 | 高 | 中 |
高可用 | 好 | 好 |
目标 | 数据缓存 | 分布式辅助功能 |
应用场景 | 缓存数据库 | 分布式锁/配置中心 |
设计目标
由于zookeeper设计时的目标就是作为分布式辅助功能,所以zookeeper采用的是树形结构,而不是速度更快的k-v结构,这就注定了zookeeper的引用场景不是作为缓存,虽然zookeeper也可以作为缓存.
二.数据模型
1.数据模型结构
- redis是使用k-v结构
- zookeeper是使用树状结构,类似于linux的文件结构 节点的表示
/xxx/yyy
2.节点的属性
1.cZxid : 数据节点创建时的事务ID
2.从Time : 节点创建的时间
3.mZxid : 节点最后一次更新是的事务ID
4.mTime : 节点最后一次更新的时间
5.pZxid : 节点最后一次呗修改时的事务ID
6.cversion : 子节点的更改次数
7.dataVersion :节点数据更改的次数
8.aclVersion : 节点ACL的更改次数
9.ephemeralOwer : 表示是临时节点还是永久节点;临时节点为会话的SessionID,持久化节点为0;
10.dataLength :数据长度
11.numChildren :子节点个数
三.单机安装
1.在linux上使用root用户创建zookeeper用户,密码可设置为: zookeeper
- useradd zookeeper
#添加用户 - passwd zookeeper
#添加密码
2.安装JDK,因为zookeeper是java开发的,所以zookeeper依赖于JDk
- 上传JDK
- 解压JDK : tar -xvf jdk-8u51-linux-x64.tar.gz
3.配置java环境变量
配置环境变量: vim /etc/profile
4.检验jdk安装是否成功
检查java环境变量是否配置成功: java -version
5.上传解压zookeeper
- 上传zookeeper文件
- 解压zookeeper: tar -xvf zookeeper-3.4.8.tar.gz
6.为zookeeper准备配置文件
- 在根目录下,创建一个data和log目录 : mkdir data log
- 进入当前zookeeper根目录的 conf目录
- 复制配置文件: cp zoo_sample.cfg zoo.cfg
- 编辑配置文件: vim zoo.cfg
1.端口号
2.修改data目录 ,指定data目录为我们刚刚创建的data目录
3.设置log目录,指定log目录为刚刚我们创建的log目录
- 启动zookeeper
1.进入zookeeper根目录下的 bin目录
2.启动服务器: sh zkServer.sh start [zoo1.cfg] #可以指定配置文件,不指定默认是在conf目录下的zoo.cfg配置文件
四.zookeeper常用命令(都是在bin目录下)
1.服务器相关命令
(1) 启动服务器:
- sh zkServer.sh start [zoo.cfg]
#可以指定配置文件,不指定默认是在conf目录下的zoo.cfg配置文件
(2) 关闭服务器:
- sh zkServer stop [zoo.cfg]
#可以指定配置文件,不指定默认是在conf目录下的zoo.cfg配置文件
(3)查看当前服务器状态:
-
sh zkServer status [zoo.cfg]
#可以指定配置文件,不指定默认是在conf目录下的zoo.cfg配置文件
(4)登陆服务器
-
sh zkCli.sh
#默认登陆的是本地的2181端口的服务器 -
sh zkCli.sh -server 127.0.0.1:2182
#登陆到其他服务器
2.数据操作(先登陆到服务上)
(1)新增节点
create [-s] [-e] path data
#其中-s为有序节点,-e为临时节点,默认为持久化节点
-
持久化节点
1.创建: create /a “a”
2.取值: get /a -
有序节点
1.创建: create -s /b “b”
2.取值: get /a0000000003
#/a0000000003 这里的是代指我们创建时,服务器分配给我的节点号
常用来作为分布式的全局id -
临时节点(会话过期之后被删除)
1.创建: create -e /c cc
2.取值: get /c -
临时有序节点
1.创建: create -s -e /aa “aa”
2.取值: get /aa00000001
# /aa00000001这个是服务器给我们分配的节点号;常用来作为分布式事务锁,会话结束就消失,保证不会死锁,有序保证分布式有序行
(2)修改节点:
- set path data [数据版本号]
#修改节点
(3)删除节点
-
delete path [数据版本号]
#删除节点(该节点没有子节点) : -
rmr path
#删除该节点及其子节点
(4)查看节点和状态
-
get path
#返回属性和数据 -
stat path
#只返回该节点的属性,不包含数据
(4)查看节点列表
- ls path
#查看节点的所有子节点列表
(5)监听器(一次监听器,只能监听一次变化,用了就失效了)
-
get path watch
#监听该节点的数据变化 -
ls/ls2 path watch
#监听子节点发生变化
作用:监听配置的变化,如果配置发生变化,则我们需要重新获取配置
(6)权限控制(针对节点操作的权限)
1)授权的三个主要特性
- 授权模式 : 授权的策略
- 授权对象
- 权限
2)授权模式
-
world
#默认的,任何人都可操作 -
ip
#指定ip才能操作 -
auth
#使用已添加认证的用户 -
digest
#用户+密码的方式
3)权限
- create c
#可以在该节点下创建子节点 - delete d
#可以删除子节点(仅仅是子节点,不能删除当前节点) - read r
#可以读取该节点及其子节点 - write w
#可以设置该节点数据 - admin a
#可以设置节点访问控制的权限
4)权限相关语法
[1] 授权语法
setAcl path acl
#acl表示的授权模式+授权对象+具体的权限
-
world授权: setAcl path world:anyone:drwa
#设置该节点的权限为任何人可以drwa(删除/读/写/修改权限)的操作. -
ip授权:
setAcl path ip:192.168.10.100:cdrwa[,ip:192.168.10.101:cdrwa]
#授予这个IP地址cdrwa权限.[]方括号内表示添加多个ip授权,则用逗号分开 -
author授权
-
登陆授权用户: addauth digest 用户名:密码
#不登录则无法获取该节点数据 -
授权: setAcl path auth:用户名:密码:wa
#针对用户wa权限,要获取该节点数据之前,则必须登陆
-
-
digest授权
1.先获取密码的密文
2.授权: setAcl path digest:用户名:密码:wa #针对用户wa权限,密码必须使用密文 -
同时使用多种授权模式
setAcl path world:anyone:drwa,ip:192.168.10.100:cdrwa,auth:用户名:密码:wa
#多种授权模式.其实就是中间用逗号分隔开 -
超级管理员
1.生成超级管理员的密码密文
2.在脚本文件 zhServer.sh 中增加我们的超级管理员
[2]读取acl权限:
getAcl path
[3] 添加授权用户:
addauth digest 用户名:密码
五.zookeeper原生api
1.下载linux服务器上的zookeeper根目录下的zookeeper-3.4.14.jar和lib目录下的所有jar包
sz 文件名
2.将这些jar包导入我们的项目中
3.使用api
- 1)zookeeper连接对象的创建
try {
//创建计数器对象
CountDownLatch countDownLatch = new CountDownLatch(1);
/**
* 参数一:IP地址及端口
* 参数二:超时时间(毫秒)
* 参数三:监视器
*/
//连接单个zookeeper
//ZooKeeper zooKeeper = new ZooKeeper("118.190.58.27:2181", 5000, new Watcher() {
//连接集群,只需要在ip中用逗号隔开即可
ZooKeeper zooKeeper = new ZooKeeper("118.190.0.27:2181,118.190.0.27:2182,118.190.0.27:2183", 5000, new Watcher() {
@Override
public void process(WatchedEvent event) {
if(event.getState() == Event.KeeperState.SyncConnected){
System.out.println("连接创建成功!!");
//执行计数减1的,因为原来就是1,减去1就等于0了,所以countDownLatch.await()阻塞的代码就不阻塞了
countDownLatch.countDown();
}
}
});
//主线程阻塞等待连接对象的创建,当计数变为0时,则继续往下执行
countDownLatch.await();
//会话编号
System.out.println("当前会话id:" + zooKeeper.getSessionId());
zooKeeper.close();
}catch (Exception e){
e.printStackTrace();
}
-
2)创建节点对象
zooKeeper.create(...)
public class ZKcreate {
private ZooKeeper zooKeeper;
private String ip = "118.190.0.27:2181";
/**
* 构建zookeeper对象
*/
@Before
public void before(){
try {
//创建计数器对象
CountDownLatch countDownLatch = new CountDownLatch(1);
/**
* 参数一:IP地址及端口
* 参数二:超时时间(毫秒)
* 参数三:监视器
*/
zooKeeper = new ZooKeeper(ip, 5000, new Watcher() {
@Override
public void process(WatchedEvent event) {
if(event.getState() == Event.KeeperState.SyncConnected){
System.out.println("连接创建成功!!");
countDownLatch.countDown();
}
}
});
//主线程阻塞等待连接对象的创建
countDownLatch.await();
//会话编号
System.out.println("当前会话id:" + zooKeeper.getSessionId());
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 关闭zookeeper连接
*/
@After
public void after(){
try {
zooKeeper.close();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 同步创建zookeeper节点
* 设置world:anyone权限
*/
@Test
public void create1() throws Exception {
/**
* 参数一: 节点(该节点的上一个节点必须存在)
* 参数二: 节点数据
* 参数三: 权限列表 'world,'anyone
* 参数四: 创建持久化节点
*/
String s = zooKeeper.create("/create/node1", "node1".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println("创建成功!!!");
}
/**
* 同步创建zookeeper节点
* 设置create和write权限
*/
@Test
public void create2() throws Exception{
//授权列表
ArrayList<ACL> list = new ArrayList<>();
//授权模式和授权对象
Id id = new Id("world", "anyone");
list.add(new ACL(ZooDefs.Perms.CREATE,id));
list.add(new ACL(ZooDefs.Perms.WRITE,id));
String s = zooKeeper.create("/create/node2", "node2".getBytes(), list, CreateMode.PERSISTENT);
System.out.println("创建成功!!!");
}
/**
* 同步创建zookeeper节点
* 根据ip设置权限
*/
@Test
public void create3() throws Exception{
//授权列表
ArrayList<ACL> list = new ArrayList<>();
//授权模式和授权对象
Id id = new Id("ip", "192.168.0.101");
list.add(new ACL(ZooDefs.Perms.ALL,id));
String s = zooKeeper.create("/create/node3", "node3".getBytes(), list, CreateMode.PERSISTENT);
System.out.println("创建成功!!!");
}
/**
* 同步创建zookeeper节点
* 根据auth设置权限
*/
@Test
public void create4() throws Exception{
zooKeeper.addAuthInfo("digest","gl:123456".getBytes());
String s = zooKeeper.create("/create/node4", "node4".getBytes(), ZooDefs.Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT);
System.out.println("创建成功!!!");
}
}
-
3)修改节点对象
zooKeeper.setData(...)
/**
* 同步修改节点
*/
@Test
public void set1() throws Exception {
/**
* 参数一: 节点path
* 参数二: 节点数据
* 参数三: 版本 版本不一致,则修改不了,-1表示当前最新的版本,不指定版本号
*/
Stat stat = zooKeeper.setData("/set/node1", "111".getBytes(), -1);
System.out.println(stat);
}
/**
* 异步修改节点
*/
@Test
public void set2() throws Exception {
/**
* 参数一: 节点path
* 参数二: 节点数据
* 参数三: 版本 版本不一致,则修改不了,-1表示当前最新的版本,不指定版本号
*/
zooKeeper.setData("/set/node1", "222".getBytes(), -1, new AsyncCallback.StatCallback() {
/**
*
* @param rc 是否成功 0代表修改成功
* @param path 节点路劲
* @param ctx 上下文参数对象
* @param stat 属性表示对象
*/
@Override
public void processResult(int rc, String path, Object ctx, Stat stat) {
System.out.println("rc:" + rc);
System.out.println("path:" + path);
System.out.println("ctx:" + ctx);
System.out.println("stat:" + stat);
}
},"i am context");
Thread.sleep(1000);
System.out.println("结束");
}
-
4)删除节点对象
zooKeeper.delete(...)
/**
* 同步删除节点
*/
@Test
public void delete() throws KeeperException, InterruptedException {
zooKeeper.delete("/delete/node1",-1);
System.out.println("删除成功");
}
/**
* 异步删除节点
*/
@Test
public void delete2() throws KeeperException, InterruptedException {
zooKeeper.delete("/delete/node2", -1, new AsyncCallback.VoidCallback() {
/**
*
* @param rc 删除是否成功 0代表删除成功过
* @param path 删除成功
* @param ctx 上下文对象
*/
@Override
public void processResult(int rc, String path, Object ctx) {
System.out.println("rc:" + rc);
System.out.println("path:" + path);
System.out.println("ctx:" + ctx);
}
}, "i am context");
Thread.sleep(1000);
System.out.println("删除成功");
}
-
5)获取节点对象
zooKeeper.getData(...)
/**
* 同步获取节点数据
*/
@Test
public void get1() throws KeeperException, InterruptedException {
Stat stat = new Stat();
byte[] data = zooKeeper.getData("/get/node1", false, stat);
System.out.println(new String(data));
System.out.println("version:" + stat.getVersion());
}
/**
* 异步获取节点数据
*/
@Test
public void get2() throws KeeperException, InterruptedException {
Stat stat = new Stat();
zooKeeper.getData("/get/node1", false, new AsyncCallback.DataCallback() {
/**
* @param rc 0代表成功
* @param path 节点路劲
* @param ctx 上下文对象
* @param data 数据
* @param stat 属性对象
*/
@Override
public void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) {
System.out.println("rc:" + rc);
System.out.println("path:" + path);
System.out.println("ctx:" + ctx);
System.out.println("data:" + new String(data));
System.out.println("stat:" + stat);
}
},"i am context");
Thread.sleep(1000);
System.out.println("version:" + stat.getVersion());
}
-
6)获取子节点对象
zooKeeper.getChildren(...)
/**
* 同步获取子节点数据
*/
@Test
public void get1() throws KeeperException, InterruptedException {
Stat stat = new Stat();
List<String> data = zooKeeper.getChildren("/get", false, stat);
for (String datum : data) {
System.out.println(datum);
}
System.out.println("version:" + stat);
}
/**
* 异步获取子节点数据
*/
@Test
public void get2() throws KeeperException, InterruptedException {
Stat stat = new Stat();
zooKeeper.getChildren("/get", false, new AsyncCallback.ChildrenCallback() {
/**
* @param rc 0代表成功
* @param path 节点路劲
* @param ctx 上下文对象
* @param children 子节点数据
*/
@Override
public void processResult(int rc, String path, Object ctx, List<String> children) {
System.out.println("rc:" + rc);
System.out.println("path:" + path);
System.out.println("ctx:" + ctx);
for (String child : children) {
System.out.println(child);
}
}
}, "i am context");
Thread.sleep(1000);
System.out.println("version:" + stat.getVersion());
}
-
7)判断子节点对象是否存在
zooKeeper.exists(...)
/**
* 同步判断该节点是否存在
* stat: null表示不存在,否则是存在
*/
@Test
public void exist1() throws KeeperException, InterruptedException {
Stat stat = zooKeeper.exists("/get1", false);
System.out.println(stat);
}
/**
* 异步判断该节点是否存在
* stat: null表示不存在,否则是存在
*/
@Test
public void exist2() throws KeeperException, InterruptedException {
zooKeeper.exists("/get", false, new AsyncCallback.StatCallback() {
/**
* @param rc 0代表成功
* @param path 节点路劲
* @param ctx 上下文对象
* @param stat 节点状态对象
*/
@Override
public void processResult(int rc, String path, Object ctx, Stat stat) {
System.out.println("rc:" + rc);
System.out.println("path:" + path);
System.out.println("ctx:" + ctx);
System.out.println("stat:" + stat);
}
},"i am context");
Thread.sleep(1000);
}
五.zookeeper的事件监听
1.概念
监听某个节点的变化
2.架构
3.特性
-
1.一次性
#watcher是一次性的,一旦被触发就会被移除,再次使用需要重新注册 -
2.客户端顺序回调
#watcher回调顺序是串行化执行的,只有回调后客户端才能看到最新的数据状态,一个watcher回调逻辑不应该太多. -
3.轻量级
#watchEvent是最小的通信单元,结构上只包含通知状态,时间类型和节点路劲,并不会告诉数据节点变化前后的具体内容 -
4.时效性
#watcher只有在当前session彻底失效时才会无效,若在session有效期内快速重连成功,则watcher依然存在,仍然可接受到通知
4.监听节点事件的类型
1.None
2.NodeCreated #监听子节点被创建
3.Nodedelete #当前节点被删除
4.NodeDataChanged #监听节点数据变化
5.NodeChildrenChanged #监听子节列表发生变化
5.watcher的通知类型
1.SyncConnected 客户端与服务器建立连接时
2.DisConnected #客户端与服务器断开连接时
3.Expired #会话session失效时
4.AuthFailed #身份认证失败时
6.javaAPI注册watcher
exist 方法注册watcher
getData方法注册watcher
getChildren 方法注册watcher
exists方法注册watcher
public class ZKWathcerExists {
private ZooKeeper zooKeeper = null;
private String ip = "118.190.00.27:2181";
@Before
public void before() throws Exception{
CountDownLatch countDownLatch = new CountDownLatch(1);
//连接zookeeper
zooKeeper = new ZooKeeper(ip, 6000, new Watcher() {
@Override
public void process(WatchedEvent event) {
System.out.println("连接对象的额参数");
if(event.getState() == Event.KeeperState.SyncConnected){
countDownLatch.countDown();
}
System.out.println("path:" + event.getPath());
System.out.println("eventType:" + event.getType());
}
});
countDownLatch.await();
}
@After
public void after() throws InterruptedException {
System.out.println("结束");
zooKeeper.close();
}
/**
* 监听 /watcher节点.当节点发生变化的时候,返回变化类型
* @throws KeeperException
* @throws InterruptedException
*/
@Test
public void watcher1() throws KeeperException, InterruptedException {
//使用连接时的watcher监听
Stat exists = zooKeeper.exists("/watcher", true);
Thread.sleep(5000);
}
/**
* 监听 /watcher节点.当节点发生变化的时候,返回变化类型
* 值监听一次
* @throws KeeperException
* @throws InterruptedException
*/
@Test
public void watcher2() throws KeeperException, InterruptedException {
//使用自定义的watcher监听
Stat exists = zooKeeper.exists("/watcher", new Watcher() {
@Override
public void process(WatchedEvent event) {
System.out.println("path:" + event.getPath());
System.out.println("type:" + event.getType());
}
});
Thread.sleep(10000);
}
/**
* 监听 /watcher节点.当节点发生变化的时候,返回变化类型
* 对watcher整个生命周期都监听,监听多个事件
* @throws KeeperException
* @throws InterruptedException
*/
@Test
public void watcher3() throws KeeperException, InterruptedException {
//使用自定义的watcher监听
Stat exists = zooKeeper.exists("/watcher", new Watcher() {
@Override
public void process(WatchedEvent event) {
System.out.println("path:" + event.getPath());
System.out.println("type:" + event.getType());
try {
zooKeeper.exists("/watcher",this);
}catch (Exception e){
e.printStackTrace();
}
}
});
Thread.sleep(30000);
}
/**
* 监听 /watcher节点.当节点发生变化的时候,返回变化类型
* 一个节点可以注册多个监听器对象
* @throws KeeperException
* @throws InterruptedException
*/
@Test
public void watcher4() throws KeeperException, InterruptedException {
//使用自定义的watcher监听
zooKeeper.exists("/watcher", new Watcher() {
@Override
public void process(WatchedEvent event) {
System.out.println(1);
System.out.println("path:" + event.getPath());
System.out.println("type:" + event.getType());
}
});
zooKeeper.exists("/watcher", new Watcher() {
@Override
public void process(WatchedEvent event) {
System.out.println(2);
System.out.println("path:" + event.getPath());
System.out.println("type:" + event.getType());
}
});
Thread.sleep(10000);
}
创建zookeeper连接对象的时候注册watcher
public class ZKConnectionWatcher implements Watcher{ //实现watcher接口
private static CountDownLatch countDownLatch = new CountDownLatch(1);
private static ZooKeeper zooKeeper;
public static void main(String[] args) {
try {
zooKeeper = new ZooKeeper("118.190.58.27:2181", 5000, new ZKConnectionWatcher());
//阻塞线程,等待连接
countDownLatch.await();
Thread.sleep(1000);
zooKeeper.close();
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void process(WatchedEvent event) {
try {
//事件状态
if(event.getType() == Event.EventType.None){
if (event.getState() == Event.KeeperState.SyncConnected){
System.out.println("连接创建成功!");
}else if (event.getState() == Event.KeeperState.Disconnected){
System.out.println("断开连接");
}else if (event.getState() == Event.KeeperState.Expired){
System.out.println("会话超时!");
}else if(event.getState() == Event.KeeperState.AuthFailed){
System.out.println("认证失败!!");
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}
六.zookeeper的应用场景
1.zookeeper实现配置中心的管理
- 1.zookeeper中创建config中的信息
- 2.java代码连接zookeeper去读取数据
- 3.watcher机制去监视这些配置数据,如果发生变化,则重新读取数据
/**
* @author gl
* @time 2020-06-15 21:14
* @function : zookeeper作为配置中心,javaapi读取zookeeper中的节点信息
* 并且通过监听,一旦节点数据发生改变,则重新读取数据
* @step :
*/
public class MyConfigCenter implements Watcher {
private static CountDownLatch countDownLatch = new CountDownLatch(1);
private static final String ip = "118.190.58.27:2181";
private ZooKeeper zooKeeper;
private String url;
private String username;
private String password;
//创建对象的时候,就执行初始化方法
public MyConfigCenter(){
initValue();
}
/**
* 初始化发方法
*/
public void initValue(){
try {
zooKeeper = new ZooKeeper(ip, 5000, this);
//阻塞线程,等待连接创建的成功
countDownLatch.await();
this.url = new String(zooKeeper.getData("/config/url",this,null));
this.username = new String(zooKeeper.getData("/config/username",this,null));
this.password = new String(zooKeeper.getData("/config/password",this,null));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 监听
* @param event
*/
@Override
public void process(WatchedEvent event) {
try {
//事件状态
if(event.getType() == Event.EventType.None){
if (event.getState() == Event.KeeperState.SyncConnected){
System.out.println("连接创建成功!");
countDownLatch.countDown();
}else if (event.getState() == Event.KeeperState.Disconnected){
System.out.println("断开连接");
}else if (event.getState() == Event.KeeperState.Expired){
System.out.println("会话超时!");
zooKeeper = new ZooKeeper(ip,6000,new ZKConnectionWatcher());
}else if(event.getState() == Event.KeeperState.AuthFailed){
System.out.println("认证失败!!");
}
//当节点变化时,重新读取配置信息
}else if (event.getType() == Event.EventType.NodeDataChanged){
initValue();;
}
}catch (Exception e){
e.printStackTrace();
}
}
public String getUrl() {
return url;
}
public MyConfigCenter setUrl(String url) {
this.url = url;
return this;
}
public String getUsername() {
return username;
}
public MyConfigCenter setUsername(String username) {
this.username = username;
return this;
}
public String getPassword() {
return password;
}
public MyConfigCenter setPassword(String password) {
this.password = password;
return this;
}
}
2.zookeeper实现分布式id
- 1.利用javaAPi的create方法,创建一个临时有序节点,则zookeeper会为我们分配节点的节点序号,使用这个节点序号做为分布式id
3.zookeeper实现分布式锁
- 1.利用zookeeper的临时有序节点
- 2.监听上一个节点的状态
- 3.如果锁处于第一个位置则获取到锁
- 4.删除节点则释放锁
/**
* @author gl
* @time 2020-06-15 21:48
* @function :利用zookeeper实现分布式锁
* @step :
*/
public class MyLock {
//zookeeper连接的IP地址
private static final String IP = "118.190.58.27:2181";
//计数对象
CountDownLatch countDownLatch = new CountDownLatch(1);
//zookeeper的配置信息
ZooKeeper zooKeeper;
private static final String LOCK_ROOT_PATH = "/locks";
private static final String LOCK_ROOT_NAME = "Lock_";
private String lockPath ;
/**
* 构造方法,初始化zookeeper连接
*/
public MyLock(){
try {
zooKeeper = new ZooKeeper(IP, 5000, new Watcher() {
@Override
public void process(WatchedEvent event) {
if (event.getType() == Event.EventType.None){
if (event.getState() == Event.KeeperState.SyncConnected){
System.out.println("连接成功!");
countDownLatch.countDown();
}
}
}
});
countDownLatch.await();
}catch (Exception e){
e.printStackTrace();
}
}
//获取锁
public void acquireLock() throws Exception{
//创建锁节点
createLock();
//尝试获取锁
attemptLock();
}
//监视器对象,监视上一个节点是否被删除
Watcher watcher = new Watcher() {
@Override
public void process(WatchedEvent event) {
if(event.getType() == Event.EventType.NodeDeleted){
synchronized (this){
notifyAll();
}
}
}
};
//尝试获取锁
private void attemptLock() throws Exception{
//获取locks节点下的所有子节点
List<String> list = zooKeeper.getChildren(LOCK_ROOT_PATH, false);
//对子节点排序
Collections.sort(list);
// 节点名称: /Locks/lock_0000000001
int index = list.indexOf(lockPath.substring(LOCK_ROOT_PATH.length() + 1));
//如果当前路劲在第一位,则我们直接获取到锁
if(index == 0){
return;
}else {
//否则监听上一个节点信息
//上一个节点的路径
String path = list.get(index -1);
//监听上一个节点的状态
Stat stat = zooKeeper.exists(LOCK_ROOT_PATH + "/" + path, watcher);
//如果上一个节点状态为null,表示上一个节点已经删除,则我们重新获取锁
if(stat == null){
attemptLock();
}else {
//否则使线程等待
synchronized (watcher){
watcher.wait();
attemptLock();
}
}
}
}
//创建锁节点
private void createLock() throws Exception{
Stat stat = zooKeeper.exists(LOCK_ROOT_PATH, false);
if (stat == null){
zooKeeper.create(LOCK_ROOT_PATH,new byte[0],ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
}
lockPath = zooKeeper.create(LOCK_ROOT_PATH + "/" + LOCK_ROOT_NAME, new byte[0],
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println("节点创建成功!节点路径:" + lockPath);
}
//锁释放
public void releaseLock() throws Exception{
zooKeeper.delete(lockPath,-1);
zooKeeper.close();
}
//验证创建锁节点方法
public static void main(String[] args) throws Exception {
MyLock myLock = new MyLock();
myLock.createLock();
}
}