分布式锁之zookeeper

引言

在上一篇文章分布式事务之数据库锁中,我们明白了基于mysql数据库行锁能完成分布式事务,同时在最后留了个问题:当某个app加完行锁后因为某种原因非正常退出,此时该app并未释放行锁,导致其他app没法获取锁,该如何处理?接下来这篇文章将针对这个问题提出我们的解决思路,即采用zookeeper实现分布式锁。

解决方案

在实现zookeeper的分布式锁前,先理解zookeeper的临时节点生成分布式锁的原理,临时节点就是zookeeper在创建节点时,该节点是临时,一旦app和zookeeper的会话结束或者其他网络等问题超时,zookeeper就会自动删除该节点,所以叫临时节点,所有的app在启动的时候都向zookeeper进行注册临时节点,一旦发现有其他节点已经注册了,那其他节点就等待下去直到该节点的事务处理完释放锁,释放锁后其他节点就可以向该节点重新竞争获取锁,这样就能保证所有的app在同一个时候只有一个app能获取锁,这样不就解决了我们app忘记释放锁导致的问题了。但这里还有一个问题是,如果我的app有成千上万个,当锁释放后所有的app都会等到该通知,这样在该瞬时会造成“羊群效应”,那我们又没其他的办法呢?答案是采用zookeeper的临时顺序节点,即zookeeper给每个app分配一个全局唯一的序列号,序列号大的只监听比其小一号的节点,这样一旦释放锁,只会有一个app得到通知取获取锁,这样就可以解决我们的问题了。

实现方案

下面来看我们的实现:

public class DistributedLock 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;
    private int sessionTimeout = 30000;
    private List<Exception> exceptionList = new ArrayList<Exception>();

    /**
     * 配置分布式锁
     * @param config 连接的url
     * @param lockName 竞争资源
     */
    public DistributedLock(String config, String lockName) {
        this.lockName = lockName;
        try {
            // 连接zookeeper
            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();
        }
    }

    // 节点监视器
    public void process(WatchedEvent event) {
        if (this.countDownLatch != null) {
            this.countDownLatch.countDown();
        }
    }

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

    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);
            System.out.println(CURRENT_LOCK + " 已经创建");
            // 取所有子节点
            List<String> subNodes = zk.getChildren(ROOT_LOCK, false);
            // 取出所有lockName的锁
            List<String> lockObjects = new ArrayList<String>();
            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;
    }

    public boolean tryLock(long timeout, TimeUnit unit) {
        try {
            if (this.tryLock()) {
                return true;
            }
            return waitForLock(WAIT_LOCK, timeout);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    // 等待锁
    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);
            // 计数等待,若等到前一个节点消失,则precess中进行countDown,停止等待,获取锁
            this.countDownLatch.await(waitTime, TimeUnit.MILLISECONDS);
            this.countDownLatch = null;
            System.out.println(Thread.currentThread().getName() + " 等到了锁");
        }
        return true;
    }

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

    public Condition newCondition() {
        return null;
    }

    public void lockInterruptibly() throws InterruptedException {
        this.lock();
    }


    public class LockException extends RuntimeException {
        private static final long serialVersionUID = 1L;
        public LockException(String e){
            super(e);
        }
        public LockException(Exception e){
            super(e);
        }
    }
}
public class LockTest {
    private int n=60; //简单测试计数器,测试线程在zookeeper锁后能否是顺序执行
    public void secskill(){
        System.out.println(--n);
    }
    @Test
    public void testLock(){
        Runnable runnable1 = new Runnable() {
            public void run() {
                DistributedLock lock = null;
                try {
                    lock = new DistributedLock("10.7.8.102:2181", "test1");
                    lock.lock();//加锁
                    secskill();//这里来处理我们的业务逻辑
                    System.out.println(Thread.currentThread().getName() + "正在运行");
                } finally {
                    if (lock != null) {
                        lock.unlock();//业务处理完成后释放锁
                    }
                }
            }
        };
         //10个线程并发执行
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(runnable1);
            t.start();//
        }
        //防止主线程退出
        while(true){
            try {
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

来看下测试结果:

... ...
Thread-0等待锁 /locks/test1_lock_0000000044
Thread-4等待锁 /locks/test1_lock_0000000048
Thread-13等待锁 /locks/test2_lock_0000000055
Thread-5等待锁 /locks/test2_lock_0000000050
Thread-9等待锁 /locks/test2_lock_0000000046
Thread-10等待锁 /locks/test1_lock_0000000057
Thread-1等待锁 /locks/test2_lock_0000000049
Thread-18等待锁 /locks/test1_lock_0000000051
Thread-6等待锁 /locks/test1_lock_0000000058
57
Thread-17正在运行
释放锁 /locks/test2_lock_0000000042
56
Thread-2正在运行
释放锁 /locks/test1_lock_0000000044
Thread-3 等到了锁
55
Thread-3正在运行
释放锁 /locks/test2_lock_0000000043
Thread-0 等到了锁
54
... ...

结论

基于zookeeper能方便的实现分布式锁,尤其是解决了会话的超时自动释放,自动监听一个节点等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章