文章目录
先行步骤
spring boot中引入如下依赖
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.2</version>
</dependency>
创建多级节点
/**
* 由于zk不支持递归创建 所以这里创建节点都得手动逐级创建
*/
@Test
void createDeepNode() {
ZooKeeper client = null;
try {
client = new ZooKeeper(ipAddress, 50000, null);
client.create("/updateVideo", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
client.create("/updateVideo/dance", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
client.create("/updateVideo/dance/20201101", "dance dance dance".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
// client.setData("/更新视频", "这是Data,可以写一些关于业务的参数".getBytes(), -1);
// client.delete("/更新视频", -1);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
client.close();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果
[zk: localhost:2181(CONNECTED) 0] ls -R /updateVideo
/updateVideo
/updateVideo/dance
/updateVideo/dance/20201101
查看节点是否存在示例
@Test
void isNodeExist() {
ZooKeeper client = null;
try {
client = new ZooKeeper(ipAddress, 50000, null);
Stat stat = client.exists("/updateVideo", false);
logger.info("当前节点是否存在 {}", stat != null ? "存在" : "不存在");
} catch (Exception e) {
} finally {
try {
client.close();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
获取结果
@Test
void getData() throws Exception {
ZooKeeper client = null;
client = new ZooKeeper(ipAddress, 50000, null);
byte[] data = client.getData("/updateVideo/dance/20201101", false, null);
logger.info("result {}", new String(data));
client.close();
}
获取子节点
@Test
void getChildren() throws Exception {
ZooKeeper client = null;
client = new ZooKeeper(ipAddress, 50000, null);
List<String> children = client.getChildren("/updateVideo", false);
logger.info("result {}", children);
client.close();
}
设置订阅
第一种
设置defaultWatcher,无论何种动作都用以此类作为订阅者
@Test
void register1() throws Exception {
// 方式1
ZooKeeper client = new ZooKeeper(ipAddress, 50000, new Watcher() {
// 这个就是 defaultWatcher 参数,是当前客户端默认的回调实现
@Override
public void process(WatchedEvent event) {
System.out.println("这是本客户端全局的默认回调对象");
}
});
// exists
client.exists("/updateVideo", true);
// getData
client.getData("/updateVideo/dance", true, null);
// getChildren
client.getChildren("/updateVideo", true);
client.close();
}
第二种(推荐)
在对应操作里面传入对应订阅者对象,这样做可以保证这个类只作为这个路径的回调对象
@Test
void register2() throws Exception {
ZooKeeper client = null;
client = new ZooKeeper(ipAddress, 50000, null);
// 方式2
// exists
client.exists("/updateVideo", new Watcher() {
@Override
public void process(WatchedEvent event) {
System.out.println("我是回调对象的实现");
}
});
// getData
client.getData("/updateVideo/dance", new Watcher() {
@Override
public void process(WatchedEvent event) {
System.out.println("我是回调对象的实现");
}
}, null);
// getChildren
client.getChildren("/updateVideo", new Watcher() {
@Override
public void process(WatchedEvent event) {
System.out.println("我是回调对象的实现");
}
});
client.close();
}
分布式锁
工作原理
假设多个客户端需要操作同一个节点,为了保证操作的顺序,我们要求每个客户端在操作这个节点前,先去获取锁,如下图所示,每个客户端都需要在lock节点下创建一个临时的锁节点,每个锁都会有一个序列号,序列号小的锁拥有者会最先操作节点,创建次小锁节点的用户必须等到最小序列号的锁拥有者完成操作且将锁释放(即在zookeeper节点中删除)后,才能进行操作。所以排在后面的节点必须监听排在他前面的节点。
基于原生的分布式锁实现
package com.example.zookeeperDemo;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class ZkLock {
private static final Logger logger = LoggerFactory.getLogger(ZkLock.class);
// zookeeper server 列表
private String connectString =
"ip:2181";
// 超时时间
private int sessionTimeout = 100000;
private ZooKeeper zk;
private String rootNode = "locks";
private String subNode = "seq-";
// 当前 client 等待的子节点
private String waitPath;
//ZooKeeper 连接
private CountDownLatch connectLatch = new CountDownLatch(1);
//ZooKeeper 节点等待
private CountDownLatch waitLatch = new CountDownLatch(1);
// 当前 client 创建的子节点
private String currentNode;
public ZkLock() throws Exception {
zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
// 连接建立时, 打开 latch, 唤醒 wait 在该 latch 上的线程
if (watchedEvent.getState() ==
Event.KeeperState.SyncConnected) {
connectLatch.countDown();
}
// 发生了 waitPath 的删除事件
if (watchedEvent.getType() ==
Event.EventType.NodeDeleted && watchedEvent.getPath().equals(waitPath)) {
waitLatch.countDown();
}
}
});
//等待上方代码进程完成连接后 再让代码往下走
connectLatch.await();
Stat stat = zk.exists("/" + rootNode, false);
if (stat == null) {
zk.create("/" + rootNode, "locks".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}
public void lock() throws Exception {
currentNode = zk.create("/" + rootNode + "/" + subNode, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
Thread.sleep(10);
List<String> children = zk.getChildren("/" + rootNode, false);
if (children.size() == 1) {
return;
} else {
Collections.sort(children);
String currentNodeSeq = currentNode.substring(("/" + rootNode + "/").length());
int index = children.indexOf(currentNodeSeq);
if (index == -1) {
logger.error("当前节点不存在,创建格式错误");
} else if (index == 0) {
return;
} else {
this.waitPath = "/" + rootNode + "/" + children.get(index - 1);
zk.getData(waitPath, true, new Stat());
waitLatch.await();
return;
}
}
}
public void unLock() throws Exception {
zk.delete(this.currentNode, -1);
}
public static void main(String[] args) throws Exception {
ZkLock lock1 = new ZkLock();
ZkLock lock2 = new ZkLock();
new Thread(new Runnable() {
@Override
public void run() {
try {
lock1.lock();
logger.info("*****************lock1取锁成功");
Thread.sleep(5*1000);
lock1.unLock();
logger.info("*****************lock1解锁成功");
} catch (Exception e) {
logger.error("lock1锁定出错"+e);
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
lock2.lock();
logger.info("*****************lock2取锁成功");
Thread.sleep(5*1000);
lock2.unLock();
logger.info("*****************lock2解锁成功");
} catch (Exception e) {
logger.error("lock1锁定出错"+e);
}
}
}).start();
}
}
基于开源框架Curator的实现
可参考这个开源项目
Curator 框架整合 Spring Boot 操作 ZooKeeper 并使用分布式工具,示范项目
关键代码解释
zk锁配置
package com.github.hellogithub;
import org.apache.curator.framework.CuratorFramework;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.zookeeper.config.CuratorFrameworkFactoryBean;
import org.springframework.integration.zookeeper.lock.ZookeeperLockRegistry;
/**
* @author:
* @date: 2020/12/8 4:34 下午
* @description:
*/
@Configuration
public class ZookeeperLockConfiguration {
@Value("${zookeeper.host:xxxx:2181}")
private String zkUrl;
@Bean
public CuratorFrameworkFactoryBean curatorFrameworkFactoryBean() {
return new CuratorFrameworkFactoryBean(zkUrl);
}
@Bean
public ZookeeperLockRegistry zookeeperLockRegistry(CuratorFramework curatorFramework) {
return new ZookeeperLockRegistry(curatorFramework, "/HG-lock");
}
}
创建两个测试映射
如下文所示,我们先调用http://127.0.0.1:9999/lock10
,这个接口会得到lock对象,然后休眠10s。
这时,我们又立刻调用http://127.0.0.1:9999/immediate
,这个接口会在/lock10
释放锁候得到锁。时间间隔大概10s左右,这就是zk在高并发场景实现分布式锁的示例。
package com.github.hellogithub;
import org.springframework.integration.support.locks.LockRegistry;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
/**
* @author:
* @date: 2020/12/8 4:31 下午
* @description:
*/
@RestController
public class TestController {
@Resource
private LockRegistry lockRegistry;
@GetMapping("debug")
public String dubug() {
System.out.println(lockRegistry);
return "SSSS";
}
@GetMapping("/lock10")
public String lock10() {
System.out.println("lock10 start " + System.currentTimeMillis());
final Lock lock = lockRegistry.obtain("lock");
try {
lock.lock();
System.out.println("lock10 get lock success " + System.currentTimeMillis());
TimeUnit.SECONDS.sleep(10);
} catch (Exception e) {
} finally {
lock.unlock();
}
return "OK";
}
@GetMapping("/immediate")
public String immediate() {
System.out.println("immediate start " + System.currentTimeMillis());
final Lock lock = lockRegistry.obtain("lock");
try {
lock.lock();
System.out.println("immediate get lock success " + System.currentTimeMillis());
} finally {
lock.unlock();
}
return "immediate return";
}
}
控制台输出结果
lock10 start 1633186840089
lock10 get lock success 1633186840146
immediate start 1633186841520
immediate get lock success 1633186850213
操作锁定时zk下节点的变化
可以看出,两个映射在取锁时,会先后在zk上创建临时顺序节点,然后分先后顺序完成操作候释放锁,并删除临时节点
[zk: localhost:2181(CONNECTED) 9] ls -R /HG-lock
/HG-lock
/HG-lock/lock
/HG-lock/lock/_c_4861b729-4e90-4c94-bdf1-10a55aec213f-lock-0000000001
/HG-lock/lock/_c_f89f6356-4905-43b2-9a4a-5d4f2af9e7bc-lock-0000000000
[zk: localhost:2181(CONNECTED) 10] ls -R /HG-lock
/HG-lock
/HG-lock/lock