基于ZOOKEEPER的一个实现了Lock接口的分布式锁

    网上有很多分布式锁的实现都是基于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;   //唯一标识当前进程的ID
    private Lock localLock = null;  //本地锁,可适当避免频繁进行网络通讯,在一个进程需要进行锁的时候可在本地进行处理,只有获得了本地锁才能尝试去获得分布式锁。
    private static final String ZKPATH = "/sblock/lock/";
    public static String APP_NAME = "_SDLOCK_"; //和使用同一个zk的其它应用作区分
    private String lockPath = null;
    private ISDClient zkClient = null;
    private String _reallyLockId;   //真正唯一标识当前锁对象的
    //产生唯一标识此进程的UUID
    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() {
        // TODO Auto-generated method stub
        return null;
    }

    private String getLockPath(){
        if(lockPath==null)
            lockPath = ZKPATH+APP_NAME+"/"+lockId;
        return lockPath;
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值