ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
今天要讲的是,利用Zookeeper的分布式特性,实现分布式锁,下图是实现的原理
1. 多个线程(可以是分布式下的),向Zookeeper中的主节点(/lock)创建临时有序的子节点( /lock/test/000001) ,排序号是自动生成的。
2. 线程获取锁的时候,判断该线程创建的字节是否是排序号最小的,如果是则获取锁,如果不是则进入等待状态。
3. 线程执行完毕,释放锁删除子节点,并通知等待的进程获取锁。
本文默认用户都部署并启动了一套zookeeper程序,如有需要,自行搜索,接下来直接用demo来实现以上的思路
新建springboot模板工程,并导入zookeeper依赖
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.12</version>
</dependency>
新建一个类实现 Lock、Watcher接口,实现方法具体如下
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* 分布式锁
* author zjj
*/
public class ZookeeperLock implements Lock, Watcher {
private ZooKeeper zk = null;
//根节点
private String ROOT_LOCK = "/locks";
//竞争的资源
private String lockName;
//等待前一个锁
private String WAIT_LOCK;
//当前锁
private String CURRENT_LOCK;
//计数器
private CountDownLatch countDownLatch;
//等待锁的时间30s
private int sessionTimeout = 30000;
private List<Exception> exceptionList = new ArrayList<>();
public ZookeeperLock(String config, String lockName) {
this.lockName = lockName;
try {
zk = new ZooKeeper(config, sessionTimeout, this);
Stat stat = zk.exists(ROOT_LOCK, false);
//如果节点不在,则创建根节点
if (stat == null) {
zk.create(ROOT_LOCK, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
@Override
public void process(WatchedEvent event) {
//节点监视器,计数
if (this.countDownLatch != null) {
this.countDownLatch.countDown();
}
}
@Override
public void lock() {
if (exceptionList.size() > 0) {
throw new LockException(exceptionList.get(0));
}
try {
if (this.tryLock()) {
System.out.println(Thread.currentThread().getName() + " " + lockName + "获得了锁");
return;
} else {
//等待锁
waitForLock(WAIT_LOCK, sessionTimeout);
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
private boolean waitForLock(String prev, long waitTime) throws KeeperException, InterruptedException {
Stat stat = zk.exists(ROOT_LOCK + "/" + prev, true);
if (stat != null) {
System.out.println(Thread.currentThread().getName() + "等待锁" + ROOT_LOCK + "/" + prev);
this.countDownLatch = new CountDownLatch(1);
//计数等待,若前一个节点消失,则process中进行countDown,停止等待,获取锁
this.countDownLatch.await(waitTime, TimeUnit.SECONDS);
this.countDownLatch = null;
System.out.println(Thread.currentThread().getName() + "等到了锁");
}
return true;
}
@Override
public void lockInterruptibly() throws InterruptedException {
this.lock();
}
@Override
public boolean tryLock() {
try{
String splitStr = "_lock_";
if(lockName.contains(splitStr)){
throw new LockException("锁名有误");
}
//创建临时有序节点
CURRENT_LOCK = zk.create(ROOT_LOCK+"/"+lockName+splitStr,new byte[0],ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
//去所有子节点
List<String> subNodes = zk.getChildren(ROOT_LOCK,false);
//去除所有的lockName
List<String> lockObjects = new ArrayList<>();
for(String node:subNodes){
String _node = node.split(splitStr)[0];
if(_node.equals(lockName)){
lockObjects.add(node);
}
}
Collections.sort(lockObjects);
System.out.println(Thread.currentThread().getName()+"的锁是"+CURRENT_LOCK);
if(CURRENT_LOCK.equals(ROOT_LOCK + "/" + lockObjects.get(0))){
return true;
}
//若不是最小节点,则找到自己的前一个节点
String prevNode = CURRENT_LOCK.substring(CURRENT_LOCK.lastIndexOf("/") + 1);
WAIT_LOCK = lockObjects.get(Collections.binarySearch(lockObjects, prevNode) - 1);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
try{
if(this.tryLock()){
return true;
}
return waitForLock(WAIT_LOCK,time);
} catch (KeeperException e) {
e.printStackTrace();
}
return false;
}
@Override
public void unlock() {
try{
System.out.println("释放锁"+CURRENT_LOCK);
zk.delete(CURRENT_LOCK,-1);
CURRENT_LOCK = null;
zk.close();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
@Override
public Condition newCondition() {
return null;
}
public class LockException extends RuntimeException {
private static final long serialVersionUID = 1L;
public LockException(String e) {
super(e);
}
public LockException(Exception e) {
super(e);
}
}
}
首先介绍一下类的成员变量
zk : zoookeeper客户端实例
ROOT_LOCK : 根节点名称
lockName : 锁的名称
WAIT_LOCK : 等待的锁的节点名称
CURRENT_LOCK : 当前持有该锁的节点名称
countDownLatch :计数器,用于等待锁
sessionTimeout:zookeeper连接超时时间
构造函数中,连接Zookeeper,并创建根节点 ROOT_LOCK
实现获取锁的方法 lock()
使用tryLock()判断是否可以获取锁,是:输出锁,否:执行等待锁方法waitForLock()
以"/lockName_lock_xxxxx"创建临时有序的子节点,赋值给CURRENT_LOCK
并获取所有的子节点,判断子节点是否属于lockName,存放进集合
对集合排序,如果集合的第一个值(最小值)与CURRENT_LOCK相等,返回true获取锁成功
否则获取它的前一个值,赋值给WAIT_LOCK,返回false进行等待
使用countDownLatch(1)进行线程等待,最长时间为waitTime,或等待process方法执行提前结束等待
释放锁,zookeeper删除该节点,并关闭zookeeper
修改ip , 修改lockName,运行测试程序
public class ZookeeperLockTest {
static int n = 10;
private static Runnable runnable = new Runnable() {
public void run() {
ZookeeperLock lock = null;
try {
lock = new ZookeeperLock( "ip:2181" , "lockName");
lock.lock();
doSomething();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock != null) {
lock.unlock();
}
}
}
};
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Thread t = new Thread(runnable);
t.start();
}
}
public static void doSomething() throws InterruptedException {
Thread.sleep(1000);
System.out.println(--n);
}
}
运行结果
代码还需进一步优化,后续会贴出来,欢迎点赞,评论