Zookeeper实现分布式锁原理与实现

3 篇文章 0 订阅
1 篇文章 0 订阅

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);
    }
}

运行结果

代码还需进一步优化,后续会贴出来,欢迎点赞,评论

参考博客网址:https://www.cnblogs.com/liuyang0/p/6800538.html

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值