网上有很多分布式锁的实现都是基于zookeeper的,看得多了,也就想自己试着写一下,下面和大家分享下,本人水平很菜,有什么不好的地方大家给我建议吧~
先说下大概的思路:
**整个实现原理非常简单,就是每一个单独的进程都有自己唯一的标识,这个标识用UUID去生成,从而保证在同一台机器获取多台机器的不同进程间都是唯一的。
然后内部维护了一个ReentrantLock 本地锁对象,每个操作都必须先得到本地锁才能尝试得到分布式锁,可以避免下动不动就请求zookeeper的情况。
之后,构造方法中的lockId是唯一标识一个锁的,只要lockId相同,都会去竞争同一个锁,竞争的方法是看谁先在zookeeper上能够成功地创建出的目录(这个目录只和lockId有关,所以lockId相同的多个锁对象只能有一个持有锁,至于创建时怎样去保证唯一性这个复杂的问题就交给zookeeper去做了),创建目录成功的对象持有锁,释放锁的时候就是删除目录,删除目录后会触发预先定义好的watcher监听代码(这个逻辑也是zk去做的),让原本在等待中的各个对象重新去竞争这个锁,同样地,最终的胜利者只有一个,而万一原本的持锁对象还没释放就因一些乱七八糟的情况挂掉了,在这种情况下,因为创建的是临时目录,zk对临时目录的处理方式是创建这个目录的对象和zookeeper的连接丢失后这个目录就会收回去,也就等同于删除了这个目录了,原本处于等待中的其它对象也可以投入到新的竞争中了(zk太方便了,用它来实现分布式锁简直就是傻瓜式操作)...总体而言zookeeprt去实现分布式锁的大概思路就是这样。**
下面是一些具体的代码实现:
首先是一些基本接口的定义(操作zookeeper的接口):
public interface ISDClient {
boolean createPath(String path,String data);
boolean deletePath(String path);
boolean isExists(String path,Watcher watcher);
void createPersistentPath(String path);
}
这个接口主要是封装了一些基本的和分布式锁的实现有关的操作,具体的实现也不仅仅限于ZK,只要实现了这个接口的都能够用,不过需要能够区分临时目录和永久目录,永久目录是建完后就一直在的,临时目录是连接丢失后就收回的。
下面是这个接口的具体实现;
public class SZKClient implements ISDClient {
private ZooKeeper zooKeeper = null;
public SZKClient(String host,int port){
try {
zooKeeper = new ZooKeeper(host, port ,null);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void createPersistentPath(String path){
String [] paths = path.split("/");
String thisPath = "";
for(String tempPath : paths){
if(tempPath == null || tempPath.length() == 0) continue;
thisPath += "/"+tempPath;
try {
this.zooKeeper.create(thisPath, "".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
} catch (Exception e) {}
}
}
public boolean createPath(String path,String data) {
try {
this.zooKeeper.create(path, data.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
return true;
} catch (Exception e) {
return false;
}
}
public boolean deletePath(String path) {
try {
this.zooKeeper.delete(path, -1);
return true;
} catch (Exception e) {
return false;
}
}
public boolean isExists(String path, Watcher watcher) {
Stat state = null;
try {
state = this.zooKeeper.exists(path, watcher);
} catch (Exception e) {
e.printStackTrace();
}
return state!=null;
}
}
再然后是创建的watcher监听类,这个监听类用于监听指定目录的变更(主要是目录删除后会触发process方法,从而让等待中的锁对象能够获取到锁),因为zookeeper只会激发一次注册过的监听类,所以当获取锁失败后,需要重新注册一次,参见fail方法:
public abstract class LockWatcher implements Watcher {
private ISDClient zkClient = null;
private String path = null;
private String lockContent = null;
public LockWatcher(ISDClient zkClient,String path,String lockContent){
this.zkClient = zkClient;
this.path = path;
this.lockContent = lockContent;
}
public void process(WatchedEvent wevent) {
if(this.zkClient.createPath(path, lockContent)){
try{
this.success(lockContent);
}catch(Exception e){
this.zkClient.deletePath(path);
this.fail();
throw new RuntimeException("出现异常");
}
}else{
this.fail();
}
}
public abstract void success(String result);
public void fail(){
this.zkClient.isExists(this.path, this);
};
}
所有准备类都准备完成后,最后就是最最核心的实现了——SDLOCK,当然咯,SDLOCK这个名字是随便起的...:
public class SDLock implements Lock{
private String lockId;
private static String distributionId;
private Lock localLock = null;
private static final String ZKPATH = "/sblock/lock/";
public static String APP_NAME = "_SDLOCK_";
private String lockPath = null;
private ISDClient zkClient = null;
private String _reallyLockId;
static{
SDLock.distributionId = UUID.randomUUID().toString();
}
private String curLockId = null;
private synchronized void setCurLockId(String lockId){
curLockId = lockId;
}
/**
* SDLock的构造函数
* SDLock需要接受一个唯一标识,这个标识是在多台机器上唯一区分一个分布式锁的,同一个ID意味着在多个分布式环境下是同一个锁
* @param lockId
*/
public SDLock(String lockId,ISDClient zkClient){
this.lockId = lockId;
this.localLock = new ReentrantLock();
this.zkClient = zkClient;
curLockId = "";
this._reallyLockId = distributionId + "_"
+ Thread.currentThread().getId() + "_"
+ this.lockId;
this.zkClient.createPersistentPath(ZKPATH+APP_NAME);
}
public void lock() {
this.localLock.lock();
if(this.zkClient.createPath(this.getLockPath(), this._reallyLockId)){
curLockId = this._reallyLockId;
return;
}else{
this.zkClient.isExists(this.getLockPath(),
new LockWatcher(zkClient,this.getLockPath(),this._reallyLockId){
@Override
public void success(String result) {
setCurLockId(result);
}
});
try {
for(;;){
TimeUnit.MILLISECONDS.sleep(500);
if(this._reallyLockId.equals(curLockId))
return;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public void lockInterruptibly() throws InterruptedException {
if(Thread.currentThread().isInterrupted()) throw new InterruptedException();
this.lock();
}
public boolean tryLock() {
boolean getLocalLock = this.localLock.tryLock();
if(!getLocalLock) return false;
try{
if(getLocalLock){
if(this.zkClient.createPath(this.getLockPath(), this._reallyLockId)){
curLockId = this._reallyLockId;
return true;
}else{
return false;
}
}else return false;
}catch(Exception e){
if(getLocalLock)
this.localLock.unlock();
return false;
}finally{
this.localLock.unlock();
}
}
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
boolean getLocalLock = this.localLock.tryLock(time,unit);
if(!getLocalLock) return false;
try{
if(this.zkClient.createPath(this.getLockPath(), this._reallyLockId)){
curLockId = this._reallyLockId;
return true;
}else{
this.zkClient.isExists(this.getLockPath(),
new LockWatcher(zkClient,this.getLockPath(),this._reallyLockId){
@Override
public void success(String result) {
setCurLockId(result);
}
});
long waitTime = 0;
long totalWaitTime = unit.toMillis(time);
try {
for(;;){
unit.sleep(500);
if(this._reallyLockId.equals(curLockId))
return true;
waitTime += 500;
if(waitTime>=totalWaitTime) return false;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}catch(Exception e){
throw new RuntimeException(e);
}finally{
this.localLock.unlock();
}
}
public void unlock() {
boolean getlocallock = this.localLock.tryLock();
if(!getlocallock) return;
if(!this._reallyLockId.equals(this.curLockId)){
this.localLock.unlock();
return;
}
try{
this.zkClient.deletePath(this.getLockPath());
}catch(Exception e){
throw new RuntimeException(e);
}finally{
this.localLock.unlock();
}
}
public Condition newCondition() {
return null;
}
private String getLockPath(){
if(lockPath==null)
lockPath = ZKPATH+APP_NAME+"/"+lockId;
return lockPath;
}
}