文章目录
-
- 一、概述
- 二、导入依赖包
- 三、创建锁的过程
-
- [3.1 通过 create 创建节点信息](#3.1 通过 create 创建节点信息)
- [3.2 AsyncCallback.StringCallback 回调函数](#3.2 AsyncCallback.StringCallback 回调函数)
- [3.3 AsyncCallback.Children2Callback 的回调函数](#3.3 AsyncCallback.Children2Callback 的回调函数)
- [3.4 Watcher 的回调函数](#3.4 Watcher 的回调函数)
- 四、完整示例
-
- [4.1 完整分布式锁代码](#4.1 完整分布式锁代码)
- [4.2 测试类](#4.2 测试类)
如果您还没有安装Zookeeper请看ZooKeeper 安装说明,Zookeeper 命令使用方法和数据说明,Zookeeper Java 开发入门。
一、概述
-
情景:假设有10个客户端(分散的10台主机)要执行一个任务,这个任务某些过程需要保持原子性。那么我们就需要一个分布式锁。
-
原理:通过在Zookeeper中创建序列节点来实现获得锁,删除节点来释放锁。其实质是一个按先来后到的排序过程,实现过程如下:
-
客户端发起请求,创建锁序列节点(/lock/xxxxxxxx)
-
获取所有锁节点,判断自己是否为最小节点
- 如果自己是最小序列节点,则立即获得锁。
- 否则不能获得锁,但要监控前一个序列节点的状态。
-
获得锁的客户端开始执行任务。
-
执行完任务后释放锁。
-
由于后一个节点监控了前一个节点,当前一个节点删除时,后一个客户端会收到回调。
-
在这个回调中再获取所有锁节点,判断自己是否为最小节点。
-
以此类推,直到全部结束。
-
-
-
流程如下
- 如果您对没有做过 Zookeeper 开发,强烈建立先看 Zookeeper Java 开发入门。
二、导入依赖包
-
在 pom.xml 文件中导入 Zookeeper 包,注意一般这个包的版本要和您安装的 Zookeeper 服务端版本一致。
xml<dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.8.2</version> </dependency>
三、创建锁的过程
3.1 通过 create 创建节点信息
- 通过 create 创建序列节点信息。他是异步方式,创建成功后会调用 AsyncCallback.StringCallback.processResult 回调函数。
java
public void lock() throws InterruptedException, LockException {
zooKeeper.create("/lock", "xxx".getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL, this, context);
countDownLatch.await();
if(StringUtils.isEmpty(this.lockNodePath)){
throw new LockException("创建锁失败");
}
System.out.println(this.appName + " 获得锁");
}
3.2 AsyncCallback.StringCallback 回调函数
- 在 AsyncCallback.StringCallback 的回调函数中通过 getChildren 方法获取 ZooKeeper 锁节点下的所有节点信息。这个方法是异步的,调用成功后会调用 AsyncCallback.Children2Callback.processResult 回调函数。
java
// AsyncCallback.StringCallback
@Override
public void processResult(int i, String s, Object o, String s1) {
if(StringUtils.isEmpty(s1)){
// 这里是创建锁失败的情况。
this.countDownLatch.countDown();
return;
}
System.out.println(this.appName + " create lock node="+s1);
this.lockNodePath = s1;
// 获取 ZooKeeper 锁节点下的所有节点信息,以此来判断我是不是第一个创建节点,如果就获得锁,否则监控前一个节点信息。
zooKeeper.getChildren("/", false, this, context);
}
3.3 AsyncCallback.Children2Callback 的回调函数
- 在 AsyncCallback.Children2Callback 的回调函数判断我是不是第一个创建节点,如果就获得锁,否则监控前一个节点信息。监控前一个节点信息使用 exists 方法,这个方法设置了 Watcher 的 processResult 回调函数
java
// AsyncCallback.Children2Callback
@Override
public void processResult(int i, String s, Object o, List<String> list, Stat stat) {
Collections.sort(list);
// for (String s1 : list) {
// System.out.println("\t "+this.lockNodePath+" previous lock node="+s1);
// }
int index = list.indexOf(lockNodePath.substring(1));
if(0 == index){
// 如果我现在是第一个节点,则获得锁
try {
zooKeeper.setData("/", this.lockNodePath.getBytes(), -1);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
this.countDownLatch.countDown();
}
else {
// 我不是第一个节点,监控前一个节点信息(等他删除后我就是可能是第一个了)
String watchNodePath = "/" + list.get(index - 1);
System.out.println("\t "+this.lockNodePath+" watch node:"+ watchNodePath);
zooKeeper.exists(watchNodePath, this, new StatCallback() {
@Override
public void processResult(int i, String s, Object o, Stat stat) {
}
}, context);
}
}
3.4 Watcher 的回调函数
- 在 Watcher 的回调函数,我们通过判断 watchedEvent.getType() 为 NodeDeleted 类型时,重新获取 ZooKeeper 锁节点下的所有节点信息,这使得消息回到了 "3.3"步,判断谁是第一个节点,然后获得得,完成整个流程。
java
// Watcher
@Override
public void process(WatchedEvent watchedEvent) {
switch (watchedEvent.getType()) {
case None:
break;
case NodeCreated:
break;
case NodeDeleted:
zooKeeper.getChildren("/", false, this, context);
break;
case NodeDataChanged:
break;
case NodeChildrenChanged:
break;
case DataWatchRemoved:
break;
case ChildWatchRemoved:
break;
case PersistentWatchRemoved:
break;
}
}
四、完整示例
4.1 完整分布式锁代码
java
package top.yiqifu.study.p131;
import org.apache.commons.lang3.StringUtils;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class ZookeeperLock implements Watcher, AsyncCallback.StringCallback,
AsyncCallback.Children2Callback {
private String appName;
private ZooKeeper zooKeeper;
private Object context;
private String lockNodePath;
private CountDownLatch countDownLatch = new CountDownLatch(1);
public ZookeeperLock(String name, ZooKeeper zk){
this.appName = name;
this.zooKeeper = zk;
this.context = this;
}
public void lock() throws InterruptedException, LockException {
zooKeeper.create("/lock", "xxx".getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL, this, context);
countDownLatch.await();
if(StringUtils.isEmpty(this.lockNodePath)){
throw new LockException("创建锁失败");
}
System.out.println(this.appName + " 获得锁");
}
public void unlock() throws KeeperException, InterruptedException, LockException {
if(StringUtils.isEmpty(this.lockNodePath)){
throw new LockException("没有获得锁,无法释放锁");
}
zooKeeper.delete(lockNodePath, -1);
System.out.println(this.appName + " 释放锁");
}
// AsyncCallback.StringCallback
@Override
public void processResult(int i, String s, Object o, String s1) {
if(StringUtils.isEmpty(s1)){
// 这里是创建锁失败的情况。
this.countDownLatch.countDown();
return;
}
System.out.println(this.appName + " create lock node="+s1);
this.lockNodePath = s1;
// 获取 ZooKeeper 锁节点下的所有节点信息,以此来判断我是不是第一个创建节点,如果就获得锁,否则监控前一个节点信息。
zooKeeper.getChildren("/", false, this, context);
}
// AsyncCallback.Children2Callback
@Override
public void processResult(int i, String s, Object o, List<String> list, Stat stat) {
Collections.sort(list);
// for (String s1 : list) {
// System.out.println("\t "+this.lockNodePath+" previous lock node="+s1);
// }
int index = list.indexOf(lockNodePath.substring(1));
if(0 == index){
// 如果我现在是第一个节点,则获得锁
try {
zooKeeper.setData("/", this.lockNodePath.getBytes(), -1);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
this.countDownLatch.countDown();
}
else {
// 我不是第一个节点,监控前一个节点信息(等他删除后我就是可能是第一个了)
String watchNodePath = "/" + list.get(index - 1);
System.out.println("\t "+this.lockNodePath+" watch node:"+ watchNodePath);
zooKeeper.exists(watchNodePath, this, new StatCallback() {
@Override
public void processResult(int i, String s, Object o, Stat stat) {
}
}, context);
}
}
// Watcher
@Override
public void process(WatchedEvent watchedEvent) {
switch (watchedEvent.getType()) {
case None:
break;
case NodeCreated:
break;
case NodeDeleted:
zooKeeper.getChildren("/", false, this, context);
break;
case NodeDataChanged:
break;
case NodeChildrenChanged:
break;
case DataWatchRemoved:
break;
case ChildWatchRemoved:
break;
case PersistentWatchRemoved:
break;
}
}
public class LockException extends Exception
{
public LockException(String message){
super(message);
}
}
}
4.2 测试类
java
package top.yiqifu.study.p131;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class Test06_ZookeeperLock {
public static void main(String[] args) {
try {
// 创建 ZooKeeper 对象
final ZooKeeper zooKeeper = testCreateZookeeper();
int clientCount = 10;
final CountDownLatch countDownLatch = new CountDownLatch(clientCount);
for (int i = 0; i < clientCount; i++) {
new Thread(new Runnable(){
@Override
public void run() {
TestLock(zooKeeper);
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
private static void TestLock(ZooKeeper zooKeeper){
try {
String appName = Thread.currentThread().getName();
ZookeeperLock zookeeperLock = new ZookeeperLock(appName, zooKeeper);
// 加锁(获得分布式锁)
zookeeperLock.lock();
System.out.println(appName + " 执行任务");
Thread.sleep(1000);
// 释放锁
zookeeperLock.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
} catch (ZookeeperLock.LockException e) {
e.printStackTrace();
}
}
private static ZooKeeper testCreateZookeeper() throws IOException, InterruptedException, KeeperException {
final CountDownLatch countDownLatch = new CountDownLatch(1);
// ZooKeeper 集群地址(没连接池的概念,是Session的概念)
//String connectionString = "192.168.8.51:2181,192.168.8.52:2181,192.168.8.53:2181";
String connectionString = "192.168.8.51:2181,192.168.8.52:2181,192.168.8.53:2181/aaa";
// ZooKeeper Session 数据超时时间(也就是这个Session关闭后,与这个Session相关的数据在ZooKeeper中保存多信)。
Integer sessionTimeout = 3000;
// ZooKeeper Session 级别 Watcher(Watch只发生在读方法上,如 get、exists)
final ZooKeeper zooKeeper = new ZooKeeper(connectionString, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
try {
Event.KeeperState state = watchedEvent.getState();
Event.EventType type = watchedEvent.getType();
String path = watchedEvent.getPath();
switch (state) {
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;
case Closed:
break;
}
switch (type) {
case None:
break;
case NodeCreated:
break;
case NodeDeleted:
break;
case NodeDataChanged:
break;
case NodeChildrenChanged:
break;
case DataWatchRemoved:
break;
case ChildWatchRemoved:
break;
case PersistentWatchRemoved:
break;
}
System.out.println("Session watch state=" + state);
System.out.println("Session watch type=" + type);
System.out.println("Session watch path=" + path);
} catch (Exception e) {
e.printStackTrace();
}
}
});
countDownLatch.await();
ZooKeeper.States state = zooKeeper.getState();
switch (state) {
case CONNECTING:
break;
case ASSOCIATING:
break;
case CONNECTED:
break;
case CONNECTEDREADONLY:
break;
case CLOSED:
break;
case AUTH_FAILED:
break;
case NOT_CONNECTED:
break;
}
System.out.println("ZooKeeper state=" + state);
return zooKeeper;
}
}